(see getBlocksAsHTML / math block
// paste). We don't render KaTeX in this pipeline; instead we bridge between
// that div and Markdown's "$$…$$" display math so math survives the text/plain
// clipboard path in both directions.
// markdown "$$…$$" →
(the shape the block paste handler expects)
function mathNodeToHast(_state: any, node: { value: string }): Element {
return {
type: "element",
tagName: "div",
properties: { dataType: "math", dataTex: node.value },
children: [],
};
}
//
→ markdown math node; other divs unwrap to their children,
// which is hast-util-to-mdast's default behaviour for block containers.
function divToMathNode(state: any, node: any) {
let tex = node.properties?.dataTex;
if (typeof tex === "string" && tex.trim()) {
return { type: "math", value: tex.trim(), meta: null };
}
return state.all(node);
}
function remarkTightListify() {
return (tree: any) => {
visit(tree, "list", (node) => {
node.spread = false;
for (const child of node.children) {
if (child.type === "listItem") {
child.spread = false;
}
}
});
};
}
export function markdownToHtml(markdown: string): string {
markdown = markdown.replace(/\n(?=[^\n])/g, "\n\n");
return String(
unified()
.use(remarkParse) // Parse markdown content to a syntax tree
.use(remarkGfm)
// singleDollarTextMath is off so prose like "$5 and $10" isn't parsed as
// inline math; only display "$$…$$" becomes a math block.
.use(remarkMath, { singleDollarTextMath: false })
.use(remarkRehype, {
handlers: { math: mathNodeToHast, inlineMath: mathNodeToHast },
}) // Turn markdown syntax tree to HTML syntax tree, ignoring embedded HTML
.use(rehypeStringify) // Serialize HTML syntax tree
.processSync(markdown),
);
}
export function htmlToMarkdown(html: string): string {
return String(
unified()
.use(rehypeParse) // Parse HTML to a syntax tree
.use(rehypeRemark, { handlers: { div: divToMathNode } }) // Turn HTML syntax tree to markdown syntax tree
.use(remarkGfm)
.use(remarkMath) // serialize math nodes back to "$$…$$"
.use(remarkTightListify)
.use(remarkStringify, {
bullet: "-", // change default list marker from '*'
}) // Serialize HTML syntax tree
.processSync(html),
);
}