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.
This commit is contained in:
2026-05-09 17:44:12 +01:00
parent d50aea6845
commit 0b3f886337
+24 -9
View File
@@ -294,8 +294,8 @@ function toggleTrash() {
loadEntries(); loadEntries();
playSound('click'); playSound('click');
} }
async function restoreEntry(id) { 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) { toast('✅ Restored!'); await loadEntries(); playSound('success'); } } catch (e) { toast('⚠️ Error', 'error'); } 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) { 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) {} 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() { async function batchDelete() {
if (!confirm(`Move ${selectedIds.size} entries to trash?`)) return; 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(); clearSelection();
await loadEntries();
} }
async function batchPermanentDelete() { async function batchPermanentDelete() {
@@ -1077,8 +1081,12 @@ async function batchPermanentDelete() {
} }
async function batchRestore() { 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(); clearSelection();
await loadEntries();
} }
async function batchMove() { async function batchMove() {
@@ -1127,11 +1135,11 @@ async function addEntry() {
} catch (e) { toast('⚠️ Error', 'error'); } } catch (e) { toast('⚠️ Error', 'error'); }
finally { btn.disabled = false; } finally { btn.disabled = false; }
} }
async function delEntry(id) { async function delEntry(id, noToast) {
try { try {
const r = await fetch(API + '/entries/' + id, { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + token, 'X-CSRF-Token': csrfToken } }); 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'); } 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) { toast('Error', 'error'); } } catch (e) { if (!noToast) toast('Error', 'error'); }
} }
// ==================== UTILS ==================== // ==================== UTILS ====================
@@ -1185,7 +1193,7 @@ function highlightText(text, query) {
function showShortcutsHelp() { function showShortcutsHelp() {
const overlay = document.createElement('div'); const overlay = document.createElement('div');
overlay.className = 'custom-modal-overlay show'; overlay.className = 'custom-modal-overlay show';
overlay.innerHTML = `<div class="custom-modal" style="min-width:380px"><h3>⌨️ Keyboard Shortcuts</h3><div style="display:grid;grid-template-columns:auto 1fr;gap:0.5rem 1.2rem;font-size:0.85rem;margin-bottom:1rem"><span style="background:var(--input);padding:0.15rem 0.6rem;border-radius:0.4rem;font-family:monospace;text-align:center">Ctrl+N</span><span>New entry</span><span style="background:var(--input);padding:0.15rem 0.6rem;border-radius:0.4rem;font-family:monospace;text-align:center">Ctrl+F</span><span>Search</span><span style="background:var(--input);padding:0.15rem 0.6rem;border-radius:0.4rem;font-family:monospace;text-align:center">Ctrl+T</span><span>Toggle trash</span><span style="background:var(--input);padding:0.15rem 0.6rem;border-radius:0.4rem;font-family:monospace;text-align:center">Ctrl+L</span><span>Lock vault</span><span style="background:var(--input);padding:0.15rem 0.6rem;border-radius:0.4rem;font-family:monospace;text-align:center">Ctrl+S</span><span>Save entry</span><span style="background:var(--input);padding:0.15rem 0.6rem;border-radius:0.4rem;font-family:monospace;text-align:center">Esc</span><span>Close modal / deselect</span><span style="background:var(--input);padding:0.15rem 0.6rem;border-radius:0.4rem;font-family:monospace;text-align:center">◀ ▶</span><span>Detail view nav</span><span style="background:var(--input);padding:0.15rem 0.6rem;border-radius:0.4rem;font-family:monospace;text-align:center">?</span><span>Show this help</span></div><p style="color:var(--text2);font-size:0.8rem;margin-bottom:0.8rem">💡 Click any entry to select, Shift+click for range, Ctrl+click to toggle</p><div class="modal-actions"><button class="btn btn-sm" onclick="this.closest('.custom-modal-overlay').remove()">Got it</button></div></div>`; overlay.innerHTML = `<div class="custom-modal" style="min-width:380px"><h3>⌨️ Keyboard Shortcuts</h3><div style="display:grid;grid-template-columns:auto 1fr;gap:0.5rem 1.2rem;font-size:0.85rem;margin-bottom:1rem"><span style="background:var(--input);padding:0.15rem 0.6rem;border-radius:0.4rem;font-family:monospace;text-align:center">Ctrl+N</span><span>New entry</span><span style="background:var(--input);padding:0.15rem 0.6rem;border-radius:0.4rem;font-family:monospace;text-align:center">Ctrl+A</span><span>Select all</span><span style="background:var(--input);padding:0.15rem 0.6rem;border-radius:0.4rem;font-family:monospace;text-align:center">Ctrl+F</span><span>Search</span><span style="background:var(--input);padding:0.15rem 0.6rem;border-radius:0.4rem;font-family:monospace;text-align:center">Ctrl+T</span><span>Toggle trash</span><span style="background:var(--input);padding:0.15rem 0.6rem;border-radius:0.4rem;font-family:monospace;text-align:center">Ctrl+L</span><span>Lock vault</span><span style="background:var(--input);padding:0.15rem 0.6rem;border-radius:0.4rem;font-family:monospace;text-align:center">Ctrl+S</span><span>Save entry</span><span style="background:var(--input);padding:0.15rem 0.6rem;border-radius:0.4rem;font-family:monospace;text-align:center">Esc</span><span>Close modal / deselect</span><span style="background:var(--input);padding:0.15rem 0.6rem;border-radius:0.4rem;font-family:monospace;text-align:center">◀ ▶</span><span>Detail view nav</span><span style="background:var(--input);padding:0.15rem 0.6rem;border-radius:0.4rem;font-family:monospace;text-align:center">?</span><span>Show this help</span></div><p style="color:var(--text2);font-size:0.8rem;margin-bottom:0.8rem">💡 Click any entry to select, Shift+click for range, Ctrl+click to toggle</p><div class="modal-actions"><button class="btn btn-sm" onclick="this.closest('.custom-modal-overlay').remove()">Got it</button></div></div>`;
document.body.appendChild(overlay); document.body.appendChild(overlay);
overlay.addEventListener('click', e => { if (e.target === overlay) overlay.remove(); }); 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 // Prevent browser defaults for ALL our shortcuts BEFORE dispatching
const code = e.code; 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(); e.preventDefault();
} }
@@ -1368,5 +1376,12 @@ document.addEventListener('keydown', function(e) {
} else if (code === 'KeyS') { } else if (code === 'KeyS') {
if (document.getElementById('addModal').classList.contains('show')) addEntry(); if (document.getElementById('addModal').classList.contains('show')) addEntry();
else if (document.getElementById('editModal').classList.contains('show')) saveEdit(); 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');
}
} }
}); });