summaryrefslogtreecommitdiffstatshomepage
path: root/webui/src/components/Content/index.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'webui/src/components/Content/index.tsx')
-rw-r--r--webui/src/components/Content/index.tsx98
1 files changed, 75 insertions, 23 deletions
diff --git a/webui/src/components/Content/index.tsx b/webui/src/components/Content/index.tsx
index 9bf9ff7a6..3cfa9eb89 100644
--- a/webui/src/components/Content/index.tsx
+++ b/webui/src/components/Content/index.tsx
@@ -1,11 +1,24 @@
-import { createElement, Fragment, useEffect, useState } from 'react';
+import type { Root as HastRoot } from 'hast';
+import type { Root as MdRoot } from 'mdast';
+import { useEffect, useState } from 'react';
import * as React from 'react';
+import * as production from 'react/jsx-runtime';
+import rehypeHighlight, {
+ Options as RehypeHighlightOpts,
+} from 'rehype-highlight';
import rehypeReact from 'rehype-react';
-import gemoji from 'remark-gemoji';
-import html from 'remark-html';
-import parse from 'remark-parse';
+import rehypeSanitize from 'rehype-sanitize';
+import remarkBreaks from 'remark-breaks';
+import remarkGemoji from 'remark-gemoji';
+import remarkGfm from 'remark-gfm';
+import remarkParse from 'remark-parse';
import remarkRehype from 'remark-rehype';
+import type { Options as RemarkRehypeOptions } from 'remark-rehype';
import { unified } from 'unified';
+import type { Plugin, Processor } from 'unified';
+import { Node as UnistNode } from 'unified/lib';
+
+import { ThemeContext } from '../Themer';
import AnchorTag from './AnchorTag';
import BlockQuoteTag from './BlockQuoteTag';
@@ -13,32 +26,71 @@ import ImageTag from './ImageTag';
import PreTag from './PreTag';
type Props = { markdown: string };
+
+// @lygaret 2025/05/16
+// type inference for some of this doesn't work, but the pipeline is fine
+// this might get better when we upgrade typescript
+
+type RemarkPlugin = Plugin<[], MdRoot, HastRoot>;
+type RemarkRehypePlugin = Plugin<RemarkRehypeOptions[], MdRoot, HastRoot>;
+type RehypePlugin<Options extends unknown[] = []> = Plugin<
+ Options,
+ HastRoot,
+ HastRoot
+>;
+
+const markdownPipeline: Processor<
+ UnistNode,
+ undefined,
+ undefined,
+ HastRoot,
+ React.JSX.Element
+> = unified()
+ .use(remarkParse)
+ .use(remarkGemoji as unknown as RemarkPlugin)
+ .use(remarkBreaks as unknown as RemarkPlugin)
+ .use(remarkGfm)
+ .use(remarkRehype as unknown as RemarkRehypePlugin, {
+ allowDangerousHtml: true,
+ })
+ .use(rehypeSanitize as unknown as RehypePlugin)
+ .use(rehypeHighlight as unknown as RehypePlugin<RehypeHighlightOpts[]>, {
+ detect: true,
+ subset: ['text'],
+ })
+ .use(rehypeReact, {
+ ...production,
+ components: {
+ a: AnchorTag,
+ blockquote: BlockQuoteTag,
+ img: ImageTag,
+ pre: PreTag,
+ },
+ });
+
const Content: React.FC<Props> = ({ markdown }: Props) => {
- const [Content, setContent] = useState(<></>);
+ const theme = React.useContext(ThemeContext);
+ const [content, setContent] = useState(<></>);
useEffect(() => {
- unified()
- .use(parse)
- .use(gemoji)
- .use(html)
- .use(remarkRehype)
- .use(rehypeReact, {
- createElement,
- Fragment,
- components: {
- img: ImageTag,
- pre: PreTag,
- a: AnchorTag,
- blockquote: BlockQuoteTag,
- },
- })
+ markdownPipeline
.process(markdown)
- .then((file) => {
- setContent(file.result);
+ .then((file) => setContent(file.result))
+ .catch((err: any) => {
+ setContent(
+ <>
+ <span className="error">{err}</span>
+ <pre>{markdown}</pre>
+ </>
+ );
});
}, [markdown]);
- return Content;
+ return (
+ <div className={'highlight-theme'} data-theme={theme.mode}>
+ {content}
+ </div>
+ );
};
export default Content;