diff --git a/app/static/js/vault.js b/app/static/js/vault.js index 85edd6e..9561861 100644 --- a/app/static/js/vault.js +++ b/app/static/js/vault.js @@ -187,11 +187,11 @@ const Vault = (() => { function getSortedItems(items) { const sorted = [...items]; switch (_sortOrder) { - case 'name-asc': sorted.sort((a,b) => a.name.localeCompare(b.name)); break; - case 'name-desc': sorted.sort((a,b) => b.name.localeCompare(a.name)); break; - case 'date-desc': sorted.sort((a,b) => new Date(b.created_at) - new Date(a.created_at)); break; - case 'date-asc': sorted.sort((a,b) => new Date(a.created_at) - new Date(b.created_at)); break; - case 'folder': sorted.sort((a,b) => folderName(a.folder_id).localeCompare(folderName(b.folder_id)) || a.name.localeCompare(b.name)); break; + case 'name-asc': sorted.sort((a, b) => a.name.localeCompare(b.name)); break; + case 'name-desc': sorted.sort((a, b) => b.name.localeCompare(a.name)); break; + case 'date-desc': sorted.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); break; + case 'date-asc': sorted.sort((a, b) => new Date(a.created_at) - new Date(b.created_at)); break; + case 'folder': sorted.sort((a, b) => folderName(a.folder_id).localeCompare(folderName(b.folder_id)) || a.name.localeCompare(b.name)); break; } return sorted; } @@ -238,24 +238,24 @@ const Vault = (() => { li.className = 'vault-item'; li.dataset.id = item.id; - const iconMap = { password:'🔑', note:'📝', card:'💳', bank:'🏦', address:'🏠', ssn:'🪪', passkey:'🔐' }; + const iconMap = { password: '🔑', note: '📝', card: '💳', bank: '🏦', address: '🏠', ssn: '🪪', passkey: '🔐' }; const icon = iconMap[item.item_type] || '🔑'; let subText = ''; if (item.plain) { switch (item.item_type) { case 'password': subText = item.plain.username || item.plain.url || ''; break; - case 'note': subText = (item.plain.note_body || '').slice(0, 60); break; - case 'card': subText = item.plain.card_number ? '•••• ' + String(item.plain.card_number).replace(/\s/g,'').slice(-4) : ''; break; - case 'bank': subText = item.plain.bank_name || ''; break; - case 'address': subText = [item.plain.first_name, item.plain.last_name].filter(Boolean).join(' '); break; - case 'ssn': subText = '•••-••-••••'; break; + case 'note': subText = (item.plain.note_body || '').slice(0, 60); break; + case 'card': subText = item.plain.card_number ? '•••• ' + String(item.plain.card_number).replace(/\s/g, '').slice(-4) : ''; break; + case 'bank': subText = item.plain.bank_name || ''; break; + case 'address': subText = [item.plain.first_name, item.plain.last_name].filter(Boolean).join(' '); break; + case 'ssn': subText = '•••-••-••••'; break; } } const showCopyUser = item.item_type === 'password'; - const showCopyPass = ['password','card','bank','ssn'].includes(item.item_type); - const showLaunch = item.item_type === 'password' && item.plain?.url; + const showCopyPass = ['password', 'card', 'bank', 'ssn'].includes(item.item_type); + const showLaunch = item.item_type === 'password' && item.plain?.url; li.innerHTML = `
${icon}
@@ -264,7 +264,7 @@ const Vault = (() => { ${escHtml(subText)}
- ${showLaunch ? `` : ''} + ${showLaunch ? `` : ''} ${showCopyUser ? `` : ''} ${showCopyPass ? `` : ''} @@ -289,8 +289,8 @@ const Vault = (() => { e.stopPropagation(); const s = item.item_type === 'password' ? item.plain?.password : item.item_type === 'card' ? item.plain?.cvv - : item.item_type === 'bank' ? item.plain?.account_number - : item.plain?.ssn_number; + : item.item_type === 'bank' ? item.plain?.account_number + : item.plain?.ssn_number; copyToClipboard(s || '', 'Copied to clipboard'); }); } @@ -328,7 +328,7 @@ const Vault = (() => { const old = pwItems.filter(i => new Date(i.created_at).getTime() < cutoff); const total = pwItems.length; - const score = Math.max(0, Math.round(100 - (weak.length/total)*40 - (reused.length/total)*30 - (old.length/total)*15)); + const score = Math.max(0, Math.round(100 - (weak.length / total) * 40 - (reused.length / total) * 30 - (old.length / total) * 15)); const cls = score >= 80 ? 'score-good' : score >= 50 ? 'score-fair' : 'score-poor'; const label = score >= 80 ? 'Good' : score >= 50 ? 'Fair' : 'Needs attention'; @@ -473,9 +473,9 @@ const Vault = (() => { From ${escHtml(s.owner_email)}
${!s.accepted - ? `` - : `` - } + ? `` + : `` + } `).join(''); ul.querySelectorAll('[data-accept]').forEach(btn => { @@ -836,8 +836,8 @@ const Vault = (() => { `; }).join(''); @@ -866,18 +866,18 @@ const Vault = (() => { // ── Password Generator ──────────────────────────────────────────────── const GEN_CHARSETS = { - upper: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', - lower: 'abcdefghijklmnopqrstuvwxyz', - digits: '0123456789', + upper: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + lower: 'abcdefghijklmnopqrstuvwxyz', + digits: '0123456789', symbols: '!@#$%^&*()-_=+[]{}|;:,.<>?', }; function generatePassword(length = 20, opts = { upper: true, lower: true, digits: true, symbols: true }) { let pool = ''; const required = []; - if (opts.upper) { pool += GEN_CHARSETS.upper; required.push(GEN_CHARSETS.upper); } - if (opts.lower) { pool += GEN_CHARSETS.lower; required.push(GEN_CHARSETS.lower); } - if (opts.digits) { pool += GEN_CHARSETS.digits; required.push(GEN_CHARSETS.digits); } + if (opts.upper) { pool += GEN_CHARSETS.upper; required.push(GEN_CHARSETS.upper); } + if (opts.lower) { pool += GEN_CHARSETS.lower; required.push(GEN_CHARSETS.lower); } + if (opts.digits) { pool += GEN_CHARSETS.digits; required.push(GEN_CHARSETS.digits); } if (opts.symbols) { pool += GEN_CHARSETS.symbols; required.push(GEN_CHARSETS.symbols); } if (!pool) pool = GEN_CHARSETS.lower; @@ -902,7 +902,7 @@ const Vault = (() => { function scorePassword(pw) { if (!pw) return { score: 0, label: '', cls: '' }; let score = 0; - if (pw.length >= 8) score++; + if (pw.length >= 8) score++; if (pw.length >= 12) score++; if (pw.length >= 16) score++; if (/[A-Z]/.test(pw)) score++; @@ -912,7 +912,7 @@ const Vault = (() => { // clamp to 5 bands const band = Math.min(5, Math.ceil(score / 7 * 5)); const labels = ['', 'Very Weak', 'Weak', 'Fair', 'Strong', 'Very Strong']; - const clses = ['', 'strength-1','strength-2','strength-3','strength-4','strength-5']; + const clses = ['', 'strength-1', 'strength-2', 'strength-3', 'strength-4', 'strength-5']; return { score: band, label: labels[band], cls: clses[band] }; } @@ -995,16 +995,16 @@ const Vault = (() => { `; document.body.appendChild(modal); - const lengthSlider = document.getElementById('gen-length'); + const lengthSlider = document.getElementById('gen-length'); const lengthDisplay = document.getElementById('gen-length-display'); - const output = document.getElementById('gen-output'); - const strengthEl = document.getElementById('gen-strength'); + const output = document.getElementById('gen-output'); + const strengthEl = document.getElementById('gen-strength'); function refreshGen() { const pw = generatePassword(parseInt(lengthSlider.value), { - upper: document.getElementById('gen-upper').checked, - lower: document.getElementById('gen-lower').checked, - digits: document.getElementById('gen-digits').checked, + upper: document.getElementById('gen-upper').checked, + lower: document.getElementById('gen-lower').checked, + digits: document.getElementById('gen-digits').checked, symbols: document.getElementById('gen-symbols').checked, }); output.value = pw; @@ -1017,7 +1017,7 @@ const Vault = (() => { lengthDisplay.textContent = lengthSlider.value; refreshGen(); }); - ['gen-upper','gen-lower','gen-digits','gen-symbols'].forEach(id => { + ['gen-upper', 'gen-lower', 'gen-digits', 'gen-symbols'].forEach(id => { document.getElementById(id).addEventListener('change', refreshGen); }); document.getElementById('btn-regenerate').addEventListener('click', refreshGen); @@ -1036,9 +1036,9 @@ const Vault = (() => { const lengthSlider = document.getElementById('gen-length'); document.getElementById('gen-length-display').textContent = lengthSlider.value; const pw = generatePassword(parseInt(lengthSlider.value), { - upper: document.getElementById('gen-upper').checked, - lower: document.getElementById('gen-lower').checked, - digits: document.getElementById('gen-digits').checked, + upper: document.getElementById('gen-upper').checked, + lower: document.getElementById('gen-lower').checked, + digits: document.getElementById('gen-digits').checked, symbols: document.getElementById('gen-symbols').checked, }); document.getElementById('gen-output').value = pw; @@ -1269,7 +1269,7 @@ const Vault = (() => { // ── Item Modal ──────────────────────────────────────────────────────────── - const TYPE_LABELS = { password:'🔑 Password', note:'📝 Secure Note', card:'💳 Payment Card', bank:'🏦 Bank Account', address:'🏠 Address', ssn:'🪪 Identity (SSN)' }; + const TYPE_LABELS = { password: '🔑 Password', note: '📝 Secure Note', card: '💳 Payment Card', bank: '🏦 Bank Account', address: '🏠 Address', ssn: '🪪 Identity (SSN)' }; function switchModalType(type) { document.querySelectorAll('.type-fields').forEach(el => { @@ -1307,19 +1307,19 @@ const Vault = (() => { if (item.plain) { const p = item.plain; switch (t) { - case 'password': setVal('field-url',p.url); setVal('field-username',p.username); setVal('field-password',p.password); setVal('field-notes',p.notes); break; - case 'note': setVal('field-note-body',p.note_body); break; - case 'card': setVal('field-cardholder',p.cardholder_name); setVal('field-card-number',p.card_number); setVal('field-expiry-month',p.expiry_month); setVal('field-expiry-year',p.expiry_year); setVal('field-cvv',p.cvv); setVal('field-notes',p.notes); break; - case 'bank': setVal('field-bank-name',p.bank_name); setVal('field-account-type',p.account_type); setVal('field-routing',p.routing_number); setVal('field-account-number',p.account_number); setVal('field-notes',p.notes); break; - case 'address': ['first_name','last_name','company','address_line','city','state','zip','country','phone','email'].forEach(k => setVal('field-'+k.replace(/_/g,'-'), p[k])); setVal('field-notes',p.notes); break; - case 'ssn': setVal('field-ssn-number',p.ssn_number); setVal('field-notes',p.notes); break; + case 'password': setVal('field-url', p.url); setVal('field-username', p.username); setVal('field-password', p.password); setVal('field-notes', p.notes); break; + case 'note': setVal('field-note-body', p.note_body); break; + case 'card': setVal('field-cardholder', p.cardholder_name); setVal('field-card-number', p.card_number); setVal('field-expiry-month', p.expiry_month); setVal('field-expiry-year', p.expiry_year); setVal('field-cvv', p.cvv); setVal('field-notes', p.notes); break; + case 'bank': setVal('field-bank-name', p.bank_name); setVal('field-account-type', p.account_type); setVal('field-routing', p.routing_number); setVal('field-account-number', p.account_number); setVal('field-notes', p.notes); break; + case 'address': ['first_name', 'last_name', 'company', 'address_line', 'city', 'state', 'zip', 'country', 'phone', 'email'].forEach(k => setVal('field-' + k.replace(/_/g, '-'), p[k])); setVal('field-notes', p.notes); break; + case 'ssn': setVal('field-ssn-number', p.ssn_number); setVal('field-notes', p.notes); break; } } } document.getElementById('item-modal').classList.add('open'); document.getElementById('field-name').focus(); if ((mode === 'add' && (document.getElementById('field-type').value || 'password') === 'password') || - (mode === 'edit' && (item?.item_type || 'password') === 'password')) { + (mode === 'edit' && (item?.item_type || 'password') === 'password')) { initPasswordFieldEnhancements(); } } @@ -1395,7 +1395,7 @@ const Vault = (() => { method: 'POST', headers: { 'Authorization': `Bearer ${sessionStorage.getItem('access_token') || ''}`, 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken() }, body: JSON.stringify({ refresh_token: refreshToken }), - }).catch(() => {}); + }).catch(() => { }); VaultSession.clear(); SharingSession.clear(); sessionStorage.removeItem('access_token'); @@ -1407,15 +1407,15 @@ const Vault = (() => { // ── Utilities ───────────────────────────────────────────────────────────── function copyToClipboard(text, msg = 'Copied') { - navigator.clipboard.writeText(text).then(() => showToast(msg)).catch(() => {}); + navigator.clipboard.writeText(text).then(() => showToast(msg)).catch(() => { }); } function escHtml(str) { - return String(str).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); + return String(str).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } function itemIcon(type) { - return { password:'🔑', note:'📝', card:'💳', bank:'🏦', address:'🏠', ssn:'🪪' }[type] || '🔑'; + return { password: '🔑', note: '📝', card: '💳', bank: '🏦', address: '🏠', ssn: '🪪' }[type] || '🔑'; } function showToast(message, type = 'success') { @@ -1524,7 +1524,7 @@ const Vault = (() => { document.querySelectorAll('[data-type-filter]').forEach(el => { el.addEventListener('click', () => { const type = el.dataset.typeFilter; - const labels = { password:'Passwords', note:'Secure Notes', card:'Payment Cards', bank:'Bank Accounts', address:'Addresses', ssn:'Identities' }; + const labels = { password: 'Passwords', note: 'Secure Notes', card: 'Payment Cards', bank: 'Bank Accounts', address: 'Addresses', ssn: 'Identities' }; _activeFilter = { type: 'itemType', value: type }; switchView('vault'); document.querySelectorAll('.sidebar-item').forEach(s => s.classList.remove('active')); @@ -1534,8 +1534,8 @@ const Vault = (() => { }); }); - document.getElementById('item-modal')?.addEventListener('click', e => { if (e.target.id === 'item-modal') closeModal(); }); - document.addEventListener('keydown', e => { if (e.key === 'Escape') { closeModal(); closeModal('share-modal'); closeModal('emergency-modal'); closeModal('settings-modal'); closeModal('detail-modal'); closeModal('gen-modal'); } }); + document.getElementById('item-modal')?.addEventListener('click', e => { /* backdrop click intentionally disabled — modal closes only via Cancel or Save */ }); + document.addEventListener('keydown', e => { if (e.key === 'Escape') { closeModal('share-modal'); closeModal('emergency-modal'); closeModal('settings-modal'); closeModal('detail-modal'); closeModal('gen-modal'); } }); // Detail modal (shared item view) const closeDetailModal = () => closeModal('detail-modal'); @@ -1544,8 +1544,8 @@ const Vault = (() => { document.getElementById('detail-modal')?.addEventListener('click', e => { if (e.target.id === 'detail-modal') closeDetailModal(); }); document.getElementById('field-card-number')?.addEventListener('input', e => { - let v = e.target.value.replace(/\D/g,'').slice(0,16); - e.target.value = v.replace(/(.{4})/g,'$1 ').trim(); + let v = e.target.value.replace(/\D/g, '').slice(0, 16); + e.target.value = v.replace(/(.{4})/g, '$1 ').trim(); }); // Settings modal @@ -1606,4 +1606,4 @@ const Vault = (() => { return { init }; })(); -document.addEventListener('DOMContentLoaded', Vault.init); +document.addEventListener('DOMContentLoaded', Vault.init); \ No newline at end of file