diff --git a/tone-page-web/app/(with-header-footer)/blog/[id]/page.tsx b/tone-page-web/app/(with-header-footer)/blog/[id]/page.tsx new file mode 100644 index 0000000..60cc746 --- /dev/null +++ b/tone-page-web/app/(with-header-footer)/blog/[id]/page.tsx @@ -0,0 +1,69 @@ +'use client'; + +import { BlogApi } from "@/lib/api"; +import { base62 } from "@/lib/utils"; +import { useParams } from "next/navigation"; +import { useEffect, useState } from "react"; +import useSWR from "swr"; +import { unified } from 'unified'; +import remarkParse from 'remark-parse'; +import remarkRehype from 'remark-rehype'; +import rehypeHighlight from 'rehype-highlight'; +import rehypeStringify from 'rehype-stringify'; + +export default function Blog() { + const params = useParams(); + const hex = Array.from(base62.decode(params.id as string)).map(b => b.toString(16).padStart(2, '0')).join(''); + const id = [ + hex.slice(0, 8), + hex.slice(8, 12), + hex.slice(12, 16), + hex.slice(16, 20), + hex.slice(20, 32) + ].join('-'); + + const [markdownHTML, setMarkdownHTML] = useState(null); + + const { data, error, isLoading } = useSWR( + `/api/blog/${id}`, + () => BlogApi.get(id), + ) + + useEffect(() => { + if (data?.content) { + (async () => { + try { + const processed = unified() + .use(remarkParse) // 解析 Markdown + .use(remarkRehype) // 转换为 HTML AST + .use(rehypeHighlight) // 高亮代码块 + .use(rehypeStringify); // 输出 HTML 字符串 + + const result = await processed.process(data.content); + setMarkdownHTML(result.toString()); + } catch (err) { + console.error("Failed to parse markdown", err); + setMarkdownHTML("

无法加载内容

"); + } + })(); + } + }, [data]); + + return ( +
+ {data && ( +
+

{data.title}

+ {/* 渲染 Markdown HTML */} + {markdownHTML && ( +
+ )} +

发布于:{new Date(data.createdAt).toLocaleString()}

+
+ )} +
+ ) +} \ No newline at end of file