import { remark } from "remark"; import remarkGfm from "remark-gfm"; import remarkRehype from "remark-rehype"; import { toJsxRuntime } from "hast-util-to-jsx-runtime"; import { Children, type ComponentProps, type ReactElement, type ReactNode, Suspense, use, useDeferredValue, } from "react"; import { Fragment, jsx, jsxs } from "react/jsx-runtime"; import { DynamicCodeBlock } from "fumadocs-ui/components/dynamic-codeblock"; import defaultMdxComponents from "fumadocs-ui/mdx"; import { visit } from "unist-util-visit"; import type { ElementContent, Root, RootContent } from "hast"; export interface Processor { process: (content: string) => Promise; } export function rehypeWrapWords() { return (tree: Root) => { visit(tree, ["text", "element"], (node, index, parent) => { if (node.type === "element" && node.tagName === "pre") return "skip"; if (node.type !== "text" || !parent || index === undefined) return; const words = node.value.split(/(?=\s)/); // Create new span nodes for each word and whitespace const newNodes: ElementContent[] = words.flatMap((word) => { if (word.length === 0) return []; return { type: "element", tagName: "span", properties: { class: "animate-fd-fade-in", }, children: [{ type: "text", value: word }], }; }); Object.assign(node, { type: "element", tagName: "span", properties: {}, children: newNodes, } satisfies RootContent); return "skip"; }); }; } function createProcessor(): Processor { const processor = remark() .use(remarkGfm) .use(remarkRehype) .use(rehypeWrapWords); return { async process(content) { const nodes = processor.parse({ value: content }); const hast = await processor.run(nodes); return toJsxRuntime(hast, { development: false, jsx, jsxs, Fragment, components: { ...defaultMdxComponents, pre: Pre, img: undefined, // use JSX }, }); }, }; } function Pre(props: ComponentProps<"pre">) { const code = Children.only(props.children) as ReactElement; const codeProps = code.props as ComponentProps<"code">; const content = codeProps.children; if (typeof content !== "string") return null; let lang = codeProps.className ?.split(" ") .find((v) => v.startsWith("language-")) ?.slice("language-".length) ?? "text"; if (lang === "mdx") lang = "md"; return ; } const processor = createProcessor(); export function Markdown({ text }: { text: string }) { const deferredText = useDeferredValue(text); return ( {text}

}>
); } const cache = new Map>(); function Renderer({ text }: { text: string }) { const result = cache.get(text) ?? processor.process(text); cache.set(text, result); return use(result); }