1import { createSignal, For, Show } from "solid-js";2import type { VaultEntry } from "../../shared/types";3import { sendMessage } from "../../shared/bridge";4import { NoteRunes } from "../components/NoteRunes";5import { NoteRow } from "../components/NoteRow";67interface NotesTOCProps {8 notes: VaultEntry[];9 onBack: () => void;10 onOpenNote: (note: VaultEntry, rowEl: HTMLElement) => void;11 onNewNote: () => void;12 onRefresh: () => void;13}1415export function NotesTOC(props: NotesTOCProps) {16 const [searchVisible, setSearchVisible] = createSignal(false);17 const [searchQuery, setSearchQuery] = createSignal("");1819 let searchInputRef!: HTMLInputElement;2021 function filteredNotes() {22 const q = searchQuery().trim().toLowerCase();23 if (!q) return props.notes;24 return props.notes.filter((n) =>25 (n.title ?? "").toLowerCase().includes(q) ||26 (n.notes ?? "").toLowerCase().includes(q)27 );28 }2930 function showSearch() {31 setSearchVisible(true);32 setTimeout(() => searchInputRef?.focus(), 50);33 }3435 function clearSearch() {36 setSearchQuery("");37 setSearchVisible(false);38 }3940 async function handleDelete(note: VaultEntry) {41 await sendMessage({ type: "DELETE_NOTE", payload: { id: note.id } });42 props.onRefresh();43 }4445 async function handleArchive(note: VaultEntry) {46 47 await sendMessage({ type: "DELETE_ENTRY", payload: { id: note.id } });48 props.onRefresh();49 }5051 return (52 <div class="peach-notes-toc">53 <NoteRunes mode="toc" />5455 {}56 <div class="peach-notes-header">57 <Show58 when={!searchVisible()}59 fallback={60 <>61 <input62 ref={searchInputRef}63 class="peach-notes-search-input"64 placeholder="search notes…"65 value={searchQuery()}66 onInput={(e) => setSearchQuery((e.target as HTMLInputElement).value)}67 aria-label="Search notes"68 />69 <button class="peach-notes-header-btn" onClick={clearSearch} aria-label="Clear search">✕</button>70 </>71 }72 >73 <button class="peach-notes-back" onClick={props.onBack} aria-label="Back to vault">‹</button>74 <span class="peach-notes-header-title">notes.</span>75 <button class="peach-notes-header-btn" onClick={showSearch} aria-label="Search">76 <svg width="15" height="15" viewBox="0 0 15 15" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round">77 <circle cx="6.5" cy="6.5" r="4.25" />78 <path d="M10 10L13 13" />79 </svg>80 </button>81 </Show>82 </div>8384 {}85 <div class="peach-notes-list-container">86 <Show87 when={filteredNotes().length > 0}88 fallback={89 <div class="peach-notes-empty">90 <p class="peach-notes-empty-text">91 {searchQuery() ? "No notes match." : "No notes yet.\nStart writing."}92 </p>93 </div>94 }95 >96 <For each={filteredNotes()}>97 {(note, i) => (98 <NoteRow99 note={note}100 index={i()}101 searchQuery={searchQuery()}102 onTap={(n, el) => props.onOpenNote(n, el)}103 onDelete={handleDelete}104 onArchive={handleArchive}105 />106 )}107 </For>108 </Show>109110 <button class="peach-notes-new-btn" onClick={props.onNewNote}>111 + new note112 </button>113 </div>114 </div>115 );116}