Files
jiang13-bbs/frontend/components/markdown/renderMarkdown.tsx
freefire 8d5e00a112 feat(frontend): Shiki 白名单语言高亮并支持 aardio
显式加载常用语言;修复 pretty-code 去 shiki class 导致不上色,以及 aardio 循环别名。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-16 06:09:25 +08:00

74 lines
2.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { Components } from "react-markdown";
import type { ReactElement } from "react";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
import { toJsxRuntime } from "hast-util-to-jsx-runtime";
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkGfm from "remark-gfm";
import remarkRehype from "remark-rehype";
import rehypePrettyCode, { type Options as PrettyCodeOptions } from "rehype-pretty-code";
import type { Root } from "hast";
import type { Root as MdastRoot } from "mdast";
import { visit } from "unist-util-visit";
import { mergeMarkdownComponents } from "./mergeComponents";
import {
getPrettyCodeHighlighter,
resolveShikiLang,
SHIKI_THEMES,
} from "./shikiHighlighter";
/**
* 将代码围栏语言归一到白名单(未知 → plaintext),
* 避免 rehype-pretty-code 对未装载语言 loadLanguage 失败。
*/
function remarkNormalizeCodeLang() {
return (tree: MdastRoot) => {
visit(tree, "code", (node) => {
node.lang = resolveShikiLang(node.lang);
});
};
}
export const prettyCodeOptions: PrettyCodeOptions = {
theme: {
light: SHIKI_THEMES.light,
dark: SHIKI_THEMES.dark,
},
keepBackground: false,
defaultLang: "plaintext",
// HighlighterCore 与 pretty-code 期望的 Highlighter 在运行时兼容
getHighlighter: getPrettyCodeHighlighter as unknown as PrettyCodeOptions["getHighlighter"],
// 空行在 display:grid 下会塌缩成 0 高,补一个空格撑住行高
onVisitLine(element) {
if (element.children.length === 0) {
element.children = [{ type: "text", value: " " }];
}
},
};
/** 服务端异步管线:GFM + Shiki 双主题高亮 → React 树 */
export async function renderMarkdownToReact(
content: string,
overrides?: Components
): Promise<ReactElement> {
const processor = unified()
.use(remarkParse)
.use(remarkGfm)
.use(remarkNormalizeCodeLang)
.use(remarkRehype)
.use(rehypePrettyCode, { ...prettyCodeOptions });
const mdast = processor.parse(content || "");
const hast = (await processor.run(mdast)) as Root;
return toJsxRuntime(hast, {
Fragment,
jsx,
jsxs,
components: mergeMarkdownComponents(overrides) as Record<
string,
React.ElementType
>,
}) as ReactElement;
}