04/20 Update extension UI and functions: add item, form filler
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user