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
↓ peach-browser-ce561ac4.tar.gzatlas at this save
files changed4 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/content/inject.ts+178 −8manifest.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": {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",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": {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() {