From f61a567cea68e52d07f29831830d37e295bc5c04 Mon Sep 17 00:00:00 2001 From: Frederic Cabassut Date: Sun, 8 Mar 2026 21:56:25 +0100 Subject: [PATCH] feat: auto-save note when clicking image toggle on new note Instead of disabling the image button for unsaved notes, clicking it now auto-saves the note first (with title "Untitled" if empty), then opens the upload panel. This removes friction from the upload flow. Co-Authored-By: Claude Opus 4.6 --- src/lib/components/NoteEditor.svelte | 66 ++++++++++++++++++++-------- tests/e2e/attachments.spec.ts | 13 ++++-- 2 files changed, 57 insertions(+), 22 deletions(-) diff --git a/src/lib/components/NoteEditor.svelte b/src/lib/components/NoteEditor.svelte index 0b3212a..bfc432f 100644 --- a/src/lib/components/NoteEditor.svelte +++ b/src/lib/components/NoteEditor.svelte @@ -38,13 +38,19 @@ let tiptapEditor: Editor | undefined = $state(); let editorTick = $state(0); + // Mutable note identity — allows transitioning from new → saved without closing + // svelte-ignore state_referenced_locally + let noteId = $state(note?.id ?? null); + // svelte-ignore state_referenced_locally + let currentlyNew = $state(isNew); + // svelte-ignore state_referenced_locally let attachmentsList = $state(note?.attachments ?? []); // Fetch attachments for existing notes if not pre-populated (e.g. loaded from IDB) $effect(() => { - if (note && !isNew && (!note.attachments || note.attachments.length === 0)) { - fetch(`/api/notes/${note.id}/attachments`) + if (noteId && !currentlyNew && (!note?.attachments || note.attachments.length === 0)) { + fetch(`/api/notes/${noteId}/attachments`) .then((res) => res.ok ? res.json() : []) .then((data: Attachment[]) => { if (data.length > 0) attachmentsList = data; @@ -58,9 +64,9 @@ } async function handleAttachmentRemove(attachmentId: string) { - if (!note) return; + if (!noteId) return; try { - await fetch(`/api/notes/${note.id}/attachments?attachmentId=${attachmentId}`, { + await fetch(`/api/notes/${noteId}/attachments?attachmentId=${attachmentId}`, { method: 'DELETE' }); attachmentsList = attachmentsList.filter((a) => a.id !== attachmentId); @@ -75,23 +81,48 @@ bgStyle = `background-color: ${colors.bg}`; }); - async function save() { + async function saveAndClose() { if (!title.trim() && !content.trim()) { onClose(); return; } - if (isNew) { + if (currentlyNew) { await createNote({ title, content, color, checklistMode }); - } else if (note) { - await updateNote(note.id, { title, content, color, checklistMode }); + } else if (noteId) { + await updateNote(noteId, { title, content, color, checklistMode }); } onClose(); } + /** Auto-save a new note without closing, returns the new note ID */ + async function autoSave(): Promise { + if (!currentlyNew) return noteId; + const created = await createNote({ + title: title || 'Untitled', + content, + color, + checklistMode + }); + if (created) { + noteId = created.id; + currentlyNew = false; + return created.id; + } + return null; + } + + async function toggleImageUpload() { + if (currentlyNew) { + const id = await autoSave(); + if (!id) return; + } + showImageUpload = !showImageUpload; + } + function handleKeydown(e: KeyboardEvent) { if (e.key === 'Escape') { - save(); + saveAndClose(); } } @@ -111,7 +142,7 @@
@@ -159,21 +190,21 @@
- {#if !isNew && note && showImageUpload} + {#if noteId && showImageUpload}
- {:else if !isNew && attachmentsList.length > 0} + {:else if noteId && attachmentsList.length > 0}
{#each attachmentsList as attachment} {attachment.filename} @@ -223,10 +254,9 @@