ce561ac4b57e

inline silver-platter Edit: replace × with Edit button, click renders inline edit form (title/url/username/password/totp/notes) inside the same shadow-DOM surface, save→UPDATE_ENTRY then refresh platter, cancel returns; bump v2.11.12

time
2026-04-29 02:58 UTC · 6 months ago
machine
koh2
parent
4fd968be25e7
4 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/content/inject.ts+178 −8
manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach",4-  "version": "2.11.11",4+  "version": "2.11.12",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.11.11",4+  "version": "2.11.12",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.11.11",3+  "version": "2.11.12",44   "description": "Zero-knowledge password manager",55   "type": "module",66   "scripts": {

view file

src/content/inject.ts+178 −8
@@ -1563,20 +1563,190 @@15631563       actions.appendChild(moreBtn);15641564     }15651565 1566-    const closeBtn = document.createElement("button");1567-    closeBtn.className = "sp-action-btn";1568-    closeBtn.type = "button";1569-    closeBtn.title = "Dismiss";1570-    closeBtn.textContent = "✕";1571-    closeBtn.addEventListener("click", (e) => {1566+    const editBtn = document.createElement("button");1567+    editBtn.className = "sp-action-btn sp-action-edit";1568+    editBtn.type = "button";1569+    editBtn.title = "Edit entry";1570+    editBtn.textContent = "Edit";1571+    editBtn.addEventListener("click", (e) => {15721572       e.stopPropagation();15731573       closeTray();1574-      dismiss(true);1574+      void renderInlineEdit(entry);15751575     });1576-    actions.appendChild(closeBtn);1576+    actions.appendChild(editBtn);15771577 15781578     platter.appendChild(actions);15791579     container.appendChild(platter);1580+  }1581+1582+  /**1583+   * Inline-popup edit mode. Swaps the silver platter for an editable form1584+   * over the same shadow-DOM surface so the user never leaves the page.1585+   * Saves go through UPDATE_ENTRY; on success we refresh the silver platter1586+   * with the new fields. Cancel returns without writing.1587+   */1588+  async function renderInlineEdit(summary: SelectorEntry) {1589+    container.innerHTML = "";1590+    const wrap = document.createElement("div");1591+    wrap.className = "create-form-wrap";1592+1593+    const head = document.createElement("div");1594+    head.className = "create-head";1595+    const back = document.createElement("button");1596+    back.className = "back-btn";1597+    back.type = "button";1598+    back.title = "Cancel";1599+    back.innerHTML = `<svg width="11" height="11" viewBox="0 0 11 11" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"><path d="M7 2L3 5.5L7 9"/></svg>`;1600+    back.addEventListener("click", () => renderSilverPlatter(summary));1601+    head.appendChild(back);1602+    const headLbl = document.createElement("div");1603+    headLbl.className = "create-label";1604+    headLbl.textContent = "Edit entry";1605+    head.appendChild(headLbl);1606+    wrap.appendChild(head);1607+1608+    const body = document.createElement("div");1609+    body.className = "create-body";1610+1611+    // Loading shimmer while we fetch the full entry1612+    const loading = document.createElement("div");1613+    loading.className = "empty-hint";1614+    loading.textContent = "loading…";1615+    body.appendChild(loading);1616+    wrap.appendChild(body);1617+    container.appendChild(wrap);1618+1619+    const res = (await chrome.runtime.sendMessage({1620+      type: "GET_ENTRY_FOR_FILL",1621+      payload: { id: summary.id },1622+    }).catch(() => null)) as { ok: boolean; data?: { title?: string; username?: string; password?: string; url?: string; totp?: string; notes?: string } } | null;1623+1624+    if (!res || !res.ok || !res.data) {1625+      loading.textContent = "couldn't load this entry. try again from the popup.";1626+      return;1627+    }1628+    body.innerHTML = "";1629+1630+    function field(label: string, value: string, type: "text" | "password" = "text"): HTMLInputElement {1631+      const group = document.createElement("div");1632+      group.className = "f-group";1633+      const lbl = document.createElement("div");1634+      lbl.className = "f-label";1635+      lbl.textContent = label;1636+      const input = document.createElement("input");1637+      input.className = "f-input";1638+      input.type = type;1639+      input.value = value;1640+      input.autocomplete = "off";1641+      group.append(lbl, input);1642+      body.appendChild(group);1643+      return input;1644+    }1645+1646+    const titleInput = field("title", res.data.title ?? "");1647+    const urlInput = field("url", res.data.url ?? "");1648+    urlInput.classList.add("url-f");1649+    const usernameInput = field("username", res.data.username ?? "");1650+1651+    // Password row with reveal toggle1652+    const pwGroup = document.createElement("div");1653+    pwGroup.className = "f-group";1654+    const pwLbl = document.createElement("div");1655+    pwLbl.className = "f-label";1656+    pwLbl.textContent = "password";1657+    const pwRow = document.createElement("div");1658+    pwRow.className = "f-row";1659+    const pwInput = document.createElement("input");1660+    pwInput.className = "f-input";1661+    pwInput.type = "password";1662+    pwInput.value = res.data.password ?? "";1663+    pwInput.autocomplete = "new-password";1664+    const revealBtn = document.createElement("button");1665+    revealBtn.className = "gen-btn";1666+    revealBtn.type = "button";1667+    revealBtn.textContent = "show";1668+    revealBtn.addEventListener("click", () => {1669+      const reveal = pwInput.type === "password";1670+      pwInput.type = reveal ? "text" : "password";1671+      revealBtn.textContent = reveal ? "hide" : "show";1672+    });1673+    pwRow.append(pwInput, revealBtn);1674+    pwGroup.append(pwLbl, pwRow);1675+    body.appendChild(pwGroup);1676+1677+    const totpInput = field("2fa secret", res.data.totp ?? "");1678+1679+    // Notes1680+    const notesGroup = document.createElement("div");1681+    notesGroup.className = "f-group";1682+    const notesLbl = document.createElement("div");1683+    notesLbl.className = "f-label";1684+    notesLbl.textContent = "notes";1685+    const notesInput = document.createElement("textarea");1686+    notesInput.className = "f-input";1687+    notesInput.rows = 3;1688+    notesInput.value = res.data.notes ?? "";1689+    notesInput.style.fontFamily = "Inter, sans-serif";1690+    notesInput.style.resize = "vertical";1691+    notesGroup.append(notesLbl, notesInput);1692+    body.appendChild(notesGroup);1693+1694+    // Save / Cancel row1695+    const actionsRow = document.createElement("div");1696+    actionsRow.className = "f-row";1697+    const saveBtn = document.createElement("button");1698+    saveBtn.className = "save-btn";1699+    saveBtn.type = "button";1700+    saveBtn.textContent = "Save changes";1701+    saveBtn.style.flex = "1";1702+    const cancelBtn = document.createElement("button");1703+    cancelBtn.className = "gen-btn";1704+    cancelBtn.type = "button";1705+    cancelBtn.textContent = "cancel";1706+    cancelBtn.addEventListener("click", () => renderSilverPlatter(summary));1707+    actionsRow.append(saveBtn, cancelBtn);1708+    body.appendChild(actionsRow);1709+1710+    saveBtn.addEventListener("click", async () => {1711+      saveBtn.disabled = true;1712+      const originalLabel = saveBtn.textContent;1713+      saveBtn.textContent = "Saving…";1714+      const patch: Record<string, unknown> = {1715+        title: titleInput.value.trim(),1716+        url: urlInput.value.trim(),1717+        username: usernameInput.value.trim(),1718+        password: pwInput.value,1719+        totp: totpInput.value.trim(),1720+        notes: notesInput.value,1721+      };1722+      const updateRes = (await chrome.runtime.sendMessage({1723+        type: "UPDATE_ENTRY",1724+        payload: { id: summary.id, patch },1725+      }).catch(() => null)) as { ok: boolean; error?: string } | null;1726+      if (!updateRes?.ok) {1727+        saveBtn.disabled = false;1728+        saveBtn.textContent = originalLabel;1729+        const err = document.createElement("div");1730+        err.className = "empty-hint";1731+        err.style.color = "color-mix(in srgb, #e55 70%, transparent)";1732+        err.textContent = updateRes?.error ?? "Couldn't save. Try the popup.";1733+        body.appendChild(err);1734+        setTimeout(() => err.remove(), 3000);1735+        return;1736+      }1737+      // Refresh the platter with updated summary fields1738+      const refreshed: SelectorEntry = {1739+        id: summary.id,1740+        title: patch.title as string,1741+        username: patch.username as string,1742+        url: patch.url as string,1743+        hasTotp: Boolean(patch.totp),1744+        hasPasskey: summary.hasPasskey,1745+      };1746+      renderSilverPlatter(refreshed);1747+    });1748+1749+    queueMicrotask(() => titleInput.focus());15801750   }15811751 15821752   function renderLocked() {

view file