363ddc594434
add + add tags option with inline tag editor on Entry detail view (v1.9.4)
- time
- 2026-04-25 01:33 UTC · 6 months ago
- machine
- koh2
- parent
- 99cb0735ea7f
↓ peach-browser-363ddc59.tar.gzatlas at this save
files changed5 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/popup/styles/popup.css+7 −0src/popup/views/Entry.tsx+107 −3manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach",4- "version": "1.9.3",4+ "version": "1.9.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": "1.9.3",4+ "version": "1.9.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": "1.9.3",3+ "version": "1.9.4",44 "description": "Zero-knowledge password manager",55 "type": "module",66 "scripts": {src/popup/styles/popup.css+7 −0
@@ -612,6 +612,13 @@612612 margin: -8px;613613 }614614 615+/* Inline tag editor on Entry detail view */616+.peach-inline-tag-editor {617+ display: flex;618+ flex-direction: column;619+ gap: 6px;620+}621+615622 /* Quiet inline "+ add X" link in the entry detail body */616623 .peach-entry-add-link {617624 justify-self: start;src/popup/views/Entry.tsx+107 −3
@@ -28,6 +28,9 @@2828 // Local draft for the additional URLs list, edited only while the URL field2929 // is in inline-edit mode. Saved alongside `url` when the user confirms.3030 const [additionalUrlsDraft, setAdditionalUrlsDraft] = createSignal<string[]>([]);31+ const [editingTags, setEditingTags] = createSignal(false);32+ const [tagsDraft, setTagsDraft] = createSignal<string[]>([]);33+ const [tagInput, setTagInput] = createSignal("");3134 const [showCardNumber, setShowCardNumber] = createSignal(false);3235 const [showCardCvc, setShowCardCvc] = createSignal(false);3336 const [totpTick, setTotpTick] = createSignal(0);@@ -178,6 +181,40 @@178181 }179182 }180183 184+ function startEditTags() {185+ setTagsDraft([...(props.entry?.tags ?? [])]);186+ setTagInput("");187+ setEditingTags(true);188+ }189+190+ function addTag(value: string) {191+ const n = value.toLowerCase().trim();192+ if (!n || n.length > 32 || tagsDraft().includes(n) || tagsDraft().length >= 10) return;193+ setTagsDraft([...tagsDraft(), n]);194+ setTagInput("");195+ }196+197+ async function saveTags() {198+ if (tagInput()) addTag(tagInput());199+ const entry = props.entry;200+ if (!entry) return;201+ const next = tagsDraft().length > 0 ? tagsDraft() : undefined;202+ setEditingTags(false);203+ setSaving(true);204+ try {205+ await sendMessage({206+ type: "UPDATE_ENTRY",207+ payload: { id: entry.id, patch: { tags: next } },208+ });209+ triggerRing();210+ await props.onFieldSaved();211+ } catch {212+ // silent213+ } finally {214+ setSaving(false);215+ }216+ }217+181218 function cancelEdit() {182219 setEditingField(null);183220 setDraft("");@@ -514,9 +551,13 @@514551 </div>515552 </Show>516553 517- {/* ── Tags ─────────────────────────────────────────── */}518- <Show when={entry().tags && entry().tags!.length > 0}>519- <div style="display:flex;flex-wrap:wrap;gap:5px;margin:8px 0;position:relative;z-index:1;">554+ {/* ── Tags — read-only chips (editing state lives at bottom) */}555+ <Show when={!editingTags() && entry().tags && entry().tags!.length > 0}>556+ <div557+ style="display:flex;flex-wrap:wrap;gap:5px;margin:2px 0;position:relative;z-index:1;cursor:pointer;"558+ onClick={startEditTags}559+ title="Click to edit tags"560+ >520561 <For each={entry().tags!}>521562 {(tag) => <span class="peach-tag-chip" style="font-size:10px;padding:2px 8px;">{tag}</span>}522563 </For>@@ -929,6 +970,69 @@929970 </div>930971 </Show>931972 </section>973+ </Show>974+ </Show>975+976+ {/* ── Tags editor / add-tags link ──────────────────── */}977+ <Show when={entry().type !== "passkey"}>978+ <Show979+ when={editingTags()}980+ fallback={981+ <Show when={!entry().tags || entry().tags!.length === 0}>982+ <button983+ class="peach-add-field-link peach-entry-add-link"984+ onClick={startEditTags}985+ >986+ + add tags987+ </button>988+ </Show>989+ }990+ >991+ <div992+ class="peach-inline-tag-editor"993+ onFocusOut={(e) => {994+ const next = e.relatedTarget;995+ if (next instanceof Node && e.currentTarget.contains(next)) return;996+ void saveTags();997+ }}998+ >999+ <span class="peach-label">tags</span>1000+ <div class="peach-tag-input-wrap">1001+ <For each={tagsDraft()}>1002+ {(tag) => (1003+ <span class="peach-tag-chip">1004+ {tag}1005+ <button1006+ class="peach-tag-chip-remove"1007+ onMouseDown={(e) => e.preventDefault()}1008+ onClick={() => setTagsDraft(tagsDraft().filter(t => t !== tag))}1009+ aria-label={`Remove ${tag}`}1010+ >×</button>1011+ </span>1012+ )}1013+ </For>1014+ <input1015+ class="peach-tag-input"1016+ placeholder={tagsDraft().length === 0 ? "type and press Enter…" : ""}1017+ value={tagInput()}1018+ onInput={(e) => setTagInput(e.currentTarget.value)}1019+ onKeyDown={(e) => {1020+ if (e.key === "Enter" || e.key === ",") {1021+ e.preventDefault();1022+ addTag(tagInput());1023+ } else if (e.key === "Backspace" && !tagInput() && tagsDraft().length > 0) {1024+ setTagsDraft(tagsDraft().slice(0, -1));1025+ } else if (e.key === "Escape") {1026+ e.preventDefault();1027+ setEditingTags(false);1028+ setTagInput("");1029+ }1030+ }}1031+ ref={(el) => requestAnimationFrame(() => el?.focus())}1032+ style="flex:1;min-width:80px;background:transparent;border:none;outline:none;color:var(--t1);font-size:12px;padding:2px;"1033+ />1034+ </div>1035+ </div>9321036 </Show>9331037 </Show>9341038