import { useMemo, useState } from 'react'; import type { Campaign, Note } from '@/lib/schemas'; import { notesRepo } from '@/lib/db/repositories'; import { useDebouncedCallback } from '@/lib/useDebouncedCallback'; import { extractWikiLinks, splitWikiLinks } from '@/lib/wikilinks'; import { useNotes } from './hooks'; import { Page, PageHeader, EmptyState, RequireCampaign } from '@/components/ui/Page'; import { Button } from '@/components/ui/Button'; import { Input } from '@/components/ui/Input'; import { useConfirm } from '@/components/ui/useConfirm'; import { cn } from '@/lib/cn'; export function NotesPage() { return {(c) => }; } function Notes({ campaign }: { campaign: Campaign }) { const notes = useNotes(campaign.id); const [selectedId, setSelectedId] = useState(null); const [query, setQuery] = useState(''); const selected = notes.find((n) => n.id === selectedId) ?? null; const filtered = useMemo(() => { const q = query.trim().toLowerCase(); const list = q ? notes.filter((n) => n.title.toLowerCase().includes(q) || n.body.toLowerCase().includes(q) || n.tags.some((t) => t.toLowerCase().includes(q))) : notes; return [...list].sort((a, b) => a.title.localeCompare(b.title)); }, [notes, query]); const createNote = async (title = 'Untitled') => { const note = await notesRepo.create(campaign.id, title); setSelectedId(note.id); }; const openByTitle = async (title: string) => { const existing = notes.find((n) => n.title.toLowerCase() === title.toLowerCase()); if (existing) setSelectedId(existing.id); else await createNote(title); }; return ( createNote()}>+ New note} /> {notes.length === 0 ? ( createNote()}>+ New note} /> ) : (
setQuery(e.target.value)} placeholder="Search notes…" aria-label="Search notes" />
    {filtered.map((n) => (
  • ))}
{selected ? ( ) : (
Select a note to edit.
)}
)}
); } function NoteEditor({ note, allNotes, onOpenTitle, onSelect, }: { note: Note; allNotes: Note[]; onOpenTitle: (title: string) => void; onSelect: (id: string | null) => void; }) { const [title, setTitle] = useState(note.title); const [body, setBody] = useState(note.body); const [tags, setTags] = useState(note.tags.join(', ')); const { confirm, confirmElement } = useConfirm(); // Save the full snapshot so editing title + body quickly doesn't drop an edit. const save = useDebouncedCallback((next: { title: string; body: string; tags: string }) => { void notesRepo.update(note.id, { title: next.title, body: next.body, tags: next.tags.split(',').map((t) => t.trim()).filter(Boolean), }); }, 350); const persist = (over: Partial<{ title: string; body: string; tags: string }>) => save({ title, body, tags, ...over }); const backlinks = allNotes.filter( (n) => n.id !== note.id && extractWikiLinks(n.body).some((t) => t.toLowerCase() === note.title.toLowerCase()), ); const titlesLower = new Set(allNotes.map((n) => n.title.toLowerCase())); return (
{ setTitle(e.target.value); persist({ title: e.target.value }); }} />
{confirmElement}