"use client"; /** * Renders Disclosure Bureau markdown — including wiki-links `[[id]]` — * as semantic HTML with clickable navigation. * * Wiki-link resolution rules (matches CLAUDE.md §7): * [[doc-id/pNNN]] → /d//pNNN * [[people/]] → /e/people/ * [[org/]] → /e/organizations/ * [[loc/]] → /e/locations/ * [[event/]] → /e/events/ * [[uap/]] → /e/uap-objects/ * [[vehicle/]] → /e/vehicles/ * [[op/]] → /e/operations/ * [[concept/]] → /e/concepts/ * [[table/]] → /t/ * [[image/]] → /i/ * [[]] → /d/ * [[X|alias]] → same target, display text = alias */ import Link from "next/link"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; import { InlineCitation } from "./inline-citation"; import type { Components } from "react-markdown"; const CLASS_MAP: Record = { people: "people", person: "people", org: "organizations", organization: "organizations", organizations: "organizations", loc: "locations", location: "locations", locations: "locations", event: "events", events: "events", uap: "uap-objects", "uap-object": "uap-objects", "uap-objects": "uap-objects", vehicle: "vehicles", vehicles: "vehicles", op: "operations", operation: "operations", operations: "operations", concept: "concepts", concepts: "concepts", }; function resolveWikiLink(target: string): { href: string; entityClass?: string } { const t = target.trim(); // chunk anchor — [[doc-id/p007#c0042]] → /d/#c0042 (V2 page hosts the anchors) const chunkM = t.match(/^([A-Za-z0-9._-]+)\/(p\d{3})#(c\d{4})$/); if (chunkM) return { href: `/d/${chunkM[1]}#${chunkM[3]}` }; // alt form: [[doc-id#c0042]] const altChunkM = t.match(/^([A-Za-z0-9._-]+)#(c\d{4})$/); if (altChunkM) return { href: `/d/${altChunkM[1]}#${altChunkM[2]}` }; // table/, image/ if (t.startsWith("table/")) return { href: `/t/${t.slice(6)}` }; if (t.startsWith("image/")) return { href: `/i/${t.slice(6)}` }; // entity link / const m = t.match(/^([a-z-]+)\/([A-Za-z0-9._-]+)$/); if (m) { const cls = CLASS_MAP[m[1]]; if (cls) return { href: `/e/${cls}/${m[2]}`, entityClass: cls }; // doc-id/pNNN if (/^p\d{3}$/.test(m[2])) return { href: `/d/${m[1]}/${m[2]}` }; } // bare doc-id return { href: `/d/${t}` }; } /** * Pre-process raw markdown to convert `[[wiki|alias]]` and `[[wiki]]` syntax * into standard markdown link `[alias](/path)`. Simpler + more robust than * adding a remark plugin. */ function preprocessWikiLinks(md: string): string { // Allow `#anchor` inside the target part: [[doc-id/p007#c0042]] return md.replace(/\[\[([^\]|]+?)(?:\|([^\]]+))?\]\]/g, (_full, target: string, alias?: string) => { const { href } = resolveWikiLink(target); const label = (alias ?? target).trim(); return `[${label}](dbw:${href})`; }); } const CHUNK_HREF_RE = /^\/d\/([A-Za-z0-9._-]+)\#(c\d{4})$/; const components: Components = { a({ href, children, ...rest }) { const h = href ?? ""; if (h.startsWith("dbw:")) { const real = h.slice(4); // Chunk-anchor link → render as rich inline citation with bbox crop const chunkM = real.match(CHUNK_HREF_RE); if (chunkM) { const label = typeof children === "string" ? children : undefined; return ; } const target = real.split("/")[1] ?? real; // Pick a color class based on the URL prefix const cls = real.startsWith("/e/people") ? "wiki-link wiki-link--person" : real.startsWith("/e/organizations") ? "wiki-link wiki-link--org" : real.startsWith("/e/locations") ? "wiki-link wiki-link--loc" : real.startsWith("/e/events") ? "wiki-link wiki-link--event" : real.startsWith("/e/uap-objects") ? "wiki-link wiki-link--uap" : real.startsWith("/e/vehicles") ? "wiki-link wiki-link--vehicle" : real.startsWith("/e/operations") ? "wiki-link wiki-link--operation" : real.startsWith("/e/concepts") ? "wiki-link wiki-link--concept" : real.startsWith("/d/") ? "wiki-link wiki-link--doc" : "wiki-link"; return {children}; } return {children}; }, h1: ({ children }) =>

{children}

, h2: ({ children }) =>

{children}

, h3: ({ children }) =>

{children}

, h4: ({ children }) =>

{children}

, blockquote: ({ children }) =>
{children}
, table: ({ children }) =>
{children}
, code: ({ children, className }) => { const isBlock = (className ?? "").includes("language-"); return isBlock ? {children} : {children}; }, }; export function MarkdownBody({ children }: { children: string }) { const processed = preprocessWikiLinks(children); return (
{processed}
); }