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
4 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/content/inject.ts+108 −19
manifest.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": {

view file

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",

view file

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": {

view file

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"

view file