NotesTOC.tsx

4.4 KB · raw

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  /** Note being expanded — other rows should fade away */14  expandingNoteId?: string | null;15}1617export function NotesTOC(props: NotesTOCProps) {18  const [searchVisible, setSearchVisible] = createSignal(false);19  const [searchQuery, setSearchQuery] = createSignal("");2021  let searchInputRef!: HTMLInputElement;2223  function filteredNotes() {24    const q = searchQuery().trim().toLowerCase();25    if (!q) return props.notes;26    return props.notes.filter((n) =>27      (n.title ?? "").toLowerCase().includes(q) ||28      (n.notes ?? "").toLowerCase().includes(q)29    );30  }3132  function showSearch() {33    setSearchVisible(true);34    setTimeout(() => searchInputRef?.focus(), 50);35  }3637  function clearSearch() {38    setSearchQuery("");39    setSearchVisible(false);40  }4142  async function handleDelete(note: VaultEntry) {43    await sendMessage({ type: "DELETE_NOTE", payload: { id: note.id } });44    props.onRefresh();45  }4647  async function handleArchive(note: VaultEntry) {48    // Archive = soft delete with a tag — for now just delete to trash49    await sendMessage({ type: "DELETE_ENTRY", payload: { id: note.id } });50    props.onRefresh();51  }5253  return (54    <div class="peach-notes-toc">55      <NoteRunes mode="toc" />5657      {/* Header */}58      <div class="peach-notes-header">59        <Show60          when={!searchVisible()}61          fallback={62            <>63              <input64                ref={searchInputRef}65                class="peach-notes-search-input"66                placeholder="search notes…"67                value={searchQuery()}68                onInput={(e) => setSearchQuery((e.target as HTMLInputElement).value)}69                aria-label="Search notes"70              />71              <button class="peach-notes-header-btn" onClick={clearSearch} aria-label="Clear search">✕</button>72            </>73          }74        >75          <button class="peach-notes-back" onClick={props.onBack} aria-label="Back to vault">‹</button>76          <span class="peach-notes-header-title">notes.</span>77          <button class="peach-notes-header-btn" onClick={showSearch} aria-label="Search">78            <svg width="15" height="15" viewBox="0 0 15 15" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round">79              <circle cx="6.5" cy="6.5" r="4.25" />80              <path d="M10 10L13 13" />81            </svg>82          </button>83        </Show>84      </div>8586      {/* Note list */}87      <div class="peach-notes-list-container">88        <Show89          when={filteredNotes().length > 0}90          fallback={91            <div class="peach-notes-empty">92              <p class="peach-notes-empty-text">93                {searchQuery() ? "No notes match." : "No notes yet.\nStart writing."}94              </p>95            </div>96          }97        >98          <For each={filteredNotes()}>99            {(note, i) => {100              const expanding = () => props.expandingNoteId && props.expandingNoteId !== note.id;101              const expandingNote = () => props.expandingNoteId === note.id;102              return (103                <NoteRow104                  note={note}105                  index={i()}106                  searchQuery={searchQuery()}107                  onTap={(n, el) => props.onOpenNote(n, el)}108                  onDelete={handleDelete}109                  onArchive={handleArchive}110                  fadingClass={111                    expanding()112                      ? (i() < (filteredNotes().findIndex(n => n.id === props.expandingNoteId))113                          ? "is-expanding-away is-expanding-away-up"114                          : "is-expanding-away is-expanding-away-down")115                      : expandingNote()116                        ? "is-expanding-target"117                        : undefined118                  }119                />120              );121            }}122          </For>123        </Show>124125        <button class="peach-notes-new-btn" onClick={props.onNewNote}>126          + new note127        </button>128      </div>129    </div>130  );131}