From d16d62d1f19135d9b924ce60ad3717b56464c063 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Sun, 23 Aug 2026 19:31:46 +0200 Subject: [PATCH] fix: duplicate checkbox markers when serializing note task lists (#27671) Task lists in Notes serialized to markdown as `- [ ] [ ]` with the item text pushed onto a separate line after a blank line, so previewing or downloading a note produced a broken checklist, and checking an item left the second `[ ]` behind as plain text. TipTap renders each task item as a checkbox inside a label plus a block-wrapped body. The GFM turndown plugin matches that checkbox and emits its own `[ ]`, which landed next to the marker the task item rule already writes, and the block wrapper left blank lines around the text that the old leading-whitespace strip could not remove. Register a rule that drops the checkbox so the task item rule is the only source of the marker, and trim the block wrapper while indenting continuation lines so nested lists and code fences stay inside the item. Fixes #26067 --- src/lib/components/common/RichTextInput.svelte | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/lib/components/common/RichTextInput.svelte b/src/lib/components/common/RichTextInput.svelte index e4c469acd5..53a042b6f5 100644 --- a/src/lib/components/common/RichTextInput.svelte +++ b/src/lib/components/common/RichTextInput.svelte @@ -94,6 +94,12 @@ } }); + // Registered after use(gfm) to override its checkbox rule; taskListItems owns the marker. + turndownService.addRule('taskItemCheckbox', { + filter: (node) => node.nodeName === 'INPUT' && node.getAttribute('type') === 'checkbox', + replacement: () => '' + }); + turndownService.addRule('taskListItems', { filter: (node) => node.nodeName === 'LI' && @@ -101,7 +107,8 @@ node.getAttribute('data-checked') === 'false'), replacement: function (content, node) { const checked = node.getAttribute('data-checked') === 'true'; - content = content.replace(/^\s+/, ''); + // Trim TipTap's block wrapper; 4-space continuation keeps sublists and fences nested. + content = content.trim().replace(/\n(?=.)/g, '\n '); return `- [${checked ? 'x' : ' '}] ${content}\n`; } });