From fbfaabd642ce343ffd29643a4f3896b8732130e8 Mon Sep 17 00:00:00 2001 From: Alessandro Carosia Date: Tue, 25 Nov 2025 00:59:09 +0100 Subject: [PATCH] fix(ofm): support nested markdown in highlights * Replace regex with AST manipulation to handle split text nodes * Implement marking and grouping logic to wrap mixed content. * Unify plugins --- quartz/plugins/transformers/ofm.ts | 62 +++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 13 deletions(-) diff --git a/quartz/plugins/transformers/ofm.ts b/quartz/plugins/transformers/ofm.ts index 7a523aa59..a7da37d81 100644 --- a/quartz/plugins/transformers/ofm.ts +++ b/quartz/plugins/transformers/ofm.ts @@ -128,7 +128,7 @@ export const tableRegex = new RegExp(/^\|([^\n])+\|\n(\|)( ?:?-{3,}:? ?\|)+\n(\| // matches any wikilink, only used for escaping wikilinks inside tables export const tableWikilinkRegex = new RegExp(/(!?\[\[[^\]]*?\]\]|\[\^[^\]]*?\])/g) -const highlightRegex = new RegExp(/==([^=]+)==/g) +const highlightRegex = new RegExp(/==/g) const commentRegex = new RegExp(/%%[\s\S]*?%%/g) // from https://github.com/escwxyz/remark-obsidian-callout/blob/main/src/index.ts const calloutRegex = new RegExp(/^\[\!([\w-]+)\|?(.+?)?\]([+-]?)/) @@ -306,18 +306,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin> ]) } - if (opts.highlight) { - replacements.push([ - highlightRegex, - (_value: string, ...capture: string[]) => { - const [inner] = capture - return { - type: "html", - value: `${inner}`, - } - }, - ]) - } + // Highlight logic moved to separate plugin if (opts.parseArrows) { replacements.push([ @@ -393,6 +382,53 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin> } }) + if (opts.highlight) { + plugins.push(() => { + return (tree: Root) => { + mdastFindReplace(tree, [ + [highlightRegex, (_value: string) => ({ type: "highlightMarker" }) as any], + ]) + + visit(tree, (node) => { + if (!("children" in node)) return + const children = (node as any).children as any[] + + const markers: number[] = [] + for (let i = 0; i < children.length; i++) { + if (children[i].type === "highlightMarker") { + markers.push(i) + } + } + + if (markers.length < 2) return + + const pairs: [number, number][] = [] + for (let i = 0; i < markers.length - 1; i += 2) { + pairs.push([markers[i], markers[i + 1]]) + } + + for (let i = pairs.length - 1; i >= 0; i--) { + const [start, end] = pairs[i] + const content = children.slice(start + 1, end) + const htmlContent = content + .map((n) => { + const hast = toHast(n, { allowDangerousHtml: true }) + return toHtml(hast as any, { allowDangerousHtml: true }) + }) + .join("") + + const newNode = { + type: "html", + value: `${htmlContent}`, + } + + children.splice(start, end - start + 1, newNode) + } + }) + } + }) + } + if (opts.enableVideoEmbed) { plugins.push(() => { return (tree: Root, _file) => {