import rehypeRaw from 'rehype-raw'; import remarkGfm from 'remark-gfm'; import type { PluggableList, Plugin } from 'unified'; import rehypeSanitize, { defaultSchema, type Options as RehypeSanitizeOptions } from 'rehype-sanitize'; import { SKIP, visit } from 'unist-util-visit'; import type { UnistNode, UnistParent } from 'node_modules/unist-util-visit/lib'; export const allowedHTMLElements = [ 'a', 'b', 'blockquote', 'br', 'code', 'dd', 'del', 'details', 'div', 'dl', 'dt', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'ins', 'kbd', 'li', 'ol', 'p', 'pre', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'source', 'span', 'strike', 'strong', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'ul', 'var', ]; const rehypeSanitizeOptions: RehypeSanitizeOptions = { ...defaultSchema, tagNames: allowedHTMLElements, attributes: { ...defaultSchema.attributes, div: [...(defaultSchema.attributes?.div ?? []), 'data*', ['className', '__boltArtifact__']], }, strip: [], }; export function remarkPlugins(limitedMarkdown: boolean) { const plugins: PluggableList = [remarkGfm]; if (limitedMarkdown) { plugins.unshift(limitedMarkdownPlugin); } return plugins; } export function rehypePlugins(html: boolean) { const plugins: PluggableList = []; if (html) { plugins.push(rehypeRaw, [rehypeSanitize, rehypeSanitizeOptions]); } return plugins; } const limitedMarkdownPlugin: Plugin = () => { return (tree, file) => { const contents = file.toString(); visit(tree, (node: UnistNode, index, parent: UnistParent) => { if ( index == null || ['paragraph', 'text', 'inlineCode', 'code', 'strong', 'emphasis'].includes(node.type) || !node.position ) { return true; } let value = contents.slice(node.position.start.offset, node.position.end.offset); if (node.type === 'heading') { value = `\n${value}`; } parent.children[index] = { type: 'text', value, } as any; return [SKIP, index] as const; }); }; };