90500f481978
inject.ts createSmartGenerator: default 64, editable password output, click-to-edit length; showCreateForm: split into username + email fields, identity suggestions on focus for both fields (v2.38.4)
- time
- 2026-05-10 18:59 UTC · 5 months ago
- machine
- koh2
- parent
- b8ffaf221b51
↓ peach-browser-90500f48.tar.gzatlas at this save
files changed4 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/content/inject.ts+108 −19manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach",4- "version": "2.38.3",4+ "version": "2.38.4",55 "description": "Zero-knowledge password manager",66 "browser_specific_settings": {77 "gecko": {manifest.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach",4- "version": "2.38.3",4+ "version": "2.38.4",55 "description": "Zero-knowledge password manager",66 "action": {77 "default_popup": "src/popup/index.html",package.json+1 −1
@@ -1,6 +1,6 @@11 {22 "name": "peach",3- "version": "2.38.3",3+ "version": "2.38.4",44 "description": "Zero-knowledge password manager",55 "type": "module",66 "scripts": {src/content/inject.ts+108 −19
@@ -210,7 +210,7 @@210210 function createSmartGenerator(opts: SmartGeneratorOptions): SmartGenerator {211211 const policy = opts.policy ?? {};212212 213- let length = 20;213+ let length = 64;214214 if (policy.minLength) length = Math.max(length, policy.minLength);215215 if (policy.maxLength) length = Math.min(length, policy.maxLength);216216 if (length < 8) length = 8;@@ -230,8 +230,13 @@230230 231231 const displayRow = root.appendChild(document.createElement("div"));232232 displayRow.className = "pwgen-display-row";233- const output = displayRow.appendChild(document.createElement("div"));233+ const output = displayRow.appendChild(document.createElement("input")) as HTMLInputElement;234+ output.type = "text";234235 output.className = "pwgen-output";236+ output.setAttribute("autocomplete", "off");237+ output.setAttribute("spellcheck", "false");238+ output.setAttribute("autocorrect", "off");239+ output.setAttribute("autocapitalize", "off");235240 236241 const regenBtn = displayRow.appendChild(document.createElement("button"));237242 regenBtn.className = "pwgen-icon-btn";@@ -258,8 +263,27 @@258263 if (policy.minLength) slider.min = String(Math.max(8, policy.minLength));259264 if (policy.maxLength) slider.max = String(Math.min(128, policy.maxLength));260265 const lenNum = controls.appendChild(document.createElement("span"));261- lenNum.className = "pwgen-len-num";266+ lenNum.className = "pwgen-len-num pwgen-len-num--edit";262267 lenNum.textContent = String(length);268+ lenNum.addEventListener("click", () => {269+ const li = document.createElement("input");270+ li.type = "text"; li.inputMode = "numeric";271+ li.className = "pwgen-len-input"; li.value = String(length);272+ lenNum.replaceWith(li);273+ requestAnimationFrame(() => li.select());274+ function commitLenEdit() {275+ const n = parseInt(li.value, 10);276+ if (!isNaN(n)) {277+ length = Math.max(8, Math.min(128, n));278+ slider.value = String(length);279+ regenerate();280+ }281+ lenNum.textContent = String(length);282+ li.replaceWith(lenNum);283+ }284+ li.addEventListener("keydown", (e) => { if (e.key === "Enter") { e.preventDefault(); commitLenEdit(); } else if (e.key === "Escape") { li.replaceWith(lenNum); } });285+ li.addEventListener("blur", commitLenEdit);286+ });263287 264288 const pills = root.appendChild(document.createElement("div"));265289 pills.className = "pwgen-pills";@@ -316,10 +340,15 @@316340 includeSymbols: symbols,317341 forbiddenChars,318342 });319- output.textContent = password;343+ output.value = password;320344 opts.onChange?.(password);321345 }322346 347+ output.addEventListener("input", () => {348+ password = output.value;349+ opts.onChange?.(password);350+ });351+323352 slider.addEventListener("input", () => {324353 length = parseInt(slider.value, 10);325354 lenNum.textContent = String(length);@@ -342,7 +371,7 @@342371 343372 if (opts.initialPassword) {344373 password = opts.initialPassword;345- output.textContent = password;374+ output.value = password;346375 opts.onChange?.(password);347376 } else {348377 regenerate();@@ -1016,11 +1045,10 @@10161045 background: var(--peach-input-bg); border: 1px solid var(--peach-input-border);10171046 }10181047 .pwgen-output {1019- flex: 1; min-width: 0;1048+ flex: 1; min-width: 0; width: 0;10201049 font-family: "IBM Plex Mono", monospace; font-size: 12px;10211050 color: var(--peach-text);1022- white-space: nowrap; overflow: hidden; text-overflow: ellipsis;1023- user-select: text;1051+ background: transparent; border: none; outline: none; padding: 0;10241052 }10251053 .pwgen-icon-btn {10261054 flex-shrink: 0;@@ -1053,6 +1081,29 @@10531081 font-family: "IBM Plex Mono", monospace; font-size: 11px;10541082 color: var(--peach-accent); min-width: 22px; text-align: right;10551083 }1084+ .pwgen-len-num--edit { cursor: text; border-bottom: 1px dotted color-mix(in srgb, var(--peach-accent) 50%, transparent); }1085+ .pwgen-len-input {1086+ font-family: "IBM Plex Mono", monospace; font-size: 11px;1087+ color: var(--peach-accent); width: 28px; text-align: right;1088+ background: transparent; border: none; border-bottom: 1px solid var(--peach-accent);1089+ outline: none; padding: 0; min-width: 22px;1090+ }1091+ .identity-suggest-list { display: flex; flex-direction: column; gap: 1px; margin-top: 3px; }1092+ .identity-suggest-row {1093+ display: flex; align-items: center; justify-content: space-between;1094+ width: 100%; padding: 4px 8px; border-radius: 5px; border: none;1095+ background: transparent; cursor: pointer; text-align: left;1096+ transition: background 80ms;1097+ }1098+ .identity-suggest-row:hover { background: color-mix(in srgb, var(--peach-accent) 10%, transparent); }1099+ .identity-suggest-val {1100+ font-family: "IBM Plex Mono", monospace; font-size: 10px; color: var(--peach-text);1101+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; min-width: 0;1102+ }1103+ .identity-suggest-meta {1104+ font-family: "IBM Plex Mono", monospace; font-size: 9px;1105+ color: var(--peach-text-muted); flex-shrink: 0; margin-left: 6px;1106+ }10561107 .pwgen-pills { display: flex; gap: 5px; flex-wrap: wrap; }10571108 .pwgen-pill {10581109 padding: 3px 8px; border-radius: 999px;@@ -1513,6 +1564,36 @@15131564 if (idx >= 0) (all[idx] as HTMLElement).scrollIntoView({ block: "nearest" });15141565 }15151566 1567+ function attachIdentitySuggestions(g: HTMLDivElement, inp: HTMLInputElement, identities: SavedIdentity[]): void {1568+ if (!identities.length) return;1569+ const list = g.appendChild(document.createElement("div"));1570+ list.className = "identity-suggest-list";1571+ list.style.display = "none";1572+ function render() {1573+ const q = inp.value.toLowerCase();1574+ const filtered = identities.filter(s => !q || s.value.toLowerCase().includes(q)).slice(0, 6);1575+ list.innerHTML = "";1576+ if (!filtered.length) { list.style.display = "none"; return; }1577+ for (const s of filtered) {1578+ const row = list.appendChild(document.createElement("button"));1579+ row.type = "button"; row.className = "identity-suggest-row";1580+ const val = row.appendChild(document.createElement("span"));1581+ val.className = "identity-suggest-val"; val.textContent = s.value;1582+ const meta = row.appendChild(document.createElement("span"));1583+ meta.className = "identity-suggest-meta"; meta.textContent = `${s.useCount}×`;1584+ row.addEventListener("mousedown", (e) => {1585+ e.preventDefault();1586+ inp.value = s.value;1587+ list.style.display = "none";1588+ });1589+ }1590+ list.style.display = "";1591+ }1592+ inp.addEventListener("focus", render);1593+ inp.addEventListener("input", render);1594+ inp.addEventListener("blur", () => setTimeout(() => { list.style.display = "none"; }, 150));1595+ }1596+15161597 async function showCreateForm() {15171598 currentMode = "create";15181599 container.innerHTML = "";@@ -1564,12 +1645,23 @@15641645 }15651646 body.appendChild(ug.g);15661647 1567- // Username (pre-fill from anchor field)1568- const xg = fGroup("username / email");1569- xg.inp.value = preUsername; xg.inp.placeholder = "[email protected]";1648+ // Username — pre-fill from anchor if it doesn't look like an email1649+ const xg = fGroup("username");1650+ xg.inp.value = preUsername && !preUsername.includes("@") ? preUsername : "";1651+ xg.inp.placeholder = "johndoe";15701652 xg.inp.setAttribute("autocomplete", "off");1653+ if (options.savedIdentities?.length) attachIdentitySuggestions(xg.g, xg.inp, options.savedIdentities);15711654 body.appendChild(xg.g);15721655 1656+ // Email — pre-fill from anchor if it looks like an email1657+ const eg = fGroup("email");1658+ (eg.inp as HTMLInputElement).type = "email";1659+ eg.inp.value = preUsername && preUsername.includes("@") ? preUsername : "";1660+ eg.inp.placeholder = "[email protected]";1661+ eg.inp.setAttribute("autocomplete", "off");1662+ if (options.savedIdentities?.length) attachIdentitySuggestions(eg.g, eg.inp, options.savedIdentities);1663+ body.appendChild(eg.g);1664+15731665 // Password — full smart generator with charset pills + length slider +15741666 // policy hint. Mirrors the main UI's compact generator so users get the15751667 // same controls inline as in the popup.@@ -1592,6 +1684,7 @@15921684 const title = tg.inp.value.trim();15931685 const pw = currentPassword.trim();15941686 const username = xg.inp.value.trim();1687+ const email = eg.inp.value.trim();15951688 if (!title) {15961689 tg.inp.style.borderColor = "rgba(220,70,60,0.5)"; tg.inp.focus();15971690 showFormError("Entry needs a title");@@ -1606,16 +1699,12 @@16061699 try {16071700 const res = await chrome.runtime.sendMessage({16081701 type: "ADD_ENTRY",1609- payload: { title, url: ug.inp.value.trim(), username, password: pw, type: "login" },1702+ payload: { title, url: ug.inp.value.trim(), username, email: email || undefined, password: pw, type: "login" },16101703 });16111704 if ((res as { ok?: boolean }).ok) {1612- // Fill the detected form regardless of mode. For login the user1613- // created an entry to use here; for signup the password was just1614- // generated for this site. Either way the credentials land in1615- // the form. options.onFillFields is wired by the caller (index.ts)1616- // and also records the form into filledForms so the popup will1617- // not re-attach to the filled fields.1618- options.onFillFields?.(username, pw);1705+ // Fill the detected form. onFillFields uses whichever identity the1706+ // page anchored on — the vault captures both username and email.1707+ options.onFillFields?.(username || email, pw);16191708 showSuccess(title);16201709 } else {16211710 const errMsg = (res as { error?: string }).error === "vault locked"