From d474717133745417192174821d124fab2e4ed47b Mon Sep 17 00:00:00 2001 From: Wes Date: Mon, 27 Jul 2026 12:09:43 -0600 Subject: [PATCH] fix(desktop): keep collapsed table separators out of spoilers Malformed one-line GFM tables use double pipes where newlines were lost. Preserve delimiter-row-shaped spans as literal text instead of turning them into animated spoiler canvases. Co-authored-by: Carl Signed-off-by: Wes --- .../src/shared/lib/remarkSpoilers.test.mjs | 18 +++++++++++++ desktop/src/shared/lib/remarkSpoilers.ts | 25 ++++++++++++++++--- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/desktop/src/shared/lib/remarkSpoilers.test.mjs b/desktop/src/shared/lib/remarkSpoilers.test.mjs index 5ac848a1bb..4fb1248357 100644 --- a/desktop/src/shared/lib/remarkSpoilers.test.mjs +++ b/desktop/src/shared/lib/remarkSpoilers.test.mjs @@ -137,6 +137,24 @@ test("remarkSpoilers: groups block nodes between delimiter paragraphs", () => { assert.equal(tree.children[1].children[0].children[0].type, "image"); }); +test("remarkSpoilers: leaves collapsed GFM table separators as text", () => { + const value = + "| Time | Purpose ||---:|---|| 0:00 | Frame strategy || 3:30 | Orient the board |"; + const tree = runPlugin({ + type: "root", + children: [{ type: "paragraph", children: [{ type: "text", value }] }], + }); + + assert.equal( + tree.children[0].children.some((child) => child.type === "spoiler"), + false, + ); + assert.equal( + tree.children[0].children.map((child) => child.value ?? "").join(""), + value, + ); +}); + test("remarkSpoilers: leaves unmatched delimiters as text", () => { const tree = runPlugin({ type: "root", diff --git a/desktop/src/shared/lib/remarkSpoilers.ts b/desktop/src/shared/lib/remarkSpoilers.ts index 501d7c2d16..5b81d0f8cc 100644 --- a/desktop/src/shared/lib/remarkSpoilers.ts +++ b/desktop/src/shared/lib/remarkSpoilers.ts @@ -37,17 +37,29 @@ function transformNode(node: Node) { transformNode(child); } - node.children = groupBlockSpoilers(groupSpoilers(node.children)); + node.children = groupBlockSpoilers( + groupSpoilers(node.children, node.type === "paragraph"), + ); } -function groupSpoilers(children: Node[]): Node[] { +function groupSpoilers( + children: Node[], + rejectTableDelimiterRow: boolean, +): Node[] { const output: Node[] = []; let spoilerBuffer: Node[] | null = null; for (const part of splitDelimiterParts(children)) { if (part.type === "delimiter") { if (spoilerBuffer) { - output.push(buildSpoilerNode(spoilerBuffer)); + if (rejectTableDelimiterRow && isTableDelimiterRow(spoilerBuffer)) { + output.push({ type: "text", value: "||" }, ...spoilerBuffer, { + type: "text", + value: "||", + }); + } else { + output.push(buildSpoilerNode(spoilerBuffer)); + } spoilerBuffer = null; } else { spoilerBuffer = []; @@ -100,6 +112,13 @@ function splitDelimiterParts(children: Node[]): Part[] { return parts; } +function isTableDelimiterRow(children: Node[]): boolean { + if (children.some((child) => child.type !== "text")) return false; + + const value = children.map((child) => String(child.value ?? "")).join(""); + return /^\s*:?-{3,}:?(?:\s*\|\s*:?-{3,}:?)+\s*$/.test(value); +} + function buildSpoilerNode( children: Node[], options: { block?: boolean } = {},