"use client"; import type { VideoProjectLink } from "./AddToVideo"; import Link from "next/link"; import { useCallback, useEffect, useRef, useState } from "react"; import type { NotesRead } from "@/lib/annotations/types"; import { useNotes } from "@/lib/annotations/useNotes"; import EvidencePanel, { useEvidence, type EvidenceHandle } from "./EvidencePanel"; import { Composer, NoteCard } from "./NoteCards"; // Every citation of one article, one per screen: the evidence on the left, its // notes on the right. j/k (or ←/→) move, space plays, n notes it, Esc cancels. // The citation in view is in the URL (`?c=`), so a walk can be resumed. export type WalkItem = { id: string; number: number; kind: string; quote: string }; export default function EvidenceWalk({ site, report, items, initial, initialNotes, videoProjects = [], }: { site: string; report: string; items: WalkItem[]; initial: number; initialNotes: NotesRead; videoProjects?: VideoProjectLink[]; }) { const [idx, setIdx] = useState(initial); const [composing, setComposing] = useState(false); const item = items[idx]; const { ev, error } = useEvidence(site, report, item?.id ?? null); const { notes, write, busy, error: notesError } = useNotes({ article: `${site}/${report}` }, { initial: initialNotes }); const panel = useRef(null); const go = useCallback( (to: number) => { const next = Math.max(0, Math.min(items.length - 1, to)); setIdx(next); setComposing(false); const url = new URL(window.location.href); url.searchParams.set("c", items[next].id); window.history.replaceState(null, "", url); }, [items], ); useEffect(() => { const onKey = (e: KeyboardEvent) => { const t = e.target as HTMLElement | null; if (t?.closest("input, textarea, select, [contenteditable=true]") || e.metaKey || e.ctrlKey || e.altKey) return; if (e.key === "j" || e.key === "ArrowRight") { e.preventDefault(); go(idx + 1); } else if (e.key === "k" || e.key === "ArrowLeft") { e.preventDefault(); go(idx - 1); } else if (e.key === " ") { e.preventDefault(); panel.current?.togglePlay(); } else if (e.key === "n") { e.preventDefault(); setComposing(true); } else if (e.key === "Escape") { setComposing(false); } }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [idx, go]); if (!item) return

no citations

; const mine = notes.filter((n) => n.anchor.kind === "cite" && n.anchor.cite === item.id); return (
{idx + 1} / {items.length} {item.kind} space plays · n notes
notes on [{item.number}]
{notesError &&

{notesError}

} {mine.length > 0 && (
    {mine.map((n) => ( ))}
)} {composing ? ( { const note = await write({ op: "add", text, anchor: { kind: "cite", cite: item.id } }); if (note) setComposing(false); }} onCancel={() => setComposing(false)} /> ) : ( )}
← article
); }