From a1913558b99a0fd2dc34ec3af6072ae3e78acb30 Mon Sep 17 00:00:00 2001 From: Yuri Karamian Date: Fri, 19 Jun 2026 11:18:06 +0200 Subject: [PATCH 01/13] Fix pasted images disappearing when switching to source mode (#87) --- src/lib/components/Editor.svelte | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/lib/components/Editor.svelte b/src/lib/components/Editor.svelte index 627d6e9..70dd49a 100644 --- a/src/lib/components/Editor.svelte +++ b/src/lib/components/Editor.svelte @@ -4617,10 +4617,21 @@ if (!loadedPath) return; if (isSource && !lastSourceMode) { - // Switching TO source: extract markdown from editor - sourceContent = editor ? editorToMarkdown() : ($activeNote?.content ?? ''); - resetSourceHistory(sourceContent); - lastSourceMode = true; + // Switching TO source: fix blob images first, then extract markdown + if (hasPendingBlobs) { + hasPendingBlobs = false; + fixingBlobsPromise = fixBlobImages(); + } + const doSwap = () => { + sourceContent = editor ? editorToMarkdown() : ($activeNote?.content ?? ''); + resetSourceHistory(sourceContent); + lastSourceMode = true; + }; + if (fixingBlobsPromise) { + fixingBlobsPromise.then(doSwap); + } else { + doSwap(); + } } else if (!isSource && lastSourceMode) { lastSourceMode = false; if (isMobile) { From 5bf6d8063e16d11d5a8e5884cd10cc3bfb49118c Mon Sep 17 00:00:00 2001 From: Yuri Karamian Date: Fri, 19 Jun 2026 11:25:45 +0200 Subject: [PATCH 02/13] Fix images not rendering when switching back from source mode --- src/lib/components/Editor.svelte | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/lib/components/Editor.svelte b/src/lib/components/Editor.svelte index 70dd49a..700de2f 100644 --- a/src/lib/components/Editor.svelte +++ b/src/lib/components/Editor.svelte @@ -4634,22 +4634,22 @@ } } else if (!isSource && lastSourceMode) { lastSourceMode = false; - if (isMobile) { - // Mobile: editor stays in DOM, just update its content - const content = sourceContent || ($activeNote?.content ?? ''); - if (editor) { - ignoreNextUpdate = true; - editor.commands.setContent(markdownToHtml(content)); - } - } else { - // Desktop: destroy old editor (its DOM element is gone), - // wait for DOM to swap textarea→div, then create editor on new element. - destroyEditor(); - const content = sourceContent || ($activeNote?.content ?? ''); - tick().then(() => { - if (editorElement && !editor) { - createEditor(content); - } + // Both desktop and mobile: keep editor alive, just update content. + // Destroying/recreating the editor on desktop caused images not to + // render on the first switch back from source mode. + const content = sourceContent || ($activeNote?.content ?? ''); + if (editor) { + ignoreNextUpdate = true; + editor.commands.setContent(markdownToHtml(content)); + // Force a re-render of images that may not load after setContent + requestAnimationFrame(() => { + if (!editor) return; + editor.view.dom.querySelectorAll('img').forEach((img) => { + const src = img.getAttribute('src'); + if (src) { + img.setAttribute('src', src); + } + }); }); } } From 83390f5ed2463557e82e14d88e5c61d324b9803c Mon Sep 17 00:00:00 2001 From: Yuri Karamian Date: Fri, 19 Jun 2026 11:27:38 +0200 Subject: [PATCH 03/13] Fix images not rendering when switching back from source mode --- src/lib/components/Editor.svelte | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/lib/components/Editor.svelte b/src/lib/components/Editor.svelte index 700de2f..fc4c908 100644 --- a/src/lib/components/Editor.svelte +++ b/src/lib/components/Editor.svelte @@ -4634,24 +4634,23 @@ } } else if (!isSource && lastSourceMode) { lastSourceMode = false; - // Both desktop and mobile: keep editor alive, just update content. - // Destroying/recreating the editor on desktop caused images not to - // render on the first switch back from source mode. const content = sourceContent || ($activeNote?.content ?? ''); - if (editor) { - ignoreNextUpdate = true; - editor.commands.setContent(markdownToHtml(content)); - // Force a re-render of images that may not load after setContent - requestAnimationFrame(() => { - if (!editor) return; - editor.view.dom.querySelectorAll('img').forEach((img) => { - const src = img.getAttribute('src'); - if (src) { - img.setAttribute('src', src); - } + // Wait for Svelte to toggle display back to the editor element, + // then update content. The editor stays alive (no destroy/recreate). + tick().then(() => { + if (editor) { + ignoreNextUpdate = true; + editor.commands.setContent(markdownToHtml(content)); + // Force image re-render for WebViews that don't trigger loads on setContent + requestAnimationFrame(() => { + if (!editor) return; + editor.view.dom.querySelectorAll('img').forEach((img) => { + const src = img.getAttribute('src'); + if (src) img.setAttribute('src', src); + }); }); - }); - } + } + }); } }); From c1a9871703f90cc7719b6bdfa704bfbe2a8951ff Mon Sep 17 00:00:00 2001 From: Yuri Karamian Date: Fri, 19 Jun 2026 11:30:07 +0200 Subject: [PATCH 04/13] Fix pasted images disappearing when switching to source mode (#87) --- src/lib/components/Editor.svelte | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/lib/components/Editor.svelte b/src/lib/components/Editor.svelte index fc4c908..5fbebb5 100644 --- a/src/lib/components/Editor.svelte +++ b/src/lib/components/Editor.svelte @@ -4634,23 +4634,23 @@ } } else if (!isSource && lastSourceMode) { lastSourceMode = false; - const content = sourceContent || ($activeNote?.content ?? ''); - // Wait for Svelte to toggle display back to the editor element, - // then update content. The editor stays alive (no destroy/recreate). - tick().then(() => { + if (isMobile) { + // Mobile: editor stays in DOM, just update its content + const content = sourceContent || ($activeNote?.content ?? ''); if (editor) { ignoreNextUpdate = true; editor.commands.setContent(markdownToHtml(content)); - // Force image re-render for WebViews that don't trigger loads on setContent - requestAnimationFrame(() => { - if (!editor) return; - editor.view.dom.querySelectorAll('img').forEach((img) => { - const src = img.getAttribute('src'); - if (src) img.setAttribute('src', src); - }); - }); } - }); + } else { + // Desktop: destroy old editor, wait for DOM swap, then recreate. + destroyEditor(); + const content = sourceContent || ($activeNote?.content ?? ''); + tick().then(() => { + if (editorElement && !editor) { + createEditor(content); + } + }); + } } }); From 4f324ad68288a8390ae8c28f87e19668c4e3bf70 Mon Sep 17 00:00:00 2001 From: Yuri Karamian Date: Fri, 19 Jun 2026 11:31:49 +0200 Subject: [PATCH 05/13] Fix pasted images disappearing when switching to source mode (#87) --- src/lib/components/Editor.svelte | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/lib/components/Editor.svelte b/src/lib/components/Editor.svelte index 5fbebb5..ce8ecc2 100644 --- a/src/lib/components/Editor.svelte +++ b/src/lib/components/Editor.svelte @@ -4620,17 +4620,15 @@ // Switching TO source: fix blob images first, then extract markdown if (hasPendingBlobs) { hasPendingBlobs = false; - fixingBlobsPromise = fixBlobImages(); - } - const doSwap = () => { + fixBlobImages().then(() => { + sourceContent = editor ? editorToMarkdown() : ($activeNote?.content ?? ''); + resetSourceHistory(sourceContent); + lastSourceMode = true; + }); + } else { sourceContent = editor ? editorToMarkdown() : ($activeNote?.content ?? ''); resetSourceHistory(sourceContent); lastSourceMode = true; - }; - if (fixingBlobsPromise) { - fixingBlobsPromise.then(doSwap); - } else { - doSwap(); } } else if (!isSource && lastSourceMode) { lastSourceMode = false; From 1681fade3bc541809620a9825357f22364889a2c Mon Sep 17 00:00:00 2001 From: Yuri Karamian Date: Fri, 19 Jun 2026 11:38:40 +0200 Subject: [PATCH 06/13] Fix editor not rendering on every other source mode switch --- src/lib/components/Editor.svelte | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/lib/components/Editor.svelte b/src/lib/components/Editor.svelte index ce8ecc2..d4a5bf4 100644 --- a/src/lib/components/Editor.svelte +++ b/src/lib/components/Editor.svelte @@ -4617,19 +4617,10 @@ if (!loadedPath) return; if (isSource && !lastSourceMode) { - // Switching TO source: fix blob images first, then extract markdown - if (hasPendingBlobs) { - hasPendingBlobs = false; - fixBlobImages().then(() => { - sourceContent = editor ? editorToMarkdown() : ($activeNote?.content ?? ''); - resetSourceHistory(sourceContent); - lastSourceMode = true; - }); - } else { - sourceContent = editor ? editorToMarkdown() : ($activeNote?.content ?? ''); - resetSourceHistory(sourceContent); - lastSourceMode = true; - } + // Switching TO source: extract markdown from editor + sourceContent = editor ? editorToMarkdown() : ($activeNote?.content ?? ''); + resetSourceHistory(sourceContent); + lastSourceMode = true; } else if (!isSource && lastSourceMode) { lastSourceMode = false; if (isMobile) { @@ -4646,6 +4637,11 @@ tick().then(() => { if (editorElement && !editor) { createEditor(content); + } else if (!editor) { + // editorElement not ready yet — retry next frame + requestAnimationFrame(() => { + if (editorElement && !editor) createEditor(content); + }); } }); } From c5a45131a54752e71394350db725c40a8410dfd6 Mon Sep 17 00:00:00 2001 From: Yuri Karamian Date: Fri, 19 Jun 2026 11:40:57 +0200 Subject: [PATCH 07/13] Fix editor not rendering on every other source mode switch --- src/lib/components/Editor.svelte | 57 ++++++++++++++++---------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/src/lib/components/Editor.svelte b/src/lib/components/Editor.svelte index d4a5bf4..62ca55b 100644 --- a/src/lib/components/Editor.svelte +++ b/src/lib/components/Editor.svelte @@ -4616,36 +4616,37 @@ // Only act if we have a loaded note if (!loadedPath) return; - if (isSource && !lastSourceMode) { - // Switching TO source: extract markdown from editor - sourceContent = editor ? editorToMarkdown() : ($activeNote?.content ?? ''); - resetSourceHistory(sourceContent); - lastSourceMode = true; - } else if (!isSource && lastSourceMode) { - lastSourceMode = false; - if (isMobile) { - // Mobile: editor stays in DOM, just update its content - const content = sourceContent || ($activeNote?.content ?? ''); - if (editor) { - ignoreNextUpdate = true; - editor.commands.setContent(markdownToHtml(content)); - } - } else { - // Desktop: destroy old editor, wait for DOM swap, then recreate. - destroyEditor(); - const content = sourceContent || ($activeNote?.content ?? ''); - tick().then(() => { - if (editorElement && !editor) { - createEditor(content); - } else if (!editor) { - // editorElement not ready yet — retry next frame - requestAnimationFrame(() => { - if (editorElement && !editor) createEditor(content); - }); + // untrack: prevent flushSave() (inside destroyEditor) from adding + // $editorDirty/$activeNote as effect dependencies, which causes + // the effect to re-run and race with tick().then() — resulting in + // the editor failing to render on every other source-mode switch. + untrack(() => { + if (isSource && !lastSourceMode) { + // Switching TO source: extract markdown from editor + sourceContent = editor ? editorToMarkdown() : ($activeNote?.content ?? ''); + resetSourceHistory(sourceContent); + lastSourceMode = true; + } else if (!isSource && lastSourceMode) { + lastSourceMode = false; + if (isMobile) { + // Mobile: editor stays in DOM, just update its content + const content = sourceContent || ($activeNote?.content ?? ''); + if (editor) { + ignoreNextUpdate = true; + editor.commands.setContent(markdownToHtml(content)); } - }); + } else { + // Desktop: destroy old editor, wait for DOM swap, then recreate. + destroyEditor(); + const content = sourceContent || ($activeNote?.content ?? ''); + tick().then(() => { + if (editorElement && !editor) { + createEditor(content); + } + }); + } } - } + }); }); // Tauri drag-drop listener for OS file drops (browser DragEvent doesn't have files in Tauri) From f8371aaa578174435c576b17950eb48a19d49874 Mon Sep 17 00:00:00 2001 From: Yuri Karamian Date: Fri, 19 Jun 2026 11:47:11 +0200 Subject: [PATCH 08/13] Fix editor not rendering on every other source mode switch --- src/lib/components/Editor.svelte | 62 ++++++++++++++++---------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/src/lib/components/Editor.svelte b/src/lib/components/Editor.svelte index 62ca55b..2a5a00a 100644 --- a/src/lib/components/Editor.svelte +++ b/src/lib/components/Editor.svelte @@ -3392,7 +3392,15 @@ function createEditor(content: string) { if (!editorElement) return; - destroyEditor(); + // Don't call destroyEditor here — it sets editorReady=false which + // triggers reactivity and races with the tick().then() in the + // source-mode swap. Just destroy the editor instance directly. + if (editor) { + editor.destroy(); + editor = null; + } + mathObserver?.disconnect(); + mathObserver = null; isLargeDoc = content.length > LARGE_DOC_CHARS; const html = markdownToHtml(content); @@ -4616,37 +4624,31 @@ // Only act if we have a loaded note if (!loadedPath) return; - // untrack: prevent flushSave() (inside destroyEditor) from adding - // $editorDirty/$activeNote as effect dependencies, which causes - // the effect to re-run and race with tick().then() — resulting in - // the editor failing to render on every other source-mode switch. - untrack(() => { - if (isSource && !lastSourceMode) { - // Switching TO source: extract markdown from editor - sourceContent = editor ? editorToMarkdown() : ($activeNote?.content ?? ''); - resetSourceHistory(sourceContent); - lastSourceMode = true; - } else if (!isSource && lastSourceMode) { - lastSourceMode = false; - if (isMobile) { - // Mobile: editor stays in DOM, just update its content - const content = sourceContent || ($activeNote?.content ?? ''); - if (editor) { - ignoreNextUpdate = true; - editor.commands.setContent(markdownToHtml(content)); - } - } else { - // Desktop: destroy old editor, wait for DOM swap, then recreate. - destroyEditor(); - const content = sourceContent || ($activeNote?.content ?? ''); - tick().then(() => { - if (editorElement && !editor) { - createEditor(content); - } - }); + if (isSource && !lastSourceMode) { + // Switching TO source: extract markdown from editor + sourceContent = editor ? editorToMarkdown() : ($activeNote?.content ?? ''); + resetSourceHistory(sourceContent); + lastSourceMode = true; + } else if (!isSource && lastSourceMode) { + lastSourceMode = false; + if (isMobile) { + // Mobile: editor stays in DOM, just update its content + const content = sourceContent || ($activeNote?.content ?? ''); + if (editor) { + ignoreNextUpdate = true; + editor.commands.setContent(markdownToHtml(content)); } + } else { + // Desktop: destroy old editor, wait for DOM swap, then recreate. + destroyEditor(); + const content = sourceContent || ($activeNote?.content ?? ''); + tick().then(() => { + if (editorElement && !editor) { + createEditor(content); + } + }); } - }); + } }); // Tauri drag-drop listener for OS file drops (browser DragEvent doesn't have files in Tauri) From 3b88b20c61e94908f3b99c9bb0f89c9ed011a22f Mon Sep 17 00:00:00 2001 From: Yuri Karamian Date: Fri, 19 Jun 2026 11:49:18 +0200 Subject: [PATCH 09/13] Fix editor not rendering on every other source mode switch --- src/lib/components/Editor.svelte | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib/components/Editor.svelte b/src/lib/components/Editor.svelte index 2a5a00a..a998fb6 100644 --- a/src/lib/components/Editor.svelte +++ b/src/lib/components/Editor.svelte @@ -78,6 +78,7 @@ let fixingBlobsPromise: Promise = Promise.resolve(); let hasPendingBlobs = false; let lastSourceMode = $sourceMode; + let sourceSwapPending = false; let linkContextMenu = $state<{ x: number; y: number; href: string; anchor: HTMLAnchorElement } | null>(null); let titleWasStripped = false; let strippedTitle = ''; @@ -3327,7 +3328,7 @@ // When editorElement appears in DOM, initialize TipTap. // On mobile, pre-create editor with empty content so first note load is fast. $effect(() => { - if (editorElement && !editor) { + if (editorElement && !editor && !sourceSwapPending) { if (pendingContent !== null) { createEditor(pendingContent); pendingContent = null; @@ -4640,12 +4641,14 @@ } } else { // Desktop: destroy old editor, wait for DOM swap, then recreate. + sourceSwapPending = true; destroyEditor(); const content = sourceContent || ($activeNote?.content ?? ''); tick().then(() => { if (editorElement && !editor) { createEditor(content); } + sourceSwapPending = false; }); } } From d2ddc7f0de5df153206d2389f4b654d83dddc29a Mon Sep 17 00:00:00 2001 From: Yuri Karamian Date: Fri, 19 Jun 2026 11:54:13 +0200 Subject: [PATCH 10/13] Fix editor not rendering on every other source mode switch --- src/lib/components/Editor.svelte | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/lib/components/Editor.svelte b/src/lib/components/Editor.svelte index a998fb6..4fd22dc 100644 --- a/src/lib/components/Editor.svelte +++ b/src/lib/components/Editor.svelte @@ -78,7 +78,6 @@ let fixingBlobsPromise: Promise = Promise.resolve(); let hasPendingBlobs = false; let lastSourceMode = $sourceMode; - let sourceSwapPending = false; let linkContextMenu = $state<{ x: number; y: number; href: string; anchor: HTMLAnchorElement } | null>(null); let titleWasStripped = false; let strippedTitle = ''; @@ -3328,11 +3327,11 @@ // When editorElement appears in DOM, initialize TipTap. // On mobile, pre-create editor with empty content so first note load is fast. $effect(() => { - if (editorElement && !editor && !sourceSwapPending) { + if (editorElement && !editor) { if (pendingContent !== null) { createEditor(pendingContent); pendingContent = null; - } else if (isMobile) { + } else if (isMobile && !lastSourceMode) { createEditor(''); } } @@ -3393,9 +3392,6 @@ function createEditor(content: string) { if (!editorElement) return; - // Don't call destroyEditor here — it sets editorReady=false which - // triggers reactivity and races with the tick().then() in the - // source-mode swap. Just destroy the editor instance directly. if (editor) { editor.destroy(); editor = null; @@ -4640,15 +4636,14 @@ editor.commands.setContent(markdownToHtml(content)); } } else { - // Desktop: destroy old editor, wait for DOM swap, then recreate. - sourceSwapPending = true; + // Desktop: destroy old editor (its DOM element is gone), + // wait for DOM to swap textarea→div, then create editor on new element. destroyEditor(); const content = sourceContent || ($activeNote?.content ?? ''); tick().then(() => { if (editorElement && !editor) { createEditor(content); } - sourceSwapPending = false; }); } } From 2e8c048a3ea4937f7b2217f6859a40b88276db06 Mon Sep 17 00:00:00 2001 From: Yuri Karamian Date: Fri, 19 Jun 2026 12:01:13 +0200 Subject: [PATCH 11/13] Fix images not rendering after <- Client-side Create New Pull Zone button for allowed --> comment in source mode (#87) --- src/lib/components/Editor.svelte | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib/components/Editor.svelte b/src/lib/components/Editor.svelte index 4fd22dc..b1755d5 100644 --- a/src/lib/components/Editor.svelte +++ b/src/lib/components/Editor.svelte @@ -3280,6 +3280,11 @@ src = src.replace(/^([\s>]*)-\s\[ \][^\S\n]+(.+)$/gm, '$1- $2'); src = src.replace(/^([\s>]*)-\s\[ \][^\S\n]*$/gm, '$1-  '); + // Pre-process: strip list-separator comments before markdown-it. + // markdown-it treats as an HTML block start, swallowing the + // next line (e.g. an image) as raw HTML instead of parsing it. + src = src.replace(/^\s*$/gm, ''); + // Pre-process: preserve blank lines before image-only lines // markdown-it collapses blank lines into paragraph breaks, losing the empty paragraph. // Insert a
marker that markdown-it passes through (html: true), then convert to

From a0b1fa048181751028f0b1e9d6d108bac11adc86 Mon Sep 17 00:00:00 2001 From: Yuri Karamian Date: Fri, 19 Jun 2026 12:03:49 +0200 Subject: [PATCH 12/13] Fix images not rendering after comment in source mode (#87) --- src/lib/components/Editor.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/Editor.svelte b/src/lib/components/Editor.svelte index b1755d5..8f3e6a5 100644 --- a/src/lib/components/Editor.svelte +++ b/src/lib/components/Editor.svelte @@ -3283,7 +3283,7 @@ // Pre-process: strip list-separator comments before markdown-it. // markdown-it treats as an HTML block start, swallowing the // next line (e.g. an image) as raw HTML instead of parsing it. - src = src.replace(/^\s*$/gm, ''); + src = src.replace(//g, ''); // Pre-process: preserve blank lines before image-only lines // markdown-it collapses blank lines into paragraph breaks, losing the empty paragraph. From a669d96aed074d398ac07a8eac49b451d8fb894c Mon Sep 17 00:00:00 2001 From: Yuri Karamian Date: Fri, 19 Jun 2026 12:09:30 +0200 Subject: [PATCH 13/13] Fix images not rendering after comment in source mode (#87) --- src/lib/components/Editor.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/Editor.svelte b/src/lib/components/Editor.svelte index 8f3e6a5..4502c80 100644 --- a/src/lib/components/Editor.svelte +++ b/src/lib/components/Editor.svelte @@ -3283,7 +3283,7 @@ // Pre-process: strip list-separator comments before markdown-it. // markdown-it treats as an HTML block start, swallowing the // next line (e.g. an image) as raw HTML instead of parsing it. - src = src.replace(//g, ''); + src = src.replace(//g, '\n'); // Pre-process: preserve blank lines before image-only lines // markdown-it collapses blank lines into paragraph breaks, losing the empty paragraph.