// Annotation records and rendering for the unified library page. export interface Annotation { uri: string; rkey: string; target: { source: string; selector?: { exact?: string } }; body?: { value?: string }; createdAt?: string; } export async function listAnnotations(): Promise { const did = process.env.CAMERON_DID || "did:plc:gfrmhdmjvxn2sjedzboeudef"; const pds = "https://bsky.social"; let cursor: string | undefined; const all: Annotation[] = []; do { const url = new URL(`${pds}/xrpc/com.atproto.repo.listRecords`); url.searchParams.set("repo", did); url.searchParams.set("collection", "at.margin.annotation"); url.searchParams.set("limit", "100"); if (cursor) url.searchParams.set("cursor", cursor); const res = await fetch(url.toString()); if (!res.ok) break; const data = await res.json(); for (const r of data.records ?? []) { const rkey = r.uri.split("/").pop()!; all.push({ uri: r.uri, rkey, target: r.value.target ?? {}, body: r.value.body, createdAt: r.value.createdAt ?? r.value.created, }); } cursor = data.cursor; } while (cursor); // Sort newest first return all.sort( (a, b) => new Date(b.createdAt ?? 0).getTime() - new Date(a.createdAt ?? 0).getTime() ); } function displayUrl(url: string): string { try { const u = new URL(url); return u.hostname + (u.pathname === "/" ? "" : u.pathname); } catch { return url; } } export function AnnotationList({ annotations }: { annotations: Annotation[] }) { return (
{annotations.map((a) => { const date = a.createdAt ? new Date(a.createdAt).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", }) : ""; return (
{a.target.selector?.exact && (
{a.target.selector.exact}
)} {a.body?.value && (

{a.body.value}

)}
{date && {date}} record
); })}
); }