diff --git a/package.json b/package.json index 15ca19a..1f343cd 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "helixnotes", "private": true, "license": "AGPL-3.0-or-later", - "version": "1.0.7", + "version": "1.0.8", "type": "module", "scripts": { "dev": "vite dev", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index d30ca9e..6e3840a 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1778,7 +1778,7 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "helixnotes" -version = "1.0.7" +version = "1.0.8" dependencies = [ "chrono", "dirs", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 14c45b9..eb7236e 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "helixnotes" -version = "1.0.7" +version = "1.0.8" description = "Local-first markdown note-taking app" authors = ["HelixNotes"] license = "AGPL-3.0-or-later" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 0940897..bafbbd1 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "productName": "HelixNotes", - "version": "1.0.7", + "version": "1.0.8", "identifier": "com.helixnotes.app", "build": { "frontendDist": "../build", diff --git a/src/lib/components/Editor.svelte b/src/lib/components/Editor.svelte index 2cc8a4c..f093865 100644 --- a/src/lib/components/Editor.svelte +++ b/src/lib/components/Editor.svelte @@ -1071,14 +1071,23 @@ } function prosemirrorToMarkdown(doc: any): string { + const listTypes = new Set(['bulletList', 'orderedList', 'taskList']); const parts: string[] = []; let prevType = ''; + let preserveEmptyParas = false; doc.forEach((node: any) => { - // Skip empty paragraphs right after code blocks (TipTap cursor placeholder) - if (node.type.name === 'paragraph' && node.childCount === 0 && prevType === 'codeBlock') { + const isEmpty = node.type.name === 'paragraph' && node.childCount === 0; + // After a list or code block, preserve empty paragraphs as HTML comments + // so markdown-it doesn't merge adjacent lists or collapse spacing + if (listTypes.has(prevType) || prevType === 'codeBlock') { + preserveEmptyParas = true; + } + if (isEmpty && preserveEmptyParas) { + parts.push(''); prevType = node.type.name; return; } + preserveEmptyParas = false; parts.push(serializeNode(node)); prevType = node.type.name; }); @@ -1488,8 +1497,11 @@ html = html.replace(/]*)>\n?/g, ''); html = html.replace(/\n<\/code>/g, ''); - // Post-process: convert task list items to TipTap format - html = html.replace(/
  • ([\s\S]*?)<\/tiptask><\/li>/gi, (_, checked, content) => { + // Post-process: convert list-separator comments back to empty paragraphs for TipTap + html = html.replace(//g, '

    '); + + // Post-process: convert task list items to TipTap format (handle both tight and loose lists — loose lists wrap content in

    tags) + html = html.replace(/

  • \s*(?:

    )?\s*([\s\S]*?)<\/tiptask>\s*(?:<\/p>)?\s*<\/li>/gi, (_, checked, content) => { return `

  • ${content}
  • `; }); html = html.replace(/