fb33e4

fix: note editor — Portal to #hd-shell, NoteEditorInline in Add, position:absolute (v1.4.54)

time
2026-04-23 13:34 UTC · 6 months ago
machine
koh2
parent
975a4e
7 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/popup/styles/popup.css+20 −1src/popup/views/Add.tsx+5 −7src/popup/views/Entry.tsx+9 −6src/popup/views/NoteEditor.tsx+81 −52
manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach",4-  "version": "1.4.53",4+  "version": "1.4.54",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.4.53",4+  "version": "1.4.54",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.4.53",3+  "version": "1.4.54",44   "description": "Zero-knowledge password manager",55   "type": "module",66   "scripts": {

view file

src/popup/styles/popup.css+20 −1
@@ -2234,7 +2234,7 @@22342234 22352235 /* ── Note editor overlay ── */22362236 .peach-note-editor {2237-  position: fixed;2237+  position: absolute;22382238   inset: 0;22392239   z-index: 200;22402240   display: flex;@@ -2361,6 +2361,25 @@23612361   color: var(--peach-t3);23622362   font-style: italic;23632363   pointer-events: none;2364+}2365+2366+/* Inline variant (Add.tsx) */2367+.peach-note-editor-inline {2368+  border: 1px solid var(--peach-t3);2369+  border-radius: 8px;2370+  overflow: hidden;2371+}2372+.peach-note-editor-inline .peach-note-toolbar {2373+  border-bottom: 1px solid var(--peach-t3);2374+  background: var(--peach-t4);2375+  padding: 5px 10px 4px;2376+}2377+.peach-note-editor-body--inline {2378+  min-height: 140px;2379+  max-height: 200px;2380+  font-size: 15px;2381+  padding: 10px 14px 12px;2382+  background: var(--peach-t4);23642383 }23652384 23662385 /* font size elements from execCommand */

view file

src/popup/views/Add.tsx+5 −7
@@ -2,6 +2,7 @@22 import type { VaultEntry } from "../../shared/types";33 import { sendMessage } from "../App";44 import { parseOtpauthURI } from "../../crypto/totp";5+import { NoteEditorInline } from "./NoteEditor";56 import { GeneratorDrawer } from "../components/GeneratorDrawer";67 78 interface AddProps {@@ -314,13 +315,10 @@314315 315316         {/* ── Note content ── */}316317         <Show when={selectedType() === "note"}>317-          <div class="peach-field-card" style="margin:4px 12px 0">318-            <span class="peach-label">content</span>319-            <textarea320-              class="peach-new-textarea"321-              placeholder="write your note here…"322-              value={notes()}323-              onInput={(e) => setNotes((e.currentTarget as HTMLTextAreaElement).value)}318+          <div style="margin:4px 12px 0">319+            <NoteEditorInline320+              initialContent={notes()}321+              onContentChange={setNotes}324322             />325323           </div>326324         </Show>

view file

src/popup/views/Entry.tsx+9 −6
@@ -1,4 +1,5 @@11 import { createEffect, createMemo, createSignal, For, onCleanup, Show } from "solid-js";2+import { Portal } from "solid-js/web";23 import type { VaultEntry } from "../../shared/types";34 import { getDisplayDomain } from "../../shared/domain";45 import { faviconUrl } from "../../shared/favicon";@@ -748,12 +749,14 @@748749       </div>749750 750751       <Show when={noteEditing()}>751-        <NoteEditor752-          initialContent={props.entry?.notes ?? ""}753-          saving={saving()}754-          onSave={saveNote}755-          onCancel={() => setNoteEditing(false)}756-        />752+        <Portal mount={document.getElementById("hd-shell") ?? document.body}>753+          <NoteEditor754+            initialContent={props.entry?.notes ?? ""}755+            saving={saving()}756+            onSave={saveNote}757+            onCancel={() => setNoteEditing(false)}758+          />759+        </Portal>757760       </Show>758761     </>759762   );

view file

src/popup/views/NoteEditor.tsx+81 −52
@@ -1,13 +1,6 @@11 import { onMount } from "solid-js";22 3-interface NoteEditorProps {4-  initialContent: string;5-  saving: boolean;6-  onSave: (html: string) => Promise<void>;7-  onCancel: () => void;8-}9-10-function toHtml(raw: string): string {3+export function toHtml(raw: string): string {114   if (/<\/?[a-z]/i.test(raw)) return raw;125   return raw136     .replace(/&/g, "&amp;")@@ -16,6 +9,39 @@169     .replace(/\n/g, "<br>");1710 }1811 12+function Toolbar(props: { exec: (cmd: string, val?: string) => void }) {13+  const e = props.exec;14+  return (15+    <div class="peach-note-toolbar">16+      <button class="peach-note-tool-btn peach-note-tool-bold"17+        onMouseDown={(ev) => { ev.preventDefault(); e("bold"); }}18+        title="Bold (⌘B)">B</button>19+      <button class="peach-note-tool-btn peach-note-tool-italic"20+        onMouseDown={(ev) => { ev.preventDefault(); e("italic"); }}21+        title="Italic (⌘I)">I</button>22+      <div class="peach-note-tool-divider" />23+      <button class="peach-note-tool-btn"24+        onMouseDown={(ev) => { ev.preventDefault(); e("fontSize", "1"); }}25+        title="Small">sm</button>26+      <button class="peach-note-tool-btn"27+        onMouseDown={(ev) => { ev.preventDefault(); e("fontSize", "3"); }}28+        title="Normal">md</button>29+      <button class="peach-note-tool-btn"30+        onMouseDown={(ev) => { ev.preventDefault(); e("fontSize", "5"); }}31+        title="Large">lg</button>32+    </div>33+  );34+}35+36+/* ── Full-popup overlay editor (Entry.tsx — editing existing notes) ── */37+38+interface NoteEditorProps {39+  initialContent: string;40+  saving: boolean;41+  onSave: (html: string) => Promise<void>;42+  onCancel: () => void;43+}44+1945 export function NoteEditor(props: NoteEditorProps) {2046   let editorRef: HTMLDivElement | undefined;2147 @@ -48,62 +74,65 @@4874   return (4975     <div class="peach-note-editor">5076       <div class="peach-note-editor-header">51-        <button52-          class="peach-note-editor-back"53-          onClick={props.onCancel}54-          disabled={props.saving}55-          aria-label="Cancel"56-        >57-          <svg width="7" height="12" viewBox="0 0 7 12" fill="none" stroke="currentColor" stroke-width="1.1" stroke-linecap="round" stroke-linejoin="round">77+        <button class="peach-note-editor-back" onClick={props.onCancel}78+          disabled={props.saving} aria-label="Cancel">79+          <svg width="7" height="12" viewBox="0 0 7 12" fill="none" stroke="currentColor"80+            stroke-width="1.1" stroke-linecap="round" stroke-linejoin="round">5881             <path d="M6 1L1 6L6 11" />5982           </svg>6083         </button>6184         <span class="peach-note-editor-label">note</span>62-        <button63-          class="peach-note-editor-save-btn"85+        <button class="peach-note-editor-save-btn"6486           onClick={() => void props.onSave(editorRef?.innerHTML ?? "")}65-          disabled={props.saving}66-        >87+          disabled={props.saving}>6788           {props.saving ? "saving…" : "save"}6889         </button>6990       </div>7091 71-      <div class="peach-note-toolbar">72-        <button73-          class="peach-note-tool-btn peach-note-tool-bold"74-          onMouseDown={(e) => { e.preventDefault(); exec("bold"); }}75-          aria-label="Bold"76-          title="Bold (⌘B)"77-        >B</button>78-        <button79-          class="peach-note-tool-btn peach-note-tool-italic"80-          onMouseDown={(e) => { e.preventDefault(); exec("italic"); }}81-          aria-label="Italic"82-          title="Italic (⌘I)"83-        >I</button>84-        <div class="peach-note-tool-divider" />85-        <button86-          class="peach-note-tool-btn"87-          onMouseDown={(e) => { e.preventDefault(); exec("fontSize", "1"); }}88-          title="Small"89-        >sm</button>90-        <button91-          class="peach-note-tool-btn"92-          onMouseDown={(e) => { e.preventDefault(); exec("fontSize", "3"); }}93-          title="Normal"94-        >md</button>95-        <button96-          class="peach-note-tool-btn"97-          onMouseDown={(e) => { e.preventDefault(); exec("fontSize", "5"); }}98-          title="Large"99-        >lg</button>100-      </div>92+      <Toolbar exec={exec} />10193 102-      <div103-        ref={editorRef}104-        class="peach-note-editor-body"94+      <div ref={editorRef} class="peach-note-editor-body"95+        contenteditable="true" onKeyDown={handleKeyDown}96+        aria-multiline="true" aria-label="Note content" />97+    </div>98+  );99+}100+101+/* ── Inline editor (Add.tsx — embedded in the new-entry form) ── */102+103+interface NoteEditorInlineProps {104+  initialContent: string;105+  onContentChange: (html: string) => void;106+}107+108+export function NoteEditorInline(props: NoteEditorInlineProps) {109+  let editorRef: HTMLDivElement | undefined;110+111+  onMount(() => {112+    if (!editorRef) return;113+    editorRef.innerHTML = toHtml(props.initialContent);114+    editorRef.focus();115+  });116+117+  function exec(cmd: string, value?: string) {118+    document.execCommand(cmd, false, value);119+    editorRef?.focus();120+  }121+122+  function handleKeyDown(e: KeyboardEvent) {123+    const mod = e.metaKey || e.ctrlKey;124+    if (mod && e.key === "b") { e.preventDefault(); exec("bold"); }125+    if (mod && e.key === "i") { e.preventDefault(); exec("italic"); }126+  }127+128+  return (129+    <div class="peach-note-editor-inline">130+      <Toolbar exec={exec} />131+      <div ref={editorRef}132+        class="peach-note-editor-body peach-note-editor-body--inline"105133         contenteditable="true"106134         onKeyDown={handleKeyDown}135+        onInput={() => props.onContentChange(editorRef?.innerHTML ?? "")}107136         aria-multiline="true"108137         aria-label="Note content"109138       />

view file