NoteEditor.tsx

3.3 KB · raw

1import { onMount } from "solid-js";23interface NoteEditorProps {4  initialContent: string;5  saving: boolean;6  onSave: (html: string) => Promise<void>;7  onCancel: () => void;8}910function toHtml(raw: string): string {11  if (/<\/?[a-z]/i.test(raw)) return raw;12  return raw13    .replace(/&/g, "&amp;")14    .replace(/</g, "&lt;")15    .replace(/>/g, "&gt;")16    .replace(/\n/g, "<br>");17}1819export function NoteEditor(props: NoteEditorProps) {20  let editorRef: HTMLDivElement | undefined;2122  onMount(() => {23    if (!editorRef) return;24    editorRef.innerHTML = toHtml(props.initialContent);25    editorRef.focus();26    const sel = window.getSelection();27    if (sel) {28      const range = document.createRange();29      range.selectNodeContents(editorRef);30      range.collapse(false);31      sel.removeAllRanges();32      sel.addRange(range);33    }34  });3536  function exec(cmd: string, value?: string) {37    document.execCommand(cmd, false, value);38    editorRef?.focus();39  }4041  function handleKeyDown(e: KeyboardEvent) {42    const mod = e.metaKey || e.ctrlKey;43    if (mod && e.key === "b") { e.preventDefault(); exec("bold"); return; }44    if (mod && e.key === "i") { e.preventDefault(); exec("italic"); return; }45    if (e.key === "Escape") { e.preventDefault(); props.onCancel(); }46  }4748  return (49    <div class="peach-note-editor">50      <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">58            <path d="M6 1L1 6L6 11" />59          </svg>60        </button>61        <span class="peach-note-editor-label">note</span>62        <button63          class="peach-note-editor-save-btn"64          onClick={() => void props.onSave(editorRef?.innerHTML ?? "")}65          disabled={props.saving}66        >67          {props.saving ? "saving…" : "save"}68        </button>69      </div>7071      <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>101102      <div103        ref={editorRef}104        class="peach-note-editor-body"105        contenteditable="true"106        onKeyDown={handleKeyDown}107        aria-multiline="true"108        aria-label="Note content"109      />110    </div>111  );112}