diff --git a/js/app.js b/js/app.js index 75735c8..5d2b37c 100644 --- a/js/app.js +++ b/js/app.js @@ -300,13 +300,17 @@ async function restoreEntry(id, noToast) { async function toggleFavorite(id) { try { await fetch(API + '/entries/' + id + '/favorite', { method: 'POST', headers: { 'Authorization': 'Bearer ' + token, 'X-CSRF-Token': csrfToken } }); const e = entries.find(x => x.id == id); if (e) e.favorite = e.favorite ? 0 : 1; renderFolders(); render(); playSound('click'); } catch (e) {} } -async function permanentDelete(id) { - if (!confirm('Permanently delete?')) return; - try { const r = await fetch(API + '/entries/' + id + '?permanent=1', { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + token, 'X-CSRF-Token': csrfToken } }); if (r.ok) { toast('🗑️ Permanently deleted'); await loadEntries(); playSound('error'); } } catch (e) { toast('⚠️ Error', 'error'); } +async function permanentDelete(id, silent) { + const doDelete = async () => { + try { const r = await fetch(API + '/entries/' + id + '?permanent=1', { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + token, 'X-CSRF-Token': csrfToken } }); if (r.ok) { order = order.filter(x => x != id); localStorage.setItem('entryOrder', JSON.stringify(order)); if (!silent) { toast('🗑️ Permanently deleted'); await loadEntries(); playSound('error'); } } } catch (e) { if (!silent) toast('⚠️ Error', 'error'); } + }; + if (silent) { await doDelete(); return; } + showCenterConfirm('Permanently delete?', doDelete); } async function emptyTrash() { - if (!confirm('Delete ALL trashed entries?')) return; - try { const r = await fetch(API + '/entries/trash/empty', { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + token, 'X-CSRF-Token': csrfToken } }); if (r.ok) { toast('🗑️ Trash emptied'); await loadEntries(); playSound('error'); } } catch (e) { toast('⚠️ Error', 'error'); } + showCenterConfirm('Delete ALL trashed entries?', async () => { + try { const r = await fetch(API + '/entries/trash/empty', { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + token, 'X-CSRF-Token': csrfToken } }); if (r.ok) { toast('🗑️ Trash emptied'); await loadEntries(); playSound('error'); } } catch (e) { toast('⚠️ Error', 'error'); } + }); } function timeAgo(dateStr) { if (!dateStr) return ''; @@ -937,6 +941,36 @@ function showConfirm(btn, message, callback) { }, 10); } function entryPw(id) { const e = entries.find(x => x.id == id); return e ? e.password : ''; } +function showCenterConfirm(message, callback) { + const existing = document.querySelector('.custom-confirm'); + if (existing) existing.remove(); + const confirm = document.createElement('div'); + confirm.className = 'custom-confirm show'; + confirm.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:10000;'; + confirm.innerHTML = + '
' + message + '
' + + '
' + + '' + + '' + + '
'; + document.body.appendChild(confirm); + const yesBtn = confirm.querySelector('.confirm-yes'); + const noBtn = confirm.querySelector('.confirm-no'); + const cleanup = () => { confirm.remove(); document.removeEventListener('keydown', keyHandler); }; + const keyHandler = (e) => { + if (e.key === 'Enter' || e.key === 'y' || e.key === 'Y') { e.preventDefault(); cleanup(); callback(); playSound('delete'); } + else if (e.key === 'Escape' || e.key === 'n' || e.key === 'N') { e.preventDefault(); cleanup(); } + }; + yesBtn.onclick = () => { cleanup(); callback(); playSound('delete'); }; + noBtn.onclick = () => cleanup(); + confirm.tabIndex = 0; confirm.focus(); + document.addEventListener('keydown', keyHandler); + setTimeout(() => { + document.addEventListener('click', function closeConfirm(e) { + if (!confirm.contains(e.target)) { cleanup(); document.removeEventListener('click', closeConfirm); } + }); + }, 10); +} function attachEvents() { document.querySelectorAll('.delete-btn').forEach(b => b.onclick = function(ev) { ev.stopPropagation(); if (showTrash) { permanentDelete(this.dataset.id); } else { showConfirm(this, 'Delete this entry?', id => delEntry(id)); } }); document.querySelectorAll('.edit-btn').forEach(b => b.onclick = function(ev) { ev.stopPropagation(); openEdit(this.dataset.id); }); @@ -1066,19 +1100,25 @@ function hideBatchBar() { } async function batchDelete() { - if (!confirm(`Move ${selectedIds.size} entries to trash?`)) return; const count = selectedIds.size; - for (const id of selectedIds) await delEntry(id, true); - toast('📦 Moved ' + count + ' entries to trash'); - playSound('delete'); - clearSelection(); - await loadEntries(); + showCenterConfirm('Move ' + count + ' entries to trash?', async () => { + for (const id of selectedIds) await delEntry(id, true); + toast('📦 Moved ' + count + ' entries to trash'); + playSound('delete'); + clearSelection(); + await loadEntries(); + }); } async function batchPermanentDelete() { - if (!confirm(`Permanently delete ${selectedIds.size} entries?`)) return; - for (const id of selectedIds) await permanentDelete(id); - clearSelection(); + const count = selectedIds.size; + showCenterConfirm('Permanently delete ' + count + ' entries?', async () => { + for (const id of selectedIds) await permanentDelete(id, true); + toast('🗑️ Permanently deleted ' + count + ' entries'); + playSound('error'); + clearSelection(); + await loadEntries(); + }); } async function batchRestore() {