04/17/2026 update add item modal disappears when lose focus on it
This commit is contained in:
+27
-27
@@ -187,11 +187,11 @@ const Vault = (() => {
|
|||||||
function getSortedItems(items) {
|
function getSortedItems(items) {
|
||||||
const sorted = [...items];
|
const sorted = [...items];
|
||||||
switch (_sortOrder) {
|
switch (_sortOrder) {
|
||||||
case 'name-asc': sorted.sort((a,b) => 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 '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-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 '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 'folder': sorted.sort((a, b) => folderName(a.folder_id).localeCompare(folderName(b.folder_id)) || a.name.localeCompare(b.name)); break;
|
||||||
}
|
}
|
||||||
return sorted;
|
return sorted;
|
||||||
}
|
}
|
||||||
@@ -238,7 +238,7 @@ const Vault = (() => {
|
|||||||
li.className = 'vault-item';
|
li.className = 'vault-item';
|
||||||
li.dataset.id = item.id;
|
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] || '🔑';
|
const icon = iconMap[item.item_type] || '🔑';
|
||||||
|
|
||||||
let subText = '';
|
let subText = '';
|
||||||
@@ -246,7 +246,7 @@ const Vault = (() => {
|
|||||||
switch (item.item_type) {
|
switch (item.item_type) {
|
||||||
case 'password': subText = item.plain.username || item.plain.url || ''; break;
|
case 'password': subText = item.plain.username || item.plain.url || ''; break;
|
||||||
case 'note': subText = (item.plain.note_body || '').slice(0, 60); 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 '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 'bank': subText = item.plain.bank_name || ''; break;
|
||||||
case 'address': subText = [item.plain.first_name, item.plain.last_name].filter(Boolean).join(' '); break;
|
case 'address': subText = [item.plain.first_name, item.plain.last_name].filter(Boolean).join(' '); break;
|
||||||
case 'ssn': subText = '•••-••-••••'; break;
|
case 'ssn': subText = '•••-••-••••'; break;
|
||||||
@@ -254,7 +254,7 @@ const Vault = (() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const showCopyUser = item.item_type === 'password';
|
const showCopyUser = item.item_type === 'password';
|
||||||
const showCopyPass = ['password','card','bank','ssn'].includes(item.item_type);
|
const showCopyPass = ['password', 'card', 'bank', 'ssn'].includes(item.item_type);
|
||||||
const showLaunch = item.item_type === 'password' && item.plain?.url;
|
const showLaunch = item.item_type === 'password' && item.plain?.url;
|
||||||
|
|
||||||
li.innerHTML = `
|
li.innerHTML = `
|
||||||
@@ -328,7 +328,7 @@ const Vault = (() => {
|
|||||||
const old = pwItems.filter(i => new Date(i.created_at).getTime() < cutoff);
|
const old = pwItems.filter(i => new Date(i.created_at).getTime() < cutoff);
|
||||||
|
|
||||||
const total = pwItems.length;
|
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 cls = score >= 80 ? 'score-good' : score >= 50 ? 'score-fair' : 'score-poor';
|
||||||
const label = score >= 80 ? 'Good' : score >= 50 ? 'Fair' : 'Needs attention';
|
const label = score >= 80 ? 'Good' : score >= 50 ? 'Fair' : 'Needs attention';
|
||||||
|
|
||||||
@@ -912,7 +912,7 @@ const Vault = (() => {
|
|||||||
// clamp to 5 bands
|
// clamp to 5 bands
|
||||||
const band = Math.min(5, Math.ceil(score / 7 * 5));
|
const band = Math.min(5, Math.ceil(score / 7 * 5));
|
||||||
const labels = ['', 'Very Weak', 'Weak', 'Fair', 'Strong', 'Very Strong'];
|
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] };
|
return { score: band, label: labels[band], cls: clses[band] };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1017,7 +1017,7 @@ const Vault = (() => {
|
|||||||
lengthDisplay.textContent = lengthSlider.value;
|
lengthDisplay.textContent = lengthSlider.value;
|
||||||
refreshGen();
|
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(id).addEventListener('change', refreshGen);
|
||||||
});
|
});
|
||||||
document.getElementById('btn-regenerate').addEventListener('click', refreshGen);
|
document.getElementById('btn-regenerate').addEventListener('click', refreshGen);
|
||||||
@@ -1269,7 +1269,7 @@ const Vault = (() => {
|
|||||||
|
|
||||||
// ── Item Modal ────────────────────────────────────────────────────────────
|
// ── 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) {
|
function switchModalType(type) {
|
||||||
document.querySelectorAll('.type-fields').forEach(el => {
|
document.querySelectorAll('.type-fields').forEach(el => {
|
||||||
@@ -1307,12 +1307,12 @@ const Vault = (() => {
|
|||||||
if (item.plain) {
|
if (item.plain) {
|
||||||
const p = item.plain;
|
const p = item.plain;
|
||||||
switch (t) {
|
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 '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 '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 '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 '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 '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 'ssn': setVal('field-ssn-number', p.ssn_number); setVal('field-notes', p.notes); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1395,7 +1395,7 @@ const Vault = (() => {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Authorization': `Bearer ${sessionStorage.getItem('access_token') || ''}`, 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken() },
|
headers: { 'Authorization': `Bearer ${sessionStorage.getItem('access_token') || ''}`, 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken() },
|
||||||
body: JSON.stringify({ refresh_token: refreshToken }),
|
body: JSON.stringify({ refresh_token: refreshToken }),
|
||||||
}).catch(() => {});
|
}).catch(() => { });
|
||||||
VaultSession.clear();
|
VaultSession.clear();
|
||||||
SharingSession.clear();
|
SharingSession.clear();
|
||||||
sessionStorage.removeItem('access_token');
|
sessionStorage.removeItem('access_token');
|
||||||
@@ -1407,15 +1407,15 @@ const Vault = (() => {
|
|||||||
// ── Utilities ─────────────────────────────────────────────────────────────
|
// ── Utilities ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function copyToClipboard(text, msg = 'Copied') {
|
function copyToClipboard(text, msg = 'Copied') {
|
||||||
navigator.clipboard.writeText(text).then(() => showToast(msg)).catch(() => {});
|
navigator.clipboard.writeText(text).then(() => showToast(msg)).catch(() => { });
|
||||||
}
|
}
|
||||||
|
|
||||||
function escHtml(str) {
|
function escHtml(str) {
|
||||||
return String(str).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||||
}
|
}
|
||||||
|
|
||||||
function itemIcon(type) {
|
function itemIcon(type) {
|
||||||
return { password:'🔑', note:'📝', card:'💳', bank:'🏦', address:'🏠', ssn:'🪪' }[type] || '🔑';
|
return { password: '🔑', note: '📝', card: '💳', bank: '🏦', address: '🏠', ssn: '🪪' }[type] || '🔑';
|
||||||
}
|
}
|
||||||
|
|
||||||
function showToast(message, type = 'success') {
|
function showToast(message, type = 'success') {
|
||||||
@@ -1524,7 +1524,7 @@ const Vault = (() => {
|
|||||||
document.querySelectorAll('[data-type-filter]').forEach(el => {
|
document.querySelectorAll('[data-type-filter]').forEach(el => {
|
||||||
el.addEventListener('click', () => {
|
el.addEventListener('click', () => {
|
||||||
const type = el.dataset.typeFilter;
|
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 };
|
_activeFilter = { type: 'itemType', value: type };
|
||||||
switchView('vault');
|
switchView('vault');
|
||||||
document.querySelectorAll('.sidebar-item').forEach(s => s.classList.remove('active'));
|
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.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(); closeModal('share-modal'); closeModal('emergency-modal'); closeModal('settings-modal'); closeModal('detail-modal'); closeModal('gen-modal'); } });
|
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)
|
// Detail modal (shared item view)
|
||||||
const closeDetailModal = () => closeModal('detail-modal');
|
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('detail-modal')?.addEventListener('click', e => { if (e.target.id === 'detail-modal') closeDetailModal(); });
|
||||||
|
|
||||||
document.getElementById('field-card-number')?.addEventListener('input', e => {
|
document.getElementById('field-card-number')?.addEventListener('input', e => {
|
||||||
let v = e.target.value.replace(/\D/g,'').slice(0,16);
|
let v = e.target.value.replace(/\D/g, '').slice(0, 16);
|
||||||
e.target.value = v.replace(/(.{4})/g,'$1 ').trim();
|
e.target.value = v.replace(/(.{4})/g, '$1 ').trim();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Settings modal
|
// Settings modal
|
||||||
|
|||||||
Reference in New Issue
Block a user