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
↓ peach-browser-fb33e4.tar.gzatlas at this save
files changed7 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 −52manifest.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": {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",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": {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 */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>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 );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, "&")@@ -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 />