diff --git a/js/app.js b/js/app.js index ed89123..b4f5d92 100644 --- a/js/app.js +++ b/js/app.js @@ -3562,6 +3562,7 @@ async function batchDelete() { danger: true, }); if (!ok) return; + await closeEditorForDeleted(ids, true); for (const id of ids) { try { await api('/entries/' + id, { method: 'DELETE', headers: authHeaders() }); @@ -3599,6 +3600,7 @@ async function batchPermDelete() { danger: true, }); if (!ok) return; + await closeEditorForDeleted(ids, true); for (const id of ids) { try { await api('/entries/' + id + '?permanent=1', { method: 'DELETE', headers: authHeaders() }); @@ -5162,6 +5164,7 @@ async function permanentDelete(id) { danger: true, }); if (!ok) return; + await closeEditorForDeleted([id], true); try { await api('/entries/' + id + '?permanent=1', { method: 'DELETE', headers: authHeaders() }); toast('Deleted permanently'); @@ -5180,6 +5183,7 @@ async function emptyTrash() { danger: true, }); if (!ok) return; + await closeEditorForDeleted(state.trashed.map(t => t.id), true); try { await api('/entries/trash/empty', { method: 'DELETE', headers: authHeaders() }); toast('Trash emptied'); @@ -5276,8 +5280,21 @@ function flashEntry(id) { }, 50); } +// Every delete path (single, batch, permanent) routes its editor handling +// through here. The open editor must not outlive its entry, and its unsaved +// edits must not vanish unannounced. Returns false when the user backed out +// of the discard prompt — the caller then deletes nothing. +// `alreadyConfirmed` = the caller already showed a delete modal, so the +// discard prompt is suppressed: one modal per action. +async function closeEditorForDeleted(ids, alreadyConfirmed) { + if (!soState || soState.id == null || !ids.includes(soState.id)) return true; + if (alreadyConfirmed) { closeSlideOver(); return true; } + return await requestCloseSlideOver(); +} + async function deleteEntry(id) { const e = state.entries.find(x => x.id === id); + let confirmed = false; if (state.askBeforeDelete) { const ok = await confirmDialog({ title: 'Move to trash', @@ -5286,21 +5303,12 @@ async function deleteEntry(id) { danger: true, }); if (!ok) return; - } else if (soState && soState.id === id) { - // No delete confirmation configured: a misclick on ✕ would trash the - // entry AND drop the edits in the open editor with no warning at all. - // The discard gate stands in for it (it no-ops when nothing is dirty). - // Skipped when askBeforeDelete already asked — one modal is enough. - if (!(await requestCloseSlideOver())) return; + confirmed = true; } + if (!(await closeEditorForDeleted([id], confirmed))) return; try { await api('/entries/' + id, { method: 'DELETE', headers: authHeaders() }); toast('Moved to trash'); - // Close the editor ONLY when it holds the entry being trashed. Deleting - // some other row used to close it unconditionally, silently dropping - // unsaved edits (no discard prompt — this path bypasses - // requestCloseSlideOver by design: the entry is gone either way). - if (soState && soState.id === id) closeSlideOver(); state.checked.delete(id); await loadEntries(); state.trashedCount = (state.trashedCount || 0) + 1;