From fbfaabd642ce343ffd29643a4f3896b8732130e8 Mon Sep 17 00:00:00 2001 From: Alessandro Carosia Date: Tue, 25 Nov 2025 00:59:09 +0100 Subject: [PATCH 1/3] 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 7a523aa5936e1..a7da37d81603e 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) => { From ef0c650a709bb7ae4fb5339bc1a76cef36b6273b Mon Sep 17 00:00:00 2001 From: "Alessandro C." <152720918+kiycoh@users.noreply.github.com> Date: Fri, 26 Dec 2025 21:03:08 +0100 Subject: [PATCH 2/3] Update quartz/plugins/transformers/ofm.ts comment fix Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- quartz/plugins/transformers/ofm.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quartz/plugins/transformers/ofm.ts b/quartz/plugins/transformers/ofm.ts index a7da37d81603e..bfc9aae5e3f33 100644 --- a/quartz/plugins/transformers/ofm.ts +++ b/quartz/plugins/transformers/ofm.ts @@ -306,7 +306,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin> ]) } - // Highlight logic moved to separate plugin + // Highlight logic is handled separately below (lines 385-430) to support nested markdown if (opts.parseArrows) { replacements.push([ From 23d2cfbfd3e31b405ec0c8783f618029c708618f Mon Sep 17 00:00:00 2001 From: kiycoh Date: Fri, 26 Dec 2025 21:54:24 +0100 Subject: [PATCH 3/3] feat: new marker interface for highlight parsing logic and validation --- quartz/plugins/transformers/ofm.ts | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/quartz/plugins/transformers/ofm.ts b/quartz/plugins/transformers/ofm.ts index bfc9aae5e3f33..d2a0cfed2fd4d 100644 --- a/quartz/plugins/transformers/ofm.ts +++ b/quartz/plugins/transformers/ofm.ts @@ -7,6 +7,7 @@ import { DefinitionContent, Paragraph, Code, + Parent, } from "mdast" import { Element, Literal, Root as HtmlRoot } from "hast" import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace" @@ -100,6 +101,19 @@ const arrowMapping: Record = { "<=": "⇐", "<==": "⇐", } +// Marker node used for highlighting +interface HighlightMarker { + type: "highlightMarker" +} + +function isHighlightMarker(node: unknown): node is HighlightMarker { + return ( + typeof node === "object" && + node !== null && + "type" in node && + (node as { type: unknown }).type === "highlightMarker" + ) +} function canonicalizeCallout(calloutName: string): keyof typeof calloutMapping { const normalizedCallout = calloutName.toLowerCase() as keyof typeof calloutMapping @@ -391,11 +405,13 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin> visit(tree, (node) => { if (!("children" in node)) return - const children = (node as any).children as any[] + const parent = node as Parent + const children = parent.children + if (children.length === 0) return const markers: number[] = [] for (let i = 0; i < children.length; i++) { - if (children[i].type === "highlightMarker") { + if (isHighlightMarker(children[i])) { markers.push(i) } } @@ -413,11 +429,11 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin> const htmlContent = content .map((n) => { const hast = toHast(n, { allowDangerousHtml: true }) - return toHtml(hast as any, { allowDangerousHtml: true }) + return hast ? toHtml(hast, { allowDangerousHtml: true }) : "" }) .join("") - const newNode = { + const newNode: Html = { type: "html", value: `${htmlContent}`, }