04/20 Update extension UI and functions: add item, form filler

This commit is contained in:
2026-04-20 08:15:49 -04:00
parent 0320e4f8eb
commit bb000e0aff
5 changed files with 672 additions and 212 deletions
+32 -6
View File
@@ -432,9 +432,11 @@ body {
inset: 0;
background: rgba(0, 0, 0, 0.45);
display: flex;
align-items: center;
align-items: flex-start; /* anchor to top so card is never cut off */
justify-content: center;
z-index: 9999;
overflow-y: auto; /* allow scrolling if popup height is small */
padding: 16px 14px; /* breathing room from top/bottom edges */
}
.save-overlay.hidden {
display: none;
@@ -442,24 +444,27 @@ body {
.save-modal {
background: #fff;
border-radius: 12px;
padding: 20px 18px 16px;
width: 272px;
padding: 18px 16px 14px;
width: 100%; /* fill the overlay width minus its padding */
max-width: 288px;
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.22);
flex-shrink: 0; /* never crush the card */
}
.save-modal-header {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
font-size: 13px;
font-weight: 700;
color: #1a1a2e;
margin-bottom: 6px;
margin-bottom: 5px;
}
.save-modal-sub {
font-size: 11px;
color: #6b7280;
margin-bottom: 12px;
margin-bottom: 10px;
word-break: break-all;
line-height: 1.4;
}
.save-modal-actions {
display: flex;
@@ -473,6 +478,27 @@ body {
margin-top: 0;
}
/* Folder select inside the save modal */
.save-folder-select {
width: 100%;
padding: 7px 10px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 13px;
color: #1a1a2e;
background: #fff;
outline: none;
cursor: pointer;
transition:
border-color 0.15s,
box-shadow 0.15s;
appearance: auto;
}
.save-folder-select:focus {
border-color: #c0392b;
box-shadow: 0 0 0 2px rgba(192, 57, 43, 0.12);
}
/* ── Generator view ──────────────────────────────────────────────── */
.gen-suggestion {
background: #e8f5f0;
+6
View File
@@ -313,6 +313,12 @@
placeholder="username or email"
/>
</div>
<div class="form-group">
<label for="save-folder">Folder</label>
<select id="save-folder" class="save-folder-select">
<option value="">— No folder —</option>
</select>
</div>
<div class="save-modal-actions">
<button id="btn-save-yes" class="btn-primary btn-sm">Save</button>
<button id="btn-save-no" class="btn-ghost btn-sm">Not now</button>
+91 -10
View File
@@ -341,7 +341,11 @@ async function signOut() {
}
} catch {}
await chrome.storage.session.clear();
await chrome.storage.local.remove(["refresh_token", "enc_key_salt"]);
await chrome.storage.local.remove([
"refresh_token",
"enc_key_salt",
"vault_items_cs",
]);
_vaultKey = null;
_items = [];
showView("login");
@@ -411,8 +415,27 @@ async function fetchAndDecryptVault() {
}),
);
// Write to session for the popup's own use (badge, rendering).
await chrome.storage.session.set({ vault_items: _items });
chrome.runtime.sendMessage({ type: "VAULT_UPDATED" }).catch(() => {});
// Write a lightweight copy to local storage — this is what content scripts
// read, since chrome.storage.local works reliably across all Chrome versions
// and does not require message delivery from the background service worker.
const itemsForContentScript = _items.map((item) => ({
id: item.id,
name: item.name,
item_type: item.item_type,
plain: item.plain,
}));
await chrome.storage.local.set({ vault_items_cs: itemsForContentScript });
// Notify background to refresh badges and forward to content scripts.
chrome.runtime
.sendMessage({
type: "VAULT_UPDATED",
vault_items: itemsForContentScript,
})
.catch(() => {});
} catch (err) {
console.error("fetchAndDecryptVault:", err);
} finally {
@@ -581,6 +604,30 @@ function initTabs() {
// ── Save-prompt modal ─────────────────────────────────────────────────────────
/**
* Populate the folder <select> inside the save modal.
* Fetches /api/folders and rebuilds the option list.
* The first option ("— No folder —") with value "" is always kept.
*/
async function loadFoldersIntoSaveModal() {
const select = $("save-folder");
// Reset to just the placeholder option.
select.innerHTML = '<option value="">— No folder —</option>';
try {
const res = await apiFetch("/api/folders");
if (!res?.ok) return;
const folders = await res.json();
folders.forEach((f) => {
const opt = document.createElement("option");
opt.value = f.id;
opt.textContent = f.name;
select.appendChild(opt);
});
} catch (err) {
console.error("[PassKeeper] loadFoldersIntoSaveModal failed:", err);
}
}
/**
* Show the save-prompt overlay. It is a blocking modal — no backdrop click,
* no ✕ button — so the user MUST click "Save" or "Not now".
@@ -602,6 +649,10 @@ async function checkPendingSave() {
? "Update in PassKeeper?"
: "Save to PassKeeper?";
// Load folders (non-blocking — modal shows immediately, folders populate async).
$("save-folder").value = "";
loadFoldersIntoSaveModal();
// Show the overlay.
$("save-prompt-overlay").classList.remove("hidden");
$("save-name").focus();
@@ -635,6 +686,9 @@ async function saveCredential(data) {
notes: "",
};
const name = $("save-name").value.trim() || data.siteName || "Untitled";
// Read selected folder — empty string means no folder (null).
const folderVal = $("save-folder").value;
const folder_id = folderVal ? parseInt(folderVal, 10) : null;
const { enc_data, iv } = await ExtCrypto.encryptItem(_vaultKey, plain);
try {
const res = await apiFetch("/api/vault", {
@@ -642,12 +696,16 @@ async function saveCredential(data) {
body: JSON.stringify({
name,
item_type: "password",
folder_id: null,
folder_id,
enc_data,
iv,
}),
});
if (res?.ok) {
console.log(
"[PassKeeper] Credential saved to vault, folder_id:",
folder_id,
);
await fetchAndDecryptVault();
renderList();
}
@@ -821,14 +879,37 @@ async function init() {
const sessionState = await restoreSessionIfAvailable();
if (sessionState === "vault") {
showView("vault");
await checkPendingSave();
if (_items.length) {
renderList();
fetchAndDecryptVault().then(() => renderList());
// Check whether the content script requested a specific view (e.g. generator).
const { popup_nav } = await chrome.storage.session.get("popup_nav");
if (popup_nav) {
await chrome.storage.session.remove("popup_nav");
if (popup_nav === "generator") {
showView("generator");
initGenerator();
// Still load vault data in background so the Vault tab is ready.
fetchAndDecryptVault().then(() => {});
// Wire event listeners below, then return early from vault-specific setup.
} else {
showView("vault");
await checkPendingSave();
if (_items.length) {
renderList();
fetchAndDecryptVault().then(() => renderList());
} else {
await fetchAndDecryptVault();
renderList();
}
}
} else {
await fetchAndDecryptVault();
renderList();
showView("vault");
await checkPendingSave();
if (_items.length) {
renderList();
fetchAndDecryptVault().then(() => renderList());
} else {
await fetchAndDecryptVault();
renderList();
}
}
} else if (sessionState === "unlock") {
showView("unlock");