import { remark } from "remark"; import gfm from "remark-gfm"; import { visit } from "unist-util-visit"; interface MarkdownNode { type: string; value?: string; children?: MarkdownNode[]; } type MarkdownParent = MarkdownNode & { children: MarkdownNode[] }; type MarkdownRoot = MarkdownNode & { type: "root"; children: MarkdownNode[] }; type HtmlNode = MarkdownNode & { type: "html"; value: string }; const markdownProcessor = remark().use(gfm); export default function remarkHtml() { return async function transformer(tree: MarkdownRoot): Promise { const htmlNodes: Array<{ parent: MarkdownParent; index: number; value: string; }> = []; const replacementsByParent = new Map< MarkdownParent, Array<{ index: number; nodes: MarkdownNode[] }> >(); visit(tree, "html", (node, index, parent) => { if ( typeof index !== "number" || !parent || !Array.isArray((parent as MarkdownParent).children) || typeof (node as HtmlNode).value !== "string" ) { return; } htmlNodes.push({ parent: parent as MarkdownParent, index, value: (node as HtmlNode).value, }); }); for (const htmlNode of htmlNodes) { const normalizedValue = htmlNode.value .replace(//g, "") .replace(/<[^>]+>/g, ""); const file = await markdownProcessor.process(normalizedValue); const entries = replacementsByParent.get(htmlNode.parent) ?? []; entries.push({ index: htmlNode.index, nodes: (markdownProcessor.parse(String(file)) as MarkdownRoot).children, }); replacementsByParent.set(htmlNode.parent, entries); } // Sort the final output in the order it was read, which may not match the queue. for (const [parent, entries] of replacementsByParent) { entries.sort((left, right) => right.index - left.index); for (const entry of entries) { parent.children.splice(entry.index, 1, ...entry.nodes); } } }; }