π² Generator
Click Generate
diff --git a/js/app.js b/js/app.js
index 05d65e3..69e46bb 100644
--- a/js/app.js
+++ b/js/app.js
@@ -15,6 +15,8 @@ let cryptoKey = null;
let idleT, warnT, countT;
let draggedId = null;
let showTrash = false;
+let selectMode = false;
+let selectedIds = new Set();
// ==================== SOUND ====================
let soundEnabled = localStorage.getItem('soundEnabled') !== 'false';
@@ -125,8 +127,8 @@ function setAutoLock() { lockMin = parseInt(document.getElementById('autoLockTim
function resetIdle() { clearTimeout(idleT); clearTimeout(warnT); clearInterval(countT); document.getElementById('idleWarning').classList.remove('show'); if (lockMin > 0 && token) { const lm = lockMin * 60000; warnT = setTimeout(() => { document.getElementById('idleWarning').classList.add('show'); let cd = 30; document.getElementById('idleCountdown').textContent = cd; countT = setInterval(() => { cd--; document.getElementById('idleCountdown').textContent = cd; if (cd <= 0) { clearInterval(countT); doLogout(); } }, 1000); }, Math.max(0, lm - 30000)); idleT = setTimeout(() => doLogout(), lm); } }
// ==================== USERNAME ====================
-function saveUsername() { const f = document.getElementById('usernameInput'); if (f && f.value.trim()) localStorage.setItem('savedUsername', f.value.trim()); }
-function loadUsername() { const s = localStorage.getItem('savedUsername'); const f = document.getElementById('usernameInput'); if (s && f) f.value = s; }
+function saveUsername() { const f = document.getElementById('addUsername'); if (f && f.value.trim()) localStorage.setItem('savedUsername', f.value.trim()); }
+function loadUsername() { const s = localStorage.getItem('savedUsername'); const f = document.getElementById('addUsername'); if (s && f) f.value = s; }
// ==================== FOLDERS ====================
async function loadFolders() {
@@ -196,25 +198,13 @@ function renderFolders() {
bar.innerHTML = html;
}
-function populateAddFolderSelect() {
- const select = document.getElementById('addFolderSelect');
- if (!select) return;
- select.innerHTML = '';
- folders.forEach(f => {
- if (!f) return;
- const option = document.createElement('option');
- option.value = f;
- option.textContent = 'π ' + f;
- if (f === selectedFolder) option.selected = true;
- select.appendChild(option);
- });
-}
+
function selectFolder(f) {
selectedFolder = f;
localStorage.setItem('selectedFolder', f);
renderFolders();
- populateAddFolderSelect();
+ populateFolderSelects();
render();
playSound('click');
}
@@ -229,7 +219,7 @@ function showAddFolderModal() {
const name = document.getElementById('newFolderName').value.trim();
if (!name) { toast('Enter a name', 'error'); return; }
const ok = await addFolderToServer(name);
- if (ok) { renderFolders(); populateAddFolderSelect(); overlay.remove(); toast('π Folder created!'); playSound('success'); }
+ if (ok) { renderFolders(); populateFolderSelects(); overlay.remove(); toast('π Folder created!'); playSound('success'); }
};
overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.remove(); });
playSound('open');
@@ -243,7 +233,7 @@ function showDeleteFolderConfirm(folderName) {
document.getElementById('cancelDeleteFolder').onclick = () => overlay.remove();
document.getElementById('confirmDeleteFolder').onclick = async () => {
const ok = await deleteFolderFromServer(folderName);
- if (ok) { renderFolders(); populateAddFolderSelect(); render(); overlay.remove(); toast('π Folder deleted'); playSound('delete'); }
+ if (ok) { renderFolders(); populateFolderSelects(); render(); overlay.remove(); toast('π Folder deleted'); playSound('delete'); }
};
overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.remove(); });
}
@@ -293,7 +283,7 @@ function init() {
document.querySelectorAll('.view-btn').forEach(b => b.classList.toggle('active', b.dataset.view === view));
document.getElementById('autoLockTimer').value = lockMin;
applyTheme();
- document.getElementById('usernameInput').style.display = showMail ? '' : 'none';
+ // document.getElementById('usernameInput').style.display = showMail ? '' : 'none';
loadUsername();
const sl = localStorage.getItem('savedLoginUser');
if (sl) document.getElementById('loginUsername').value = sl;
@@ -310,6 +300,9 @@ function init() {
});
const clearBtn = document.getElementById('clearSearchBtn');
if (clearBtn) clearBtn.style.display = 'none';
+ document.getElementById('addModal').addEventListener('click', e => {
+ if (e.target === e.currentTarget) closeAdd();
+ });
// Close settings when clicking outside
document.addEventListener('click', (e) => {
const menu = document.getElementById('settingsMenu');
@@ -321,15 +314,66 @@ function init() {
}
function toggleViewBtn() { showView = !showView; localStorage.setItem('showViewBtn', showView); syncSettingsUI(); render(); playSound('click'); }
-function toggleShowEmail() { showMail = !showMail; localStorage.setItem('showEmail', showMail); syncSettingsUI(); document.getElementById('usernameInput').style.display = showMail ? '' : 'none'; render(); playSound('click'); }
+function toggleShowEmail() { showMail = !showMail; localStorage.setItem('showEmail', showMail); syncSettingsUI(); render(); playSound('click'); }
// ==================== GENERATOR ====================
function openGen() { document.getElementById('genModal').style.display = 'flex'; genPwd(); playSound('open'); }
function closeGen() { document.getElementById('genModal').style.display = 'none'; playSound('close'); }
function onLenChange() { document.getElementById('lenVal').textContent = document.getElementById('pwdLen').value; genPwd(); }
function genPwd() { const l = parseInt(document.getElementById('pwdLen').value); let c = ''; if (document.getElementById('useUpper').checked) c += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; if (document.getElementById('useLower').checked) c += 'abcdefghijklmnopqrstuvwxyz'; if (document.getElementById('useNum').checked) c += '0123456789'; if (document.getElementById('useSym').checked) c += '!@#$%^&*()_+-=[]{}|;:,.<>?'; if (!c) { document.getElementById('genPreview').textContent = 'Select option'; return; } let p = ''; for (let i = 0; i < l; i++) p += c.charAt(Math.floor(Math.random() * c.length)); genPwdVal = p; document.getElementById('genPreview').textContent = p; }
-function useGen() { if (!genPwdVal) genPwd(); document.getElementById('passwordInput').value = genPwdVal; checkStrength(); navigator.clipboard.writeText(genPwdVal); toast('π² Copied!'); playSound('generate'); closeGen(); }
+function useGen() {
+ if (!genPwdVal) genPwd();
+ // Put the generated password into the addβmodalβs password field
+ const pwdField = document.getElementById('addPassword');
+ if (pwdField) {
+ pwdField.value = genPwdVal;
+ checkAddStrength(); // update the strength bar
+ }
+ navigator.clipboard.writeText(genPwdVal);
+ toast('π² Copied!');
+ closeGen(); // closes the generator modal, not the add modal
+}
+function populateFolderSelects() {
+ ['addFolder', 'editFolder'].forEach(id => {
+ const select = document.getElementById(id);
+ if (!select) return;
+ select.innerHTML = '';
+ folders.forEach(f => {
+ if (!f) return;
+ const option = document.createElement('option');
+ option.value = f;
+ option.textContent = 'π ' + f;
+ if (f === selectedFolder) option.selected = true;
+ select.appendChild(option);
+ });
+ });
+}
+function openAdd() {
+ document.getElementById('addSite').value = '';
+ document.getElementById('addPassword').value = '';
+ document.getElementById('addStrengthBar').className = 'strength-bar s0';
+ loadUsername();
+ populateFolderSelects();
+ document.getElementById('addModal').classList.add('show');
+ playSound('open');
+}
+function closeAdd() {
+ document.getElementById('addModal').classList.remove('show');
+ playSound('close');
+}
+
+function checkAddStrength() {
+ const p = document.getElementById('addPassword').value;
+ const bar = document.getElementById('addStrengthBar');
+ let s = 0;
+ if (p.length >= 8) s++;
+ if (p.length >= 12) s++;
+ if (/[A-Z]/.test(p) && /[a-z]/.test(p)) s++;
+ if (/\d/.test(p)) s++;
+ if (/[!@#$%^&*()_+\-=\[\]{}|;:,.<>?]/.test(p)) s++;
+ bar.className = 'strength-bar s' + Math.min(4, s);
+}
// ==================== AUTH ====================
function switchTab(t) { document.querySelectorAll('.auth-tab').forEach(x => x.classList.remove('active')); event.target.classList.add('active'); document.getElementById('loginForm').classList.toggle('hidden', t !== 'login'); document.getElementById('registerForm').classList.toggle('hidden', t !== 'register'); }
@@ -401,11 +445,11 @@ function showVault() {
document.getElementById('authSection').classList.add('hidden');
document.getElementById('vaultSection').classList.remove('hidden');
document.getElementById('currentUser').textContent = 'π€ ' + curUser;
- document.getElementById('usernameInput').style.display = showMail ? '' : 'none';
+ //document.getElementById('usernameInput').style.display = showMail ? '' : 'none';
document.getElementById('autoLockTimer').value = lockMin;
loadUsername();
renderFolders();
- populateAddFolderSelect();
+ populateFolderSelects();
syncSettingsUI();
resetIdle();
}
@@ -433,7 +477,7 @@ async function loadEntries(q) {
document.getElementById('connectionStatus').textContent = 'π’ Connected';
document.getElementById('entryCount').textContent = '(' + entries.length + ' entries)';
renderFolders();
- populateAddFolderSelect();
+ populateFolderSelects();
render();
} else if (r.status === 401) { toast('Session expired', 'error'); doLogout(); }
} catch (e) { document.getElementById('connectionStatus').textContent = 'π΄ Error'; toast('Connection error', 'error'); }
@@ -461,20 +505,22 @@ function render() {
(showMail ? '
π€ ' + esc(e.username) + ' | ' : '') +
'
β’β’β’β’β’β’β’β’ | ';
if (!showTrash) {
- h += '
π ' + esc(e.folder || 'All') + ' | ' +
- '
' +
- (showView ? ' ' : '') +
- ' ' +
- ' ' +
- '' +
- ' | ';
- } else {
- h += '
ποΈ ' + timeAgo(e.deleted_at) + ' | ' +
- '
' +
- ' ' +
- '' +
- ' | ';
- }
+ h += '
π ' + esc(e.folder || 'All') + ' | ' +
+ '
' +
+ (selectMode ? `` : '') +
+ (showView ? ' ' : '') +
+ ' ' +
+ ' ' +
+ '' +
+ ' | ';
+ } else {
+ h += '
ποΈ ' + timeAgo(e.deleted_at) + ' | ' +
+ '
' +
+ (selectMode ? `` : '') +
+ ' ' +
+ '' +
+ ' | ';
+ }
h += '';
});
h += '';
@@ -491,16 +537,85 @@ function render() {
}
function gridC(e) {
- if (showTrash) return '
π ' + esc(e.site) + '
' + (showMail ? '
π€ ' + esc(e.username) + '
' : '') + '
ποΈ ' + timeAgo(e.deleted_at) + '
';
- return '
π ' + esc(e.site) + '
' + (showMail ? '
π€ ' + esc(e.username) + '
' : '') + '
π ' + esc(e.folder || 'All') + '
β’β’β’β’β’β’β’β’' + (showView ? '' : '') + '
';
+ let html = '
';
+ if (selectMode) {
+ html += `
`;
+ }
+ html += '
';
+ if (showTrash) {
+ html += '';
+ html += '';
+ } else {
+ html += '';
+ html += '';
+ }
+ html += '
';
+ html += '
π ' + esc(e.site) + '
';
+ if (showMail) html += '
π€ ' + esc(e.username) + '
';
+ html += '
π ' + esc(e.folder || 'All') + '
';
+ if (!showTrash) {
+ html += '
β’β’β’β’β’β’β’β’' +
+ (showView ? '' : '') +
+ '
';
+ } else {
+ html += '
ποΈ ' + timeAgo(e.deleted_at) + '
';
+ }
+ html += '
';
+ return html;
}
function listC(e) {
- if (showTrash) return '
π ' + esc(e.site) + '' + (showMail ? 'π€ ' + esc(e.username) + '' : '') + 'ποΈ ' + timeAgo(e.deleted_at) + '
';
- return '
π ' + esc(e.site) + '' + (showMail ? '
π€ ' + esc(e.username) + '' : '') + '
π ' + esc(e.folder || 'All') + 'β’β’β’β’β’β’β’β’' + (showView ? '' : '') + '
';
+ let html = '
';
+ if (selectMode) {
+ html += `
`;
+ }
+ html += '
';
+ if (showTrash) {
+ html += '';
+ html += '';
+ } else {
+ html += '';
+ html += '';
+ }
+ html += '
';
+ html += '
π ' + esc(e.site) + '';
+ if (showMail) html += '
π€ ' + esc(e.username) + '';
+ if (!showTrash) {
+ html += '
π ' + esc(e.folder || 'All') + '';
+ html += '
β’β’β’β’β’β’β’β’' +
+ (showView ? '' : '') +
+ '
';
+ } else {
+ html += '
ποΈ ' + timeAgo(e.deleted_at) + '';
+ }
+ html += '
';
+ return html;
}
function compC(e) {
- if (showTrash) return '
π ' + esc(e.site) + '' + (showMail ? '
π€ ' + esc(e.username) + '' : '') + '
ποΈ ' + timeAgo(e.deleted_at) + ' ';
- return '
π ' + esc(e.site) + '' + (showMail ? '
π€ ' + esc(e.username) + '' : '') + '
π ' + esc(e.folder || 'All') + 'β’β’β’β’β’β’β’β’' + (showView ? '
' : '') + '
';
+ let html = '
';
+ if (selectMode) {
+ html += `
`;
+ }
+ html += '
';
+ if (showTrash) {
+ html += '';
+ html += '';
+ } else {
+ html += '';
+ html += '';
+ }
+ html += '
';
+ html += '
π ' + esc(e.site) + '';
+ if (showMail) html += '
π€ ' + esc(e.username) + '';
+ if (!showTrash) {
+ html += '
π ' + esc(e.folder || 'All') + '';
+ html += '
β’β’β’β’β’β’β’β’';
+ if (showView) html += '
';
+ html += '
';
+ } else {
+ html += '
ποΈ ' + timeAgo(e.deleted_at) + '';
+ }
+ html += '
';
+ return html;
}
// ==================== EVENTS ====================
@@ -508,18 +623,66 @@ function showConfirm(btn, message, callback) {
const id = btn.dataset.id;
const existing = document.querySelector('.custom-confirm');
if (existing) existing.remove();
+
const confirm = document.createElement('div');
confirm.className = 'custom-confirm show';
- confirm.innerHTML = '
' + message + '
';
+ confirm.innerHTML =
+ '
' + message + '
' +
+ '
' +
+ '' +
+ '' +
+ '
';
document.body.appendChild(confirm);
+
const rect = btn.getBoundingClientRect();
confirm.style.top = (rect.top - 60) + 'px';
let leftPos = rect.left - confirm.offsetWidth + rect.width;
if (leftPos < 10) leftPos = 10;
confirm.style.left = leftPos + 'px';
- confirm.querySelector('.confirm-yes').onclick = () => { confirm.remove(); callback(id); showZigzagToast(btn, 'ποΈ Deleted!', 'error'); playSound('delete'); };
- confirm.querySelector('.confirm-no').onclick = () => confirm.remove();
- setTimeout(() => { document.addEventListener('click', function closeConfirm(e) { if (!confirm.contains(e.target) && e.target !== btn) { confirm.remove(); document.removeEventListener('click', closeConfirm); } }); }, 10);
+
+ const yesBtn = confirm.querySelector('.confirm-yes');
+ const noBtn = confirm.querySelector('.confirm-no');
+
+ const cleanup = () => {
+ confirm.remove();
+ document.removeEventListener('keydown', keyHandler);
+ };
+
+ const keyHandler = (e) => {
+ if (e.key === 'Enter' || e.key === 'y' || e.key === 'Y') {
+ e.preventDefault();
+ cleanup();
+ callback(id);
+ showZigzagToast(btn, 'ποΈ Deleted!', 'error');
+ playSound('delete');
+ } else if (e.key === 'Escape' || e.key === 'n' || e.key === 'N') {
+ e.preventDefault();
+ cleanup();
+ }
+ };
+
+ yesBtn.onclick = () => {
+ cleanup();
+ callback(id);
+ showZigzagToast(btn, 'ποΈ Deleted!', 'error');
+ playSound('delete');
+ };
+ noBtn.onclick = () => cleanup();
+
+ // Focus the confirm box so keyboard events are captured
+ confirm.tabIndex = 0;
+ confirm.focus();
+ document.addEventListener('keydown', keyHandler);
+
+ // Close if clicking outside
+ setTimeout(() => {
+ document.addEventListener('click', function closeConfirm(e) {
+ if (!confirm.contains(e.target) && e.target !== btn) {
+ cleanup();
+ document.removeEventListener('click', closeConfirm);
+ }
+ });
+ }, 10);
}
function attachEvents() {
document.querySelectorAll('.delete-btn').forEach(b => b.onclick = function(ev) { ev.stopPropagation(); if (showTrash) { permanentDelete(this.dataset.id); } else { showConfirm(this, 'Delete this entry?', id => delEntry(id)); } });
@@ -577,23 +740,128 @@ async function saveEdit() {
else { const d = await r.json(); toast('β ' + (d.error || 'Failed'), 'error'); }
} catch (e) { toast('β οΈ Error', 'error'); }
}
+//======================== batch operations ==========================
+function toggleSelectMode() {
+ selectMode = !selectMode;
+ document.getElementById('selectBtn').textContent = selectMode ? 'β Deselect' : 'β Select';
+ document.getElementById('selectBtn').classList.toggle('active', selectMode);
+ if (!selectMode) {
+ selectedIds.clear();
+ hideBatchBar();
+ }
+ render();
+}
+function toggleSelectEntry(id) {
+ if (selectedIds.has(id)) {
+ selectedIds.delete(id);
+ } else {
+ selectedIds.add(id);
+ }
+ updateBatchBar();
+}
+
+function updateBatchBar() {
+ const existing = document.getElementById('batchBar');
+ if (existing) existing.remove();
+ if (selectedIds.size === 0) return;
+
+ const bar = document.createElement('div');
+ bar.id = 'batchBar';
+ bar.className = 'batch-actions';
+ bar.innerHTML = `
${selectedIds.size} selected`;
+
+ if (showTrash) {
+ bar.innerHTML += `
+
+
+ `;
+ } else {
+ bar.innerHTML += `
+
+
+
+ `;
+ }
+ bar.innerHTML += `
`;
+ document.body.appendChild(bar);
+}
+
+function hideBatchBar() {
+ const bar = document.getElementById('batchBar');
+ if (bar) bar.remove();
+}
+
+async function batchDelete() {
+ if (!confirm(`Move ${selectedIds.size} entries to trash?`)) return;
+ for (const id of selectedIds) await delEntry(id);
+ selectedIds.clear();
+ hideBatchBar();
+ loadEntries();
+}
+
+async function batchPermanentDelete() {
+ if (!confirm(`Permanently delete ${selectedIds.size} entries?`)) return;
+ for (const id of selectedIds) await permanentDelete(id);
+ selectedIds.clear();
+ hideBatchBar();
+ loadEntries();
+}
+
+async function batchRestore() {
+ for (const id of selectedIds) await restoreEntry(id);
+ selectedIds.clear();
+ hideBatchBar();
+ loadEntries();
+}
+
+async function batchMove() {
+ const folder = document.getElementById('batchFolder')?.value || 'All';
+ for (const id of selectedIds) {
+ const e = entries.find(x => x.id == id);
+ if (e) {
+ e.folder = folder;
+ await fetch(API + '/entries/' + id, {
+ method: 'PUT',
+ headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
+ body: JSON.stringify({ site: e.site, username: e.username, encrypted_password: await encryptPwd(e.password), iv: 'batch', folder })
+ });
+ }
+ }
+ selectedIds.clear();
+ hideBatchBar();
+ loadEntries();
+}
// ==================== ADD / DELETE ====================
async function addEntry() {
- const site = document.getElementById('siteInput').value.trim();
- const user = document.getElementById('usernameInput').value.trim();
- const pass = document.getElementById('passwordInput').value;
- if (!site || !pass) { toast('β Site and password required', 'error'); return; }
- if (user) localStorage.setItem('savedUsername', user);
- document.getElementById('addBtn').disabled = true;
- try {
- const enc = await encryptPwd(pass);
- const folder = document.getElementById('addFolderSelect').value;
- const r = await fetch(API + '/entries', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token }, body: JSON.stringify({ site, username: user, encrypted_password: enc.encrypted, iv: enc.iv, encryption_method: 'client', folder }) });
- if (r.ok) { document.getElementById('siteInput').value = ''; document.getElementById('passwordInput').value = ''; document.getElementById('strengthBar').className = 'strength-bar s0'; toast('β
Saved!'); loadEntries(); playSound('success'); }
- else { const d = await r.json(); toast('β ' + (d.error || 'Failed'), 'error'); }
- } catch (e) { toast('β οΈ Error', 'error'); }
- finally { document.getElementById('addBtn').disabled = false; }
+ const site = document.getElementById('addSite').value.trim();
+ const user = document.getElementById('addUsername').value.trim();
+ const pass = document.getElementById('addPassword').value;
+ if (!site || !pass) { toast('β Site and password required', 'error'); return; }
+ if (user) localStorage.setItem('savedUsername', user);
+ const btn = document.getElementById('addEntryBtn');
+ btn.disabled = true;
+ try {
+ const enc = await encryptPwd(pass);
+ const folder = document.getElementById('addFolder').value;
+ const r = await fetch(API + '/entries', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
+ body: JSON.stringify({ site, username: user, encrypted_password: enc.encrypted, iv: enc.iv, encryption_method: 'client', folder })
+ });
+ if (r.ok) {
+ closeAdd();
+ toast('β
Saved!');
+ loadEntries();
+ playSound('success');
+ } else {
+ const d = await r.json();
+ toast('β ' + (d.error || 'Failed'), 'error');
+ }
+ } catch (e) { toast('β οΈ Error', 'error'); }
+ finally { btn.disabled = false; }
}
async function delEntry(id) {
try {
@@ -648,4 +916,55 @@ if (token && curUser) {
['click', 'keypress', 'scroll', 'mousemove'].forEach(e => document.addEventListener(e, () => { if (token) resetIdle(); }));
document.addEventListener('keypress', e => { if (e.key === 'Enter') { if (document.getElementById('passwordInput') === document.activeElement) addEntry(); else if (document.getElementById('loginPassword') === document.activeElement) login(); else if (document.getElementById('regPassword') === document.activeElement) register(); } });
document.getElementById('genModal').addEventListener('click', e => { if (e.target === e.currentTarget) closeGen(); });
-document.getElementById('editModal').addEventListener('click', e => { if (e.target === e.currentTarget) closeEdit(); });
\ No newline at end of file
+document.getElementById('editModal').addEventListener('click', e => { if (e.target === e.currentTarget) closeEdit(); });
+// ==================== KEYBOARD SHORTCUTS (override browser) ====================
+document.addEventListener('keydown', function(e) {
+ // Don't fire when typing in inputs / textareas / selects
+ const tag = document.activeElement?.tagName;
+ const isInput = tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT';
+
+ // Escape β close any open modal or settings
+ if (e.key === 'Escape') {
+ if (!document.getElementById('settingsMenu').classList.contains('hidden')) {
+ document.getElementById('settingsMenu').classList.add('hidden');
+ return;
+ }
+ if (document.getElementById('addModal').classList.contains('show')) { closeAdd(); return; }
+ if (document.getElementById('editModal').classList.contains('show')) { closeEdit(); return; }
+ if (document.getElementById('genModal').style.display === 'flex') { closeGen(); return; }
+ const confirm = document.querySelector('.custom-confirm.show');
+ if (confirm) confirm.remove();
+ return;
+ }
+
+ // Only handle Ctrl+key without Shift, Alt, etc.
+ if (!e.ctrlKey || e.shiftKey || e.altKey || e.metaKey) return;
+
+ if (e.key === 'n' || e.key === 'N') {
+ e.preventDefault();
+ if (!isInput && !document.getElementById('addModal').classList.contains('show')) {
+ openAdd();
+ }
+ } else if (e.key === 'f' || e.key === 'F') {
+ e.preventDefault();
+ const searchInput = document.getElementById('searchInput');
+ if (searchInput) {
+ searchInput.focus();
+ searchInput.select();
+ }
+ } else if (e.key === 't' || e.key === 'T') {
+ e.preventDefault();
+ if (!isInput) toggleTrash();
+ } else if (e.key === 'l' || e.key === 'L') {
+ e.preventDefault();
+ if (!isInput) doLogout();
+ } else if (e.key === 's' || e.key === 'S') {
+ e.preventDefault();
+ // Save if add/edit modal is open
+ if (document.getElementById('addModal').classList.contains('show')) {
+ addEntry();
+ } else if (document.getElementById('editModal').classList.contains('show')) {
+ saveEdit();
+ }
+ }
+});
\ No newline at end of file
diff --git a/vault.db b/vault.db
index 34df7ff84e13addc60b0cae45ab9a109123347f4..8a00a2a0a656c62fa212033acf59c9664f858b76 100644
GIT binary patch
delta 1379
zcmbtUO-~b16n$+I?L*s1Qwu@VU=zc_G|p!`9VAc;SPE?sfeH!1X*-m*V=JAh5jLPJ
z-Kp~j2$8sQVf+iOO#B79cSmB}xiH?>`Q#%e#%VK|-n@J6Ip?0~-oAft-~Z~e-
P=lMuq+ZRB1DE91XVvysam^0+un_ZS>lL=Z#(Bd$Yl2}pVnZ7p4BOHt6t#cK)
zWVotx?bK@P(Q@mKTGn#Belg^p3L?e)IWPd=Lzp8?3sCAiMDGEsSB?gejg{$iR2DPWgx0%LGB2{8(=!DY2UpSf+n_y6{g-McO$pmy2anwpG!n3#)8a
z)H*E6VZ{uyoFwoP$3ZDv@|^>PrYo3}WnIy9x7GNOkB>X^gj3>xI4^`t=n%&uUT4b{
z4@RRHj|0YffULPqP`ZmR0A{8GV^)Ha9mOq-#u}o1NXB+A4M$h4SL#
z`MceFV`FrEd2za(FRhB|LTO9m-MwV(;6qkuNf4b~Ia*HVidfH3qv(q_SH}05byG|)
zPc9C;mM;;8`)`D!BTGFcK?LsU3N?i~cyPpiUr02o8#A%zVlxP@c2BHcCQdGtE^&_!}nAUL~PU6IQIkkkqao%<8ICh?!
zOjse!1{8!?j##i@2W)1C_!lFwjpYJ|>zWepN
zyT~8kJi36-pTGF>(@(xX-rf6XXQU2w=ktc)3_1<_#Iv(^A066_fql3?M!e`tI*7
z^fx4npdb1_J9_}Yi3hRl`ev!{nn+asT+l;0Fx4J01#(VStn?MlRARGCmau3lR!oee
zQ8h8w5))rw_s?546l|#A%YS2oWV4=~Q)h#2kE?2W2lixJKJ*FY6I;;S_3mP?GfI$9
znt-5>{pFQW6x{f9W%VLMV^9tfnj$8wRw~2w(*3%{Qxm>=siutfg%piWgEV7@wF`nC
zPZEk)x+6(k*T?8BO*hbMCE>on&I<&6-=Ez`YT9*ojZO}cWm4!qWLPr70v!In2%tU$
zedu32z8=)_S?a6hPf725vGdj;f*$+%YZlj#s+Rbqb>6JgRWm*%;%%+N)dnD5PEzO5
zXk$Q6dRjbNm=3azWp(aYfwD;)mj0)c6?DhR9KsYhc!q=!^!}|py`!nCkz0$sdr+T$
zqt9vBPJbKnATH$I4J}wrn`BEGDjXrjn7T18RUIRemFZ?YEw^$Jrx_atlUae}O8rbbx?-IIL1MwT`O$0?1(F?f)71cOS4Q#uCsPn7c_JJO>DR
z;$N&|<2a7%BzsX-@)l1!7s6QTn_`{shgx-!#HVV717dz+$+>n;90f~;5(*mfma_2guhYjz1Cz6_-w
z71n1A)Q7WxWI-6p)8E*?O{1#1V;zONj1Vf7%Tl?QX(>&s)u=^4-N-gm(TMyX{tefj|rz%Z6%Sd4*$N8ceHH$UCO)c~V6`Tsprl;9y2VKD%|at>A@Tld6+
zXz+5|QqP!-V)QBor`K$OOwUS2J7Zmm6fu09P-=CRLa*|
z4VoZX!aYtF=pHoXKVIB(i@V%4O{m};H5K|Hj4Tx-P8F}h%vFc1<%