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 | 729f2e096d2476634cc8c74857a137eaa8fb5c88 (patch) | |
tree | 2c29f634bb775262055855487b90fb8f32da6f43 | |
parent | aa167fa78491c9a8116d4a04e6bdec35be4742b6 (diff) | |
download | timeline-729f2e096d2476634cc8c74857a137eaa8fb5c88.tar.gz timeline-729f2e096d2476634cc8c74857a137eaa8fb5c88.tar.bz2 timeline-729f2e096d2476634cc8c74857a137eaa8fb5c88.zip |
feat: Add markdown content view.
-rw-r--r-- | FrontEnd/package.json | 1 | ||||
-rw-r--r-- | FrontEnd/src/app/views/timeline-common/TimelinePostContentView.tsx | 82 |
2 files changed, 80 insertions, 3 deletions
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 { |