aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2021-03-07 16:15:47 +0800
committercrupest <crupest@outlook.com>2021-03-07 16:15:47 +0800
commit486663e6b4b2aa4addc4c84d24e1ce5252941858 (patch)
treeab4e6995049a660047614f642ac7d1ba5fb62c99 /FrontEnd
parent7d74e849d96fa29019ef001b528df6612d93f085 (diff)
downloadtimeline-486663e6b4b2aa4addc4c84d24e1ce5252941858.tar.gz
timeline-486663e6b4b2aa4addc4c84d24e1ce5252941858.tar.bz2
timeline-486663e6b4b2aa4addc4c84d24e1ce5252941858.zip
feat: Add markdown content view.
Diffstat (limited to 'FrontEnd')
-rw-r--r--FrontEnd/package-lock.json50
-rw-r--r--FrontEnd/package.json1
-rw-r--r--FrontEnd/src/app/views/timeline-common/TimelinePostContentView.tsx82
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 {