import { useEffect, useMemo, useState } from "react"; import { docsApi } from "../api/endpoints"; import type { Doc } from "../api/types"; import { formatDate, Loading } from "./ui"; /** Searchable checklist of posts. `single` turns it into a radio list. */ export function DocPicker({ selected, onChange, single = false, exclude = [], max, }: { selected: string[]; onChange: (ids: string[]) => void; single?: boolean; exclude?: string[]; max?: number; }) { const [docs, setDocs] = useState(null); const [q, setQ] = useState(""); useEffect(() => { docsApi.list({ limit: 5000 }).then((p) => setDocs(p.items)); }, []); const visible = useMemo(() => { const skip = new Set(exclude); const needle = q.trim().toLowerCase(); return (docs ?? []).filter( (d) => !skip.has(d.id) && (!needle || d.title.toLowerCase().includes(needle) || (d.source_title ?? "").toLowerCase().includes(needle)), ); }, [docs, q, exclude]); if (!docs) return ; if (docs.length === 0) return

No posts yet. Connect a source first.

; const toggle = (id: string) => { if (single) return onChange([id]); if (selected.includes(id)) return onChange(selected.filter((x) => x !== id)); if (max && selected.length >= max) return; onChange([...selected, id]); }; return (
setQ(e.target.value)} aria-label="Search posts" /> {!single && (
{selected.length} selected{max ? ` of ${max}` : ""} {!max && ( )} {selected.length > 0 && ( )}
)}
    {visible.map((d) => (
  • ))} {visible.length === 0 &&
  • No posts match.
  • }
); }