05/19 Enhance codes
This commit is contained in:
@@ -932,6 +932,7 @@ body {
|
||||
background: #e0f2fe;
|
||||
color: #0369a1;
|
||||
}
|
||||
|
||||
/* ── Three-dot flyout menu ───────────────────────────────────────────────── */
|
||||
|
||||
.pk-flyout {
|
||||
@@ -940,7 +941,7 @@ body {
|
||||
background: #ffffff;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 16px rgba(0,0,0,0.13);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.13);
|
||||
min-width: 150px;
|
||||
padding: 4px 0;
|
||||
display: flex;
|
||||
@@ -972,9 +973,20 @@ body {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.pk-flyout-info {
|
||||
padding: 6px 12px 7px;
|
||||
font-size: 11px;
|
||||
color: #9ca3af;
|
||||
border-top: 1px solid #f3f4f6;
|
||||
margin-top: 2px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* ── Collapsible folder groups ────────────────────────────────────────────── */
|
||||
|
||||
.pk-group { margin-bottom: 2px; }
|
||||
.pk-group {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.pk-group-header {
|
||||
display: flex;
|
||||
@@ -988,7 +1000,9 @@ body {
|
||||
transition: background 0.1s;
|
||||
}
|
||||
|
||||
.pk-group-header:hover { background: #f0f4ff; }
|
||||
.pk-group-header:hover {
|
||||
background: #f0f4ff;
|
||||
}
|
||||
|
||||
.pk-group-name {
|
||||
font-size: 12px;
|
||||
@@ -1091,4 +1105,4 @@ body {
|
||||
.reprompt-actions .btn-primary,
|
||||
.reprompt-actions .btn-ghost {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
+72
-47
@@ -69,10 +69,10 @@ function escHtml(str) {
|
||||
// Auto-clear clipboard 30 s after a sensitive copy.
|
||||
let _clipTimer = null;
|
||||
function _copyWithAutoClear(text) {
|
||||
navigator.clipboard.writeText(text).catch(() => {});
|
||||
navigator.clipboard.writeText(text).catch(() => { });
|
||||
if (_clipTimer) clearTimeout(_clipTimer);
|
||||
_clipTimer = setTimeout(() => {
|
||||
navigator.clipboard.writeText("").catch(() => {});
|
||||
navigator.clipboard.writeText("").catch(() => { });
|
||||
_clipTimer = null;
|
||||
}, 30_000);
|
||||
}
|
||||
@@ -186,7 +186,7 @@ function siteLabel(item) {
|
||||
if (item.plain?.url) {
|
||||
try {
|
||||
return new URL(item.plain.url).hostname.replace(/^www\./, "");
|
||||
} catch {}
|
||||
} catch { }
|
||||
}
|
||||
return item.name;
|
||||
}
|
||||
@@ -441,7 +441,7 @@ async function completeLogin(data, masterPassword) {
|
||||
refresh_token: data.refresh_token,
|
||||
enc_key_salt: data.enc_key_salt,
|
||||
})
|
||||
.catch(() => {});
|
||||
.catch(() => { });
|
||||
|
||||
showView("vault");
|
||||
await checkPendingSave();
|
||||
@@ -502,9 +502,9 @@ async function signOut() {
|
||||
Authorization: `Bearer ${access_token}`,
|
||||
},
|
||||
body: JSON.stringify({ refresh_token }),
|
||||
}).catch(() => {});
|
||||
}).catch(() => { });
|
||||
}
|
||||
} catch {}
|
||||
} catch { }
|
||||
await chrome.storage.session.clear();
|
||||
await chrome.storage.local.remove(["refresh_token", "enc_key_salt"]);
|
||||
// vault_items_cs is now in session storage — cleared by the session.clear() call above.
|
||||
@@ -616,7 +616,7 @@ async function fetchAndDecryptVault() {
|
||||
type: "VAULT_UPDATED",
|
||||
vault_items: itemsForContentScript,
|
||||
})
|
||||
.catch(() => {});
|
||||
.catch(() => { });
|
||||
|
||||
// Run health checks in the background — sync metrics first, then HIBP.
|
||||
// Results are sent to the background SW via HEALTH_UPDATE so the toolbar
|
||||
@@ -645,7 +645,7 @@ async function _runPopupHealthCheck() {
|
||||
if (!pwItems.length) {
|
||||
chrome.runtime
|
||||
.sendMessage({ type: "HEALTH_UPDATE", breached: 0, weak: 0, reused: 0 })
|
||||
.catch(() => {});
|
||||
.catch(() => { });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -676,7 +676,7 @@ async function _runPopupHealthCheck() {
|
||||
weak: weak.length,
|
||||
reused: reused.length,
|
||||
})
|
||||
.catch(() => {});
|
||||
.catch(() => { });
|
||||
|
||||
// HIBP — k-anonymity, parallel.
|
||||
const hibpResults = await Promise.all(
|
||||
@@ -695,7 +695,7 @@ async function _runPopupHealthCheck() {
|
||||
weak: weak.length,
|
||||
reused: reused.length,
|
||||
})
|
||||
.catch(() => {});
|
||||
.catch(() => { });
|
||||
} catch (err) {
|
||||
console.error("[PassKeeper] Popup health check failed:", err);
|
||||
}
|
||||
@@ -1053,7 +1053,7 @@ function renderList() {
|
||||
password: item.plain.password || "",
|
||||
autologin: !!item.plain.autologin,
|
||||
})
|
||||
.catch(() => {});
|
||||
.catch(() => { });
|
||||
}
|
||||
window.close();
|
||||
}),
|
||||
@@ -1093,44 +1093,64 @@ function renderList() {
|
||||
const menuItems = [
|
||||
item.plain?.url
|
||||
? {
|
||||
label: "Open URL",
|
||||
icon: '<svg viewBox="0 0 24 24" fill="none" width="14" height="14"><path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/><path d="M15 3h6v6M10 14L21 3" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
||||
action: async () => {
|
||||
chrome.tabs.create({ url: item.plain.url });
|
||||
flyout.remove();
|
||||
},
|
||||
}
|
||||
label: "Open URL",
|
||||
icon: '<svg viewBox="0 0 24 24" fill="none" width="14" height="14"><path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/><path d="M15 3h6v6M10 14L21 3" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
||||
action: async () => {
|
||||
chrome.tabs.create({ url: item.plain.url });
|
||||
flyout.remove();
|
||||
},
|
||||
}
|
||||
: null,
|
||||
item.plain?.username
|
||||
? {
|
||||
label: "Copy username",
|
||||
icon: '<svg viewBox="0 0 24 24" fill="none" width="14" height="14"><circle cx="12" cy="8" r="4" stroke="currentColor" stroke-width="1.7"/><path d="M4 20c0-4 3.6-7 8-7s8 3 8 7" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>',
|
||||
action: async () => {
|
||||
if (item.plain?.reprompt) {
|
||||
const ok = await _repromptMasterPassword();
|
||||
if (!ok) return;
|
||||
}
|
||||
_copyWithAutoClear(item.plain.username);
|
||||
flyout.remove();
|
||||
},
|
||||
}
|
||||
label: "Copy username",
|
||||
icon: '<svg viewBox="0 0 24 24" fill="none" width="14" height="14"><circle cx="12" cy="8" r="4" stroke="currentColor" stroke-width="1.7"/><path d="M4 20c0-4 3.6-7 8-7s8 3 8 7" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>',
|
||||
action: async () => {
|
||||
if (item.plain?.reprompt) {
|
||||
const ok = await _repromptMasterPassword();
|
||||
if (!ok) return;
|
||||
}
|
||||
_copyWithAutoClear(item.plain.username);
|
||||
flyout.remove();
|
||||
},
|
||||
}
|
||||
: null,
|
||||
item.plain?.password
|
||||
? {
|
||||
label: "Copy password",
|
||||
icon: '<svg viewBox="0 0 24 24" fill="none" width="14" height="14"><rect x="3" y="10" width="18" height="11" rx="2" stroke="currentColor" stroke-width="1.7"/><path d="M8 10V7a4 4 0 018 0v3" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>',
|
||||
action: async () => {
|
||||
if (item.plain?.reprompt) {
|
||||
const ok = await _repromptMasterPassword();
|
||||
if (!ok) return;
|
||||
}
|
||||
_copyWithAutoClear(item.plain.password);
|
||||
flyout.remove();
|
||||
},
|
||||
}
|
||||
label: "Copy password",
|
||||
icon: '<svg viewBox="0 0 24 24" fill="none" width="14" height="14"><rect x="3" y="10" width="18" height="11" rx="2" stroke="currentColor" stroke-width="1.7"/><path d="M8 10V7a4 4 0 018 0v3" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>',
|
||||
action: async () => {
|
||||
if (item.plain?.reprompt) {
|
||||
const ok = await _repromptMasterPassword();
|
||||
if (!ok) return;
|
||||
}
|
||||
_copyWithAutoClear(item.plain.password);
|
||||
flyout.remove();
|
||||
},
|
||||
}
|
||||
: null,
|
||||
].filter(Boolean);
|
||||
|
||||
// Add a non-action info row showing when the password was last changed.
|
||||
// Only shown for password items that have password_changed_at set.
|
||||
if (item.item_type === "password" && item.plain?.password_changed_at) {
|
||||
const daysAgo = Math.floor(
|
||||
(Date.now() - new Date(item.plain.password_changed_at).getTime()) /
|
||||
86400000,
|
||||
);
|
||||
const ageText =
|
||||
daysAgo === 0
|
||||
? "Changed today"
|
||||
: daysAgo === 1
|
||||
? "Changed yesterday"
|
||||
: `Changed ${daysAgo} days ago`;
|
||||
const infoRow = document.createElement("div");
|
||||
infoRow.className = "pk-flyout-info";
|
||||
infoRow.textContent = ageText;
|
||||
// Append after action rows are added.
|
||||
flyout._ageInfoRow = infoRow;
|
||||
}
|
||||
|
||||
menuItems.forEach((mi) => {
|
||||
const row = document.createElement("button");
|
||||
row.className = "pk-flyout-item";
|
||||
@@ -1142,6 +1162,11 @@ function renderList() {
|
||||
flyout.appendChild(row);
|
||||
});
|
||||
|
||||
// Append age info row if present.
|
||||
if (flyout._ageInfoRow) {
|
||||
flyout.appendChild(flyout._ageInfoRow);
|
||||
}
|
||||
|
||||
// Position flyout using fixed coords so it escapes vault-list overflow clipping.
|
||||
const appEl = document.getElementById("app");
|
||||
appEl.appendChild(flyout);
|
||||
@@ -1246,7 +1271,7 @@ async function checkPendingSave() {
|
||||
function _clearPendingSave() {
|
||||
$("save-prompt-overlay").classList.add("hidden");
|
||||
chrome.storage.local.remove("pending_save");
|
||||
chrome.runtime.sendMessage({ type: "CLEAR_SAVE_BADGE" }).catch(() => {});
|
||||
chrome.runtime.sendMessage({ type: "CLEAR_SAVE_BADGE" }).catch(() => { });
|
||||
}
|
||||
|
||||
$("btn-save-yes").onclick = async () => {
|
||||
@@ -1484,7 +1509,7 @@ async function initAddView() {
|
||||
: new URL(tab.url).hostname.replace(/^www\./, "");
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
} catch (e) { }
|
||||
|
||||
// Load folders into the add-folder select.
|
||||
const addFolderSel = $("add-folder");
|
||||
@@ -1500,7 +1525,7 @@ async function initAddView() {
|
||||
addFolderSel.appendChild(opt);
|
||||
});
|
||||
}
|
||||
} catch (e) {}
|
||||
} catch (e) { }
|
||||
|
||||
// Wire one-time event listeners.
|
||||
if (!_addViewInitialised) {
|
||||
@@ -1635,7 +1660,7 @@ async function initAccountView() {
|
||||
// Tell background to re-apply the new interval.
|
||||
chrome.runtime
|
||||
.sendMessage({ type: "SET_IDLE_TIMEOUT", seconds: val })
|
||||
.catch(() => {});
|
||||
.catch(() => { });
|
||||
// Brief "Saved" confirmation.
|
||||
const saved = $("acct-idle-saved");
|
||||
saved.classList.remove("hidden");
|
||||
@@ -1656,7 +1681,7 @@ async function init() {
|
||||
currentWindow: true,
|
||||
});
|
||||
_currentUrl = tab?.url || "";
|
||||
} catch {}
|
||||
} catch { }
|
||||
|
||||
// Set vault link
|
||||
$("btn-open-vault").href = VAULT_URL;
|
||||
@@ -1672,7 +1697,7 @@ async function init() {
|
||||
showView("generator");
|
||||
initGenerator();
|
||||
// Still load vault data in background so the Vault tab is ready.
|
||||
fetchAndDecryptVault().then(() => {});
|
||||
fetchAndDecryptVault().then(() => { });
|
||||
// Wire event listeners below, then return early from vault-specific setup.
|
||||
} else {
|
||||
showView("vault");
|
||||
@@ -1752,7 +1777,7 @@ async function init() {
|
||||
// browser is open. We ping it every 20 s; the ping itself is a no-op but
|
||||
// prevents the SW from being killed between popup openings.
|
||||
const _keepalive = setInterval(() => {
|
||||
chrome.runtime.sendMessage({ type: "KEEPALIVE" }).catch(() => {});
|
||||
chrome.runtime.sendMessage({ type: "KEEPALIVE" }).catch(() => { });
|
||||
}, 20_000);
|
||||
|
||||
// Search
|
||||
|
||||
Reference in New Issue
Block a user