From 0b3f886337e8faca7e487a1a959bb54b4980541f Mon Sep 17 00:00:00 2001 From: Zaki <18zaki18@gmail.com> Date: Sat, 9 May 2026 17:44:12 +0100 Subject: [PATCH] Fix batch toast spam and add Ctrl+A select all Batch delete/restore now shows single toast with count instead of one per entry. delEntry() and restoreEntry() accept noToast param. Added Ctrl+A to select all visible entries. Updated keyboard shortcuts help. --- js/app.js | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/js/app.js b/js/app.js index 692bac3..41d8ac3 100644 --- a/js/app.js +++ b/js/app.js @@ -294,8 +294,8 @@ function toggleTrash() { loadEntries(); playSound('click'); } -async function restoreEntry(id) { - try { const r = await fetch(API + '/entries/' + id + '/restore', { method: 'POST', headers: { 'Authorization': 'Bearer ' + token, 'X-CSRF-Token': csrfToken } }); if (r.ok) { toast('✅ Restored!'); await loadEntries(); playSound('success'); } } catch (e) { toast('⚠️ Error', 'error'); } +async function restoreEntry(id, noToast) { + try { const r = await fetch(API + '/entries/' + id + '/restore', { method: 'POST', headers: { 'Authorization': 'Bearer ' + token, 'X-CSRF-Token': csrfToken } }); if (r.ok) { if (!noToast) { toast('✅ Restored!'); await loadEntries(); playSound('success'); } } } catch (e) { if (!noToast) toast('⚠️ Error', 'error'); } } 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) {} @@ -1066,8 +1066,12 @@ function hideBatchBar() { async function batchDelete() { if (!confirm(`Move ${selectedIds.size} entries to trash?`)) return; - for (const id of selectedIds) await delEntry(id); + const count = selectedIds.size; + for (const id of selectedIds) await delEntry(id, true); + toast('📦 Moved ' + count + ' entries to trash'); + playSound('delete'); clearSelection(); + await loadEntries(); } async function batchPermanentDelete() { @@ -1077,8 +1081,12 @@ async function batchPermanentDelete() { } async function batchRestore() { - for (const id of selectedIds) await restoreEntry(id); + const count = selectedIds.size; + for (const id of selectedIds) await restoreEntry(id, true); + toast('✅ Restored ' + count + ' entries'); + playSound('success'); clearSelection(); + await loadEntries(); } async function batchMove() { @@ -1127,11 +1135,11 @@ async function addEntry() { } catch (e) { toast('⚠️ Error', 'error'); } finally { btn.disabled = false; } } -async function delEntry(id) { +async function delEntry(id, noToast) { try { const r = await fetch(API + '/entries/' + id, { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + token, 'X-CSRF-Token': csrfToken } }); - if (r.ok) { selectedIds.delete(id); order = order.filter(x => x != id); localStorage.setItem('entryOrder', JSON.stringify(order)); toast('📦 Moved to trash'); await loadEntries(); playSound('delete'); } - } catch (e) { toast('Error', 'error'); } + if (r.ok) { selectedIds.delete(id); order = order.filter(x => x != id); localStorage.setItem('entryOrder', JSON.stringify(order)); if (!noToast) { toast('📦 Moved to trash'); await loadEntries(); playSound('delete'); } } + } catch (e) { if (!noToast) toast('Error', 'error'); } } // ==================== UTILS ==================== @@ -1185,7 +1193,7 @@ function highlightText(text, query) { function showShortcutsHelp() { const overlay = document.createElement('div'); overlay.className = 'custom-modal-overlay show'; - overlay.innerHTML = `

⌨️ Keyboard Shortcuts

Ctrl+NNew entryCtrl+FSearchCtrl+TToggle trashCtrl+LLock vaultCtrl+SSave entryEscClose modal / deselect◀ ▶Detail view nav?Show this help

💡 Click any entry to select, Shift+click for range, Ctrl+click to toggle

`; + overlay.innerHTML = `

⌨️ Keyboard Shortcuts

Ctrl+NNew entryCtrl+ASelect allCtrl+FSearchCtrl+TToggle trashCtrl+LLock vaultCtrl+SSave entryEscClose modal / deselect◀ ▶Detail view nav?Show this help

💡 Click any entry to select, Shift+click for range, Ctrl+click to toggle

`; document.body.appendChild(overlay); overlay.addEventListener('click', e => { if (e.target === overlay) overlay.remove(); }); } @@ -1352,7 +1360,7 @@ document.addEventListener('keydown', function(e) { // Prevent browser defaults for ALL our shortcuts BEFORE dispatching const code = e.code; - if (code === 'KeyN' || code === 'KeyF' || code === 'KeyT' || code === 'KeyL' || code === 'KeyS') { + if (code === 'KeyN' || code === 'KeyF' || code === 'KeyT' || code === 'KeyL' || code === 'KeyS' || code === 'KeyA') { e.preventDefault(); } @@ -1368,5 +1376,12 @@ document.addEventListener('keydown', function(e) { } else if (code === 'KeyS') { if (document.getElementById('addModal').classList.contains('show')) addEntry(); else if (document.getElementById('editModal').classList.contains('show')) saveEdit(); + } else if (code === 'KeyA') { + if (!isInput && !document.getElementById('authSection').classList.contains('hidden')) { + getFilteredEntries(view === 'grouped' || view === 'detail').forEach(e => selectedIds.add(e.id)); + updateBatchBar(); + render(); + playSound('click'); + } } }); \ No newline at end of file