UNPKG

@lobehub/ui

Version:

Lobe UI is an open-source UI component library for building AIGC web apps

82 lines (81 loc) 3.47 kB
"use client"; import { visit } from "../../node_modules/unist-util-visit/lib/index.mjs"; import { useMemo } from "react"; import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime"; import { defaultUrlTransform } from "react-markdown"; import { toJsxRuntime } from "hast-util-to-jsx-runtime"; import { urlAttributes } from "html-url-attributes"; import remarkParse from "remark-parse"; import remarkRehype from "remark-rehype"; import { unified } from "unified"; import { VFile } from "vfile"; //#region src/Markdown/SyntaxMarkdown/CachedMarkdown.tsx const EMPTY_PLUGINS = []; const DEFAULT_REMARK_REHYPE_OPTIONS = { allowDangerousHtml: true }; /** * Render-equivalent of react-markdown's synchronous `<Markdown>` that keeps * the unified processor across renders. react-markdown rebuilds the whole * plugin chain on every render; the streaming tail block re-renders on every * reveal commit (~20/s) with identical plugin identities, so caching the * processor removes the per-commit chain construction. `post`/`transform` * mirror react-markdown@10 — keep them in sync when upgrading it. */ const CachedMarkdown = (options) => { const { children, rehypePlugins, remarkPlugins, remarkRehypeOptions } = options; const processor = useMemo(() => { return unified().use(remarkParse).use(remarkPlugins || EMPTY_PLUGINS).use(remarkRehype, remarkRehypeOptions ? { ...remarkRehypeOptions, ...DEFAULT_REMARK_REHYPE_OPTIONS } : DEFAULT_REMARK_REHYPE_OPTIONS).use(rehypePlugins || EMPTY_PLUGINS); }, [ rehypePlugins, remarkPlugins, remarkRehypeOptions ]); const file = new VFile(); file.value = typeof children === "string" ? children : ""; return post(processor.runSync(processor.parse(file), file), options); }; function post(tree, options) { const { allowedElements, allowElement, components, disallowedElements, skipHtml, unwrapDisallowed } = options; const urlTransform = options.urlTransform || defaultUrlTransform; if (allowedElements && disallowedElements) throw new Error("Unexpected combined `allowedElements` and `disallowedElements`, expected one or the other"); const transform = (node, index, parent) => { if (node.type === "raw" && parent && typeof index === "number") { if (skipHtml) parent.children.splice(index, 1); else parent.children[index] = { type: "text", value: node.value }; return index; } if (node.type === "element") { let key; for (key in urlAttributes) if (Object.hasOwn(urlAttributes, key) && Object.hasOwn(node.properties, key)) { const value = node.properties[key]; const test = urlAttributes[key]; if (test === null || test.includes(node.tagName)) node.properties[key] = urlTransform(String(value || ""), key, node); } let remove = allowedElements ? !allowedElements.includes(node.tagName) : disallowedElements ? disallowedElements.includes(node.tagName) : false; if (!remove && allowElement && typeof index === "number") remove = !allowElement(node, index, parent); if (remove && parent && typeof index === "number") { if (unwrapDisallowed && node.children) parent.children.splice(index, 1, ...node.children); else parent.children.splice(index, 1); return index; } } }; visit(tree, transform); return toJsxRuntime(tree, { Fragment: Fragment$1, components, ignoreInvalidStyle: true, jsx, jsxs, passKeys: true, passNode: true }); } //#endregion export { CachedMarkdown }; //# sourceMappingURL=CachedMarkdown.mjs.map