From 486663e6b4b2aa4addc4c84d24e1ce5252941858 Mon Sep 17 00:00:00 2001
From: crupest <crupest@outlook.com>
Date: Sun, 7 Mar 2021 16:15:47 +0800
Subject: feat: Add markdown content view.

---
 .../timeline-common/TimelinePostContentView.tsx    | 82 +++++++++++++++++++++-
 1 file changed, 79 insertions(+), 3 deletions(-)

(limited to 'FrontEnd/src/app/views/timeline-common/TimelinePostContentView.tsx')

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 {
-- 
cgit v1.2.3

