Add 3 new views (Card, Grouped, Detail) and view dropdown

Replace view toggle buttons with dropdown menu containing all 7 views. Card view: 2-column grid with bigger cards. Grouped view: entries grouped by folder with sticky headers. Detail view: single entry at a time, large text, prev/next navigation. Arrow key navigation for detail view.
This commit is contained in:
2026-05-09 16:51:38 +01:00
parent 757efe0908
commit d50aea6845
3 changed files with 165 additions and 26 deletions
+35 -4
View File
@@ -64,10 +64,11 @@ input:focus, select:focus { border-color:var(--accent); }
/* Toolbar */ /* Toolbar */
.toolbar { display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:0.5rem; margin-bottom:0.7rem; } .toolbar { display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:0.5rem; margin-bottom:0.7rem; }
.view-toggle { display:flex; gap:0.2rem; background:rgba(0,0,0,0.3); padding:0.2rem; border-radius:2rem; } .view-dropdown { position:relative; }
.view-btn { background:none; border:none; color:var(--text2); padding:0.35rem 0.7rem; border-radius:2rem; cursor:pointer; font-size:0.75rem; transition:0.2s; } .view-dropdown-menu { position:absolute; top:100%; right:0; margin-top:4px; min-width:155px; background:var(--bg2); border:1px solid var(--border); border-radius:0.6rem; padding:0.3rem; box-shadow:0 8px 24px rgba(0,0,0,0.3); z-index:100; display:flex; flex-direction:column; gap:2px; }
.view-btn.active { background:var(--accent); color:#fff; } .view-opt { background:none; border:none; color:var(--text); padding:0.45rem 0.8rem; border-radius:0.4rem; cursor:pointer; font-size:0.8rem; text-align:left; transition:0.12s; white-space:nowrap; }
.view-btn:hover:not(.active) { background:rgba(255,255,255,0.08); } .view-opt:hover { background:var(--accent); color:#fff; }
.view-opt.active { background:var(--accent); color:#fff; font-weight:600; }
.toggles-row { display:flex; gap:1.2rem; align-items:center; flex-wrap:wrap; } .toggles-row { display:flex; gap:1.2rem; align-items:center; flex-wrap:wrap; }
.toggle-item { display:flex; align-items:center; gap:0.4rem; font-size:0.75rem; color:var(--text2); } .toggle-item { display:flex; align-items:center; gap:0.4rem; font-size:0.75rem; color:var(--text2); }
@@ -150,6 +151,10 @@ input:focus, select:focus { border-color:var(--accent); }
.card-user { color:var(--text2); font-size:0.75rem; margin-bottom:0.3rem; } .card-user { color:var(--text2); font-size:0.75rem; margin-bottom:0.3rem; }
.card-folder { font-size:0.65rem; color:var(--accent); margin-bottom:0.3rem; background:rgba(59,130,246,0.15); display:inline-block; padding:0.1rem 0.5rem; border-radius:1rem; } .card-folder { font-size:0.65rem; color:var(--accent); margin-bottom:0.3rem; background:rgba(59,130,246,0.15); display:inline-block; padding:0.1rem 0.5rem; border-radius:1rem; }
.card-password { background:var(--bg2); padding:0.3rem 0.5rem; border-radius:0.6rem; display:flex; align-items:center; justify-content:space-between; font-family:monospace; font-size:0.75rem; gap:0.2rem; } .card-password { background:var(--bg2); padding:0.3rem 0.5rem; border-radius:0.6rem; display:flex; align-items:center; justify-content:space-between; font-family:monospace; font-size:0.75rem; gap:0.2rem; }
#entriesContainer.card-view .entry-card { padding:1.2rem 3rem 1.2rem 1.2rem; }
#entriesContainer.card-view .card-site { font-size:1.05rem; }
#entriesContainer.card-view .card-password { font-size:0.9rem; }
#entriesContainer.card-view .entry-card:hover { transform:translateY(-3px); }
.entry-row { background:var(--card); border:1px solid var(--border); border-radius:0.8rem; padding:0.7rem 2.8rem 0.7rem 0.9rem; display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:0.4rem; position:relative; } .entry-row { background:var(--card); border:1px solid var(--border); border-radius:0.8rem; padding:0.7rem 2.8rem 0.7rem 0.9rem; display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:0.4rem; position:relative; }
.entry-compact { display:flex; align-items:center; gap:0.5rem; padding:0.35rem 2.8rem 0.35rem 0.7rem; background:var(--card); border-radius:0.5rem; border:1px solid var(--border); font-size:0.8rem; position:relative; } .entry-compact { display:flex; align-items:center; gap:0.5rem; padding:0.35rem 2.8rem 0.35rem 0.7rem; background:var(--card); border-radius:0.5rem; border:1px solid var(--border); font-size:0.8rem; position:relative; }
.action-btns { position:absolute; top:4px; right:6px; display:flex; gap:4px; z-index:1; } .action-btns { position:absolute; top:4px; right:6px; display:flex; gap:4px; z-index:1; }
@@ -165,6 +170,32 @@ input:focus, select:focus { border-color:var(--accent); }
.entry-row.favorite { border-color:rgba(255,200,0,0.3); background:rgba(255,200,0,0.05); } .entry-row.favorite { border-color:rgba(255,200,0,0.3); background:rgba(255,200,0,0.05); }
.entry-compact.favorite { border-color:rgba(255,200,0,0.3); background:rgba(255,200,0,0.05); } .entry-compact.favorite { border-color:rgba(255,200,0,0.3); background:rgba(255,200,0,0.05); }
.table-row-drag.favorite td { background:rgba(255,200,0,0.05); } .table-row-drag.favorite td { background:rgba(255,200,0,0.05); }
/* Grouped view */
#entriesContainer.grouped-view { display:flex; flex-direction:column; gap:0.15rem; }
.grouped-header { position:sticky; top:0; z-index:2; background:var(--bg); padding:0.55rem 0.8rem; border-radius:0.5rem; font-weight:600; font-size:0.9rem; color:var(--accent); display:flex; align-items:center; gap:0.5rem; border-bottom:2px solid var(--accent); margin-top:0.4rem; }
.grouped-header:first-child { margin-top:0; }
#entriesContainer.grouped-view .entry-row { border-left:3px solid transparent; transition:0.15s; padding-left:1rem; }
#entriesContainer.grouped-view .entry-row:hover { border-left-color:var(--accent); }
/* Detail view */
#entriesContainer.detail-view { display:flex; flex-direction:column; gap:1rem; }
.detail-nav { display:flex; align-items:center; justify-content:center; gap:1rem; padding:0.5rem 0; position:sticky; top:0; z-index:2; background:var(--bg); }
.detail-nav button { background:var(--bg2); border:1px solid var(--border); color:var(--text); padding:0.4rem 1rem; border-radius:0.5rem; cursor:pointer; transition:0.15s; font-size:0.85rem; }
.detail-nav button:hover { border-color:var(--accent); color:var(--accent); }
.detail-nav button:disabled { opacity:0.4; cursor:default; }
.detail-nav .detail-counter { font-size:0.8rem; color:var(--text2); min-width:80px; text-align:center; }
.detail-card { background:var(--bg2); border:1px solid var(--border); border-radius:1rem; padding:1.5rem; display:flex; flex-direction:column; gap:0.9rem; max-width:520px; margin:0 auto; width:100%; }
.detail-field { display:flex; flex-direction:column; gap:0.15rem; }
.detail-label { font-size:0.7rem; color:var(--text2); text-transform:uppercase; letter-spacing:0.5px; }
.detail-value { font-size:1.2rem; word-break:break-all; }
.detail-value.pw-display { font-family:monospace; letter-spacing:2px; font-size:1.3rem; cursor:default; }
.detail-folder { display:flex; gap:0.5rem; align-items:center; }
.detail-actions { display:flex; gap:0.5rem; margin-top:0.3rem; }
.detail-actions button { flex:1; padding:0.5rem; border-radius:0.5rem; border:1px solid var(--border); background:var(--bg); cursor:pointer; transition:0.15s; font-size:0.85rem; }
.detail-actions button:hover { border-color:var(--accent); background:var(--bg2); }
.entry-info { display:flex; gap:0.5rem; align-items:center; flex-wrap:wrap; flex:1; } .entry-info { display:flex; gap:0.5rem; align-items:center; flex-wrap:wrap; flex:1; }
.entry-site { font-weight:700; color:var(--text); } .entry-site { font-weight:700; color:var(--text); }
.entry-user { color:var(--text2); } .entry-user { color:var(--text2); }
+11 -5
View File
@@ -156,11 +156,17 @@
<div style="display:flex;gap:.4rem;align-items:center;flex-wrap:wrap"> <div style="display:flex;gap:.4rem;align-items:center;flex-wrap:wrap">
<button class="btn btn-outline btn-sm" id="trashBtn" onclick="toggleTrash()" title="Trash">🗑️</button> <button class="btn btn-outline btn-sm" id="trashBtn" onclick="toggleTrash()" title="Trash">🗑️</button>
<button class="btn btn-outline btn-sm" onclick="showShortcutsHelp()" title="Shortcuts">⌨️</button> <button class="btn btn-outline btn-sm" onclick="showShortcutsHelp()" title="Shortcuts">⌨️</button>
<div class="view-toggle" id="viewToggle"> <div class="view-dropdown" id="viewDropdown">
<button class="view-btn active" data-view="grid">🟫 Grid</button> <button class="btn btn-outline btn-sm" id="viewDropdownBtn">🟫 Grid</button>
<button class="view-btn" data-view="compact">📝 Compact</button> <div class="view-dropdown-menu hidden" id="viewDropdownMenu">
<button class="view-btn" data-view="table">📊 Table</button> <button class="view-opt" data-view="grid">🟫 Grid</button>
<button class="view-btn" data-view="list">📋 List</button> <button class="view-opt" data-view="compact">📝 Compact</button>
<button class="view-opt" data-view="list">📋 List</button>
<button class="view-opt" data-view="table">📊 Table</button>
<button class="view-opt" data-view="card">🃏 Card</button>
<button class="view-opt" data-view="grouped">📂 Grouped</button>
<button class="view-opt" data-view="detail">🔍 Detail</button>
</div>
</div> </div>
</div> </div>
</div> </div>
+118 -16
View File
@@ -5,6 +5,7 @@ let curUser = sessionStorage.getItem('currentUsername');
function a2b64(arr) { return btoa(String.fromCharCode(...new Uint8Array(arr))).replace(/\+/g,'-').replace(/\//g,'_').replace(/=+$/,''); } function a2b64(arr) { return btoa(String.fromCharCode(...new Uint8Array(arr))).replace(/\+/g,'-').replace(/\//g,'_').replace(/=+$/,''); }
function b642ab(s) { return Uint8Array.from(atob(s.replace(/-/g,'+').replace(/_/g,'/')), c=>c.charCodeAt(0)).buffer; } function b642ab(s) { return Uint8Array.from(atob(s.replace(/-/g,'+').replace(/_/g,'/')), c=>c.charCodeAt(0)).buffer; }
let view = localStorage.getItem('vaultView') || 'grid'; let view = localStorage.getItem('vaultView') || 'grid';
let detailIndex = 0;
let showView = localStorage.getItem('showViewBtn') !== 'false'; let showView = localStorage.getItem('showViewBtn') !== 'false';
let showMail = localStorage.getItem('showEmail') !== 'false'; let showMail = localStorage.getItem('showEmail') !== 'false';
let dark = localStorage.getItem('darkTheme') !== 'false'; let dark = localStorage.getItem('darkTheme') !== 'false';
@@ -356,7 +357,6 @@ async function registerPasskey() {
// ==================== INIT ==================== // ==================== INIT ====================
function init() { function init() {
document.querySelectorAll('.view-btn').forEach(b => b.classList.toggle('active', b.dataset.view === view));
document.getElementById('autoLockTimer').value = lockMin; document.getElementById('autoLockTimer').value = lockMin;
applyTheme(); applyTheme();
// document.getElementById('usernameInput').style.display = showMail ? '' : 'none'; // document.getElementById('usernameInput').style.display = showMail ? '' : 'none';
@@ -364,16 +364,28 @@ function init() {
const sl = localStorage.getItem('savedLoginUser'); const sl = localStorage.getItem('savedLoginUser');
if (sl) document.getElementById('loginUsername').value = sl; if (sl) document.getElementById('loginUsername').value = sl;
syncSettingsUI(); syncSettingsUI();
document.getElementById('viewToggle').addEventListener('click', e => { // View dropdown
if (e.target.classList.contains('view-btn')) { const vdd = document.getElementById('viewDropdown');
document.querySelectorAll('.view-btn').forEach(b => b.classList.remove('active')); const vBtn = document.getElementById('viewDropdownBtn');
e.target.classList.add('active'); const vMenu = document.getElementById('viewDropdownMenu');
view = e.target.dataset.view; const vIcons = {grid:'🟫',compact:'📝',list:'📋',table:'📊',card:'🃏',grouped:'📂',detail:'🔍'};
localStorage.setItem('vaultView', view); const updateViewBtn = () => { vBtn.textContent = (vIcons[view] || '🟫') + ' ' + view.charAt(0).toUpperCase() + view.slice(1) + ' ▾'; };
render(); updateViewBtn();
playSound('click'); vMenu.querySelectorAll('.view-opt').forEach(o => o.classList.toggle('active', o.dataset.view === view));
} vBtn.onclick = (e) => { e.stopPropagation(); vMenu.classList.toggle('hidden'); };
}); vMenu.onclick = (e) => {
const opt = e.target.closest('.view-opt');
if (!opt) return;
vMenu.querySelectorAll('.view-opt').forEach(o => o.classList.remove('active'));
opt.classList.add('active');
view = opt.dataset.view;
detailIndex = 0;
localStorage.setItem('vaultView', view);
updateViewBtn();
render();
playSound('click');
};
document.addEventListener('click', () => vMenu.classList.add('hidden'));
const clearBtn = document.getElementById('clearSearchBtn'); const clearBtn = document.getElementById('clearSearchBtn');
if (clearBtn) clearBtn.style.display = 'none'; if (clearBtn) clearBtn.style.display = 'none';
document.getElementById('addModal').addEventListener('click', e => { document.getElementById('addModal').addEventListener('click', e => {
@@ -646,9 +658,9 @@ async function loadEntries(q) {
} catch (e) { document.getElementById('connectionStatus').textContent = '🔴 Error'; toast('Connection error', 'error'); } } catch (e) { document.getElementById('connectionStatus').textContent = '🔴 Error'; toast('Connection error', 'error'); }
} }
function getFilteredEntries() { function getFilteredEntries(noFolder) {
if (showTrash) return entries; if (showTrash) return entries;
if (selectedFolder === 'All') return entries; if (noFolder || selectedFolder === 'All') return entries;
return entries.filter(e => (e.folder || 'All') === selectedFolder); return entries.filter(e => (e.folder || 'All') === selectedFolder);
} }
@@ -688,9 +700,13 @@ function render() {
}); });
h += '</tbody></table>'; h += '</tbody></table>';
c.innerHTML = h; c.innerHTML = h;
} else if (view === 'grouped') {
c.innerHTML = groupedC(getFilteredEntries(true));
} else if (view === 'detail') {
c.innerHTML = detailC(getFilteredEntries(true));
} else { } else {
c.innerHTML = filtered.map(e => { c.innerHTML = filtered.map(e => {
if (view === 'grid') return gridC(e); if (view === 'grid' || view === 'card') return gridC(e);
if (view === 'compact') return compC(e); if (view === 'compact') return compC(e);
return listC(e); return listC(e);
}).join(''); }).join('');
@@ -775,6 +791,85 @@ function compC(e) {
return html; return html;
} }
function groupedC(list) {
const groups = {};
list.forEach(e => {
const f = e.folder || 'All';
if (!groups[f]) groups[f] = [];
groups[f].push(e);
});
let html = '';
for (const [folder, items] of Object.entries(groups)) {
html += '<div class="grouped-header">📁 ' + esc(folder) + ' <span class="folder-count">' + items.length + '</span></div>';
items.forEach(e => {
const sel = selectedIds.has(e.id);
html += '<div class="entry-row' + (e.favorite ? ' favorite' : '') + (sel ? ' selected' : '') + '" draggable="true" data-id="' + e.id + '" onclick="toggleSelectEntry(' + e.id + ', event)">';
html += '<div class="action-btns">';
if (showTrash) {
html += '<button class="restore-btn" onclick="restoreEntry(' + e.id + ')">↩️</button>';
html += '<button class="delete-btn" data-id="' + e.id + '">✕</button>';
} else {
html += '<button class="star-btn" data-id="' + e.id + '">' + (e.favorite ? '⭐' : '☆') + '</button>';
html += '<button class="edit-btn" data-id="' + e.id + '">✏️</button>';
html += '<button class="delete-btn" data-id="' + e.id + '">✕</button>';
}
html += '</div>';
html += '<div class="entry-info"><span class="entry-site">🌐 ' + highlightText(e.site, searchQuery) + '</span>';
if (showMail) html += '<span class="entry-user">👤 ' + highlightText(e.username, searchQuery) + '</span>';
if (!showTrash) {
html += '<div class="password-field"><span class="password-text pw-display' + (showView ? ' pw-hover' : '') + '" id="p-' + e.id + '">••••••••</span>' +
'<button class="icon-btn copy-p" data-id="' + e.id + '">📋</button></div>';
} else {
html += '<span class="trash-badge">🗑️ ' + timeAgo(e.deleted_at) + '</span>';
}
html += '</div></div>';
});
}
return html;
}
function detailC(list) {
if (detailIndex >= list.length) detailIndex = 0;
if (detailIndex < 0) detailIndex = list.length - 1;
const e = list[detailIndex];
const hasPrev = detailIndex > 0, hasNext = detailIndex < list.length - 1;
const sel = selectedIds.has(e.id);
let html = '<div class="detail-nav">';
html += '<button onclick="goDetail(-1)"' + (hasPrev ? '' : ' disabled') + '>◀ Prev</button>';
html += '<span class="detail-counter">' + (detailIndex + 1) + ' of ' + list.length + '</span>';
html += '<button onclick="goDetail(1)"' + (hasNext ? '' : ' disabled') + '>Next ▶</button>';
html += '</div>';
html += '<div class="detail-card' + (e.favorite ? ' favorite' : '') + '">';
html += '<div class="detail-field"><span class="detail-label">Site</span><span class="detail-value">🌐 ' + highlightText(e.site, searchQuery) + '</span></div>';
if (showMail) html += '<div class="detail-field"><span class="detail-label">Username</span><span class="detail-value">👤 ' + highlightText(e.username, searchQuery) + '</span></div>';
if (!showTrash) {
html += '<div class="detail-field"><span class="detail-label">Password</span><span class="detail-value pw-display' + (showView ? ' pw-hover' : '') + '" id="p-' + e.id + '">••••••••</span></div>';
html += '<div class="detail-folder"><span class="detail-label">Folder</span><span class="entry-folder">📁 ' + esc(e.folder || 'All') + '</span></div>';
html += '<div class="detail-actions">' +
'<button class="star-btn" data-id="' + e.id + '">' + (e.favorite ? '⭐' : '☆') + '</button>' +
'<button class="icon-btn copy-p" data-id="' + e.id + '">📋</button>' +
'<button class="edit-btn" data-id="' + e.id + '">✏️</button>' +
'<button class="delete-btn" data-id="' + e.id + '">✕</button>' +
'</div>';
} else {
html += '<div class="detail-field"><span class="detail-label">Deleted</span><span class="trash-badge">🗑️ ' + timeAgo(e.deleted_at) + '</span></div>';
html += '<div class="detail-actions">' +
'<button class="restore-btn" onclick="restoreEntry(' + e.id + ')">↩️ Restore</button>' +
'<button class="delete-btn" data-id="' + e.id + '">✕ Delete</button>' +
'</div>';
}
html += '</div>';
return html;
}
function goDetail(dir) {
const list = getFilteredEntries(true);
detailIndex += dir;
if (detailIndex < 0) detailIndex = list.length - 1;
if (detailIndex >= list.length) detailIndex = 0;
render();
}
// ==================== EVENTS ==================== // ==================== EVENTS ====================
function showConfirm(btn, message, callback) { function showConfirm(btn, message, callback) {
const id = btn.dataset.id; const id = btn.dataset.id;
@@ -1090,7 +1185,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>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+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(); });
} }
@@ -1149,7 +1244,7 @@ document.getElementById('entriesContainer').addEventListener('dragstart', functi
// Document mousedown: rect selection on vault background, clear outside vault // Document mousedown: rect selection on vault background, clear outside vault
document.addEventListener('mousedown', function(e) { document.addEventListener('mousedown', function(e) {
if (e.button !== 0 || rectState.active) return; if (e.button !== 0 || rectState.active) return;
if (e.target?.closest?.('.entry-card,.entry-row,.entry-compact,.table-row-drag,#batchBar,.custom-modal-overlay.show,.edit-modal.show,.modal-overlay.show,#genModal,#settingsMenu')) return; if (e.target?.closest?.('.entry-card,.entry-row,.entry-compact,.table-row-drag,.detail-card,.detail-nav,#batchBar,.custom-modal-overlay.show,.edit-modal.show,.modal-overlay.show,#genModal,#settingsMenu')) return;
if (e.target?.closest?.('button,input,select,.folders-bar,.toolbar,#trashActions,.settings-dropdown,.fab,.view-toggle,.auth-section')) { if (e.target?.closest?.('button,input,select,.folders-bar,.toolbar,#trashActions,.settings-dropdown,.fab,.view-toggle,.auth-section')) {
if (selectedIds.size > 0) clearSelection(); if (selectedIds.size > 0) clearSelection();
return; return;
@@ -1237,6 +1332,13 @@ document.addEventListener('keydown', function(e) {
return; return;
} }
// Arrow keys for detail view navigation
if ((e.key === 'ArrowLeft' || e.key === 'ArrowRight') && !isInput && view === 'detail' && !document.getElementById('authSection').classList.contains('hidden')) {
e.preventDefault();
goDetail(e.key === 'ArrowLeft' ? -1 : 1);
return;
}
// ? or / to show shortcuts help (only in vault) // ? or / to show shortcuts help (only in vault)
if ((e.key === '?' || e.key === '/') && !isInput) { if ((e.key === '?' || e.key === '/') && !isInput) {
if (!document.getElementById('authSection').classList.contains('hidden')) return; if (!document.getElementById('authSection').classList.contains('hidden')) return;