@lobehub/ui
Version:
Lobe UI is an open-source UI component library for building AIGC web apps
82 lines (81 loc) • 3.47 kB
JavaScript
"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