diff options
author | crupest <crupest@outlook.com> | 2021-03-07 16:15:47 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2021-03-07 16:15:47 +0800 |
commit | 486663e6b4b2aa4addc4c84d24e1ce5252941858 (patch) | |
tree | ab4e6995049a660047614f642ac7d1ba5fb62c99 | |
parent | 7d74e849d96fa29019ef001b528df6612d93f085 (diff) | |
download | timeline-486663e6b4b2aa4addc4c84d24e1ce5252941858.tar.gz timeline-486663e6b4b2aa4addc4c84d24e1ce5252941858.tar.bz2 timeline-486663e6b4b2aa4addc4c84d24e1ce5252941858.zip |
feat: Add markdown content view.
-rw-r--r-- | FrontEnd/package-lock.json | 50 | ||||
-rw-r--r-- | FrontEnd/package.json | 1 | ||||
-rw-r--r-- | FrontEnd/src/app/views/timeline-common/TimelinePostContentView.tsx | 82 |
3 files changed, 130 insertions, 3 deletions
diff --git a/FrontEnd/package-lock.json b/FrontEnd/package-lock.json index fad2a473..ca680b26 100644 --- a/FrontEnd/package-lock.json +++ b/FrontEnd/package-lock.json @@ -59,6 +59,7 @@ "@types/react-router": "^5.1.12", "@types/react-router-bootstrap": "^0.24.5", "@types/react-router-dom": "^5.1.7", + "@types/remarkable": "^2.0.1", "@types/webpack-env": "^1.16.0", "@types/xregexp": "^4.3.0", "@typescript-eslint/eslint-plugin": "^4.16.1", @@ -1928,6 +1929,12 @@ "@types/node": "*" } }, + "node_modules/@types/highlight.js": { + "version": "9.12.4", + "resolved": "https://registry.npmjs.org/@types/highlight.js/-/highlight.js-9.12.4.tgz", + "integrity": "sha512-t2szdkwmg2JJyuCM20e8kR2X59WCE5Zkl4bzm1u1Oukjm79zpbiAv+QjnwLnuuV0WHEcX2NgUItu0pAMKuOPww==", + "dev": true + }, "node_modules/@types/history": { "version": "4.7.8", "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.8.tgz", @@ -2067,6 +2074,16 @@ "@types/react": "*" } }, + "node_modules/@types/remarkable": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/remarkable/-/remarkable-2.0.1.tgz", + "integrity": "sha512-4KTpW8aVjVkTgaWlEOAuorBAdlUrvlzzPlVrt9vsAAM6nB/nUln7j0bcIRLdUH0Sf2g6ZPVBfibm4+8y1glFwQ==", + "dev": true, + "dependencies": { + "@types/highlight.js": "^9.7.0", + "highlight.js": "^9.7.0" + } + }, "node_modules/@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -7264,6 +7281,17 @@ "he": "bin/he" } }, + "node_modules/highlight.js": { + "version": "9.18.5", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.18.5.tgz", + "integrity": "sha512-a5bFyofd/BHCX52/8i8uJkjr9DYwXIPnM/plwI6W7ezItLGqzt7X2G2nXuYSfsIJdkwwj/g9DG1LkcGJI/dDoA==", + "deprecated": "Support has ended for 9.x series. Upgrade to @latest", + "dev": true, + "hasInstallScript": true, + "engines": { + "node": "*" + } + }, "node_modules/history": { "version": "4.10.1", "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", @@ -17641,6 +17669,12 @@ "@types/node": "*" } }, + "@types/highlight.js": { + "version": "9.12.4", + "resolved": "https://registry.npmjs.org/@types/highlight.js/-/highlight.js-9.12.4.tgz", + "integrity": "sha512-t2szdkwmg2JJyuCM20e8kR2X59WCE5Zkl4bzm1u1Oukjm79zpbiAv+QjnwLnuuV0WHEcX2NgUItu0pAMKuOPww==", + "dev": true + }, "@types/history": { "version": "4.7.8", "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.8.tgz", @@ -17780,6 +17814,16 @@ "@types/react": "*" } }, + "@types/remarkable": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/remarkable/-/remarkable-2.0.1.tgz", + "integrity": "sha512-4KTpW8aVjVkTgaWlEOAuorBAdlUrvlzzPlVrt9vsAAM6nB/nUln7j0bcIRLdUH0Sf2g6ZPVBfibm4+8y1glFwQ==", + "dev": true, + "requires": { + "@types/highlight.js": "^9.7.0", + "highlight.js": "^9.7.0" + } + }, "@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -21832,6 +21876,12 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, + "highlight.js": { + "version": "9.18.5", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.18.5.tgz", + "integrity": "sha512-a5bFyofd/BHCX52/8i8uJkjr9DYwXIPnM/plwI6W7ezItLGqzt7X2G2nXuYSfsIJdkwwj/g9DG1LkcGJI/dDoA==", + "dev": true + }, "history": { "version": "4.10.1", "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", diff --git a/FrontEnd/package.json b/FrontEnd/package.json index f5013d8a..76d74e16 100644 --- a/FrontEnd/package.json +++ b/FrontEnd/package.json @@ -74,6 +74,7 @@ "@types/react-router": "^5.1.12",
"@types/react-router-bootstrap": "^0.24.5",
"@types/react-router-dom": "^5.1.7",
+ "@types/remarkable": "^2.0.1",
"@types/webpack-env": "^1.16.0",
"@types/xregexp": "^4.3.0",
"@typescript-eslint/eslint-plugin": "^4.16.1",
diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePostContentView.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePostContentView.tsx index 2f5d3989..d836d1db 100644 --- a/FrontEnd/src/app/views/timeline-common/TimelinePostContentView.tsx +++ b/FrontEnd/src/app/views/timeline-common/TimelinePostContentView.tsx @@ -1,5 +1,8 @@ import React from "react"; import clsx from "clsx"; +import { Remarkable } from "remarkable"; + +import { UiLogicError } from "@/common"; import { HttpNetworkError } from "@/http/common"; import { getHttpTimelineClient, HttpTimelinePostInfo } from "@/http/timeline"; @@ -81,9 +84,82 @@ const ImageView: React.FC<TimelinePostContentViewProps> = (props) => { ); }; -const MarkdownView: React.FC<TimelinePostContentViewProps> = (_props) => { - // TODO: Implement this. - return <div>Unsupported now!</div>; +const MarkdownView: React.FC<TimelinePostContentViewProps> = (props) => { + const { post, className, style } = props; + + const _remarkable = React.useRef<Remarkable>(); + + const getRemarkable = (): Remarkable => { + if (_remarkable.current) { + return _remarkable.current; + } else { + _remarkable.current = new Remarkable(); + return _remarkable.current; + } + }; + + const [markdown, setMarkdown] = React.useState<string | null>(null); + const [error, setError] = React.useState<"offline" | "error" | null>(null); + + const [reloadKey, setReloadKey] = React.useState<number>(0); + + React.useEffect(() => { + let subscribe = true; + + setMarkdown(null); + setError(null); + + void getHttpTimelineClient() + .getPostDataAsString(post.timelineName, post.id) + .then( + (data) => { + if (subscribe) setMarkdown(data); + }, + (error) => { + if (subscribe) { + if (error instanceof HttpNetworkError) { + setError("offline"); + } else { + setError("error"); + } + } + } + ); + + return () => { + subscribe = false; + }; + }, [post.timelineName, post.id, reloadKey]); + + const markdownHtml = React.useMemo<string | null>(() => { + if (markdown == null) return null; + return getRemarkable().render(markdown); + }, [markdown]); + + if (error != null) { + return ( + <LoadFailReload + className={className} + style={style} + onReload={() => setReloadKey(reloadKey + 1)} + /> + ); + } else if (markdown == null) { + return <Skeleton />; + } else { + if (markdownHtml == null) { + throw new UiLogicError("Markdown is not null but markdown html is."); + } + return ( + <div + className={className} + style={style} + dangerouslySetInnerHTML={{ + __html: markdownHtml, + }} + /> + ); + } }; export interface TimelinePostContentViewProps { |