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
5 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/popup/styles/popup.css+7 −0src/popup/views/Entry.tsx+107 −3
manifest.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": {

view file

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

view file

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

view file

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;

view file

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 

view file