| 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; | |
| }); | |
| }; | |
| }; | |