1import { createSignal, For, Show } from "solid-js";2import type { VaultEntry } from "../../shared/types";3import { sendMessage } from "../../shared/bridge";4import { NoteRow } from "../components/NoteRow";56interface NotesTOCProps {7 notes: VaultEntry[];8 onBack: () => void;9 onOpenNote: (note: VaultEntry, rowEl: HTMLElement) => void;10 onNewNote: () => void;11 onRefresh: () => void;12 13 expandingNoteId?: string | null;14}1516export function NotesTOC(props: NotesTOCProps) {17 const [searchVisible, setSearchVisible] = createSignal(false);18 const [searchQuery, setSearchQuery] = createSignal("");1920 let searchInputRef!: HTMLInputElement;2122 function filteredNotes() {23 const q = searchQuery().trim().toLowerCase();24 if (!q) return props.notes;25 return props.notes.filter((n) =>26 (n.title ?? "").toLowerCase().includes(q) ||27 (n.notes ?? "").toLowerCase().includes(q)28 );29 }3031 function showSearch() {32 setSearchVisible(true);33 setTimeout(() => searchInputRef?.focus(), 50);34 }3536 function clearSearch() {37 setSearchQuery("");38 setSearchVisible(false);39 }4041 async function handleDelete(note: VaultEntry) {42 await sendMessage({ type: "DELETE_NOTE", payload: { id: note.id } });43 props.onRefresh();44 }4546 async function handleArchive(note: VaultEntry) {47 48 await sendMessage({ type: "DELETE_ENTRY", payload: { id: note.id } });49 props.onRefresh();50 }5152 return (53 <div class="peach-notes-toc">54 {}55 <div class="peach-notes-header">56 <Show57 when={!searchVisible()}58 fallback={59 <>60 <input61 ref={searchInputRef}62 class="peach-notes-search-input"63 placeholder="search notes…"64 value={searchQuery()}65 onInput={(e) => setSearchQuery((e.target as HTMLInputElement).value)}66 aria-label="Search notes"67 />68 <button class="peach-notes-header-btn" onClick={clearSearch} aria-label="Clear search">✕</button>69 </>70 }71 >72 <button class="peach-notes-back" onClick={props.onBack} aria-label="Back to vault">‹</button>73 <span class="peach-notes-header-title">notes.</span>74 <button75 class="peach-notes-header-btn peach-notes-new-btn-icon"76 onClick={props.onNewNote}77 aria-label="New note"78 title="New note"79 >+</button>80 <button class="peach-notes-header-btn peach-notes-search-btn" onClick={showSearch} aria-label="Search">81 <svg width="15" height="15" viewBox="0 0 15 15" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round">82 <circle cx="6.5" cy="6.5" r="4.25" />83 <path d="M10 10L13 13" />84 </svg>85 </button>86 </Show>87 </div>8889 {}90 <div class="peach-notes-list-container">91 <Show92 when={filteredNotes().length > 0}93 fallback={94 <div class="peach-notes-empty">95 <p class="peach-notes-empty-text">96 {searchQuery() ? "No notes match." : "No notes yet.\nStart writing."}97 </p>98 </div>99 }100 >101 <For each={filteredNotes()}>102 {(note) => {103 104 const permanentIndex = () => props.notes.findIndex((n) => n.id === note.id);105 const expandingIdx = () => props.notes.findIndex((n) => n.id === props.expandingNoteId);106 const expanding = () => props.expandingNoteId && props.expandingNoteId !== note.id;107 const expandingNote = () => props.expandingNoteId === note.id;108 return (109 <NoteRow110 note={note}111 index={permanentIndex()}112 searchQuery={searchQuery()}113 onTap={(n, el) => props.onOpenNote(n, el)}114 onDelete={handleDelete}115 onArchive={handleArchive}116 fadingClass={117 expanding()118 ? (permanentIndex() < expandingIdx()119 ? "is-expanding-away is-expanding-away-up"120 : "is-expanding-away is-expanding-away-down")121 : expandingNote()122 ? "is-expanding-target"123 : undefined124 }125 />126 );127 }}128 </For>129 </Show>130 </div>131 </div>132 );133}