/** * Renders a single `[[wiki-link]]` string as a colored Link. * Accepts either the raw `[[id]]` form OR an already-stripped `id`. */ import Link from "next/link"; 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", }; const COLOR_BY_CLASS: Record = { people: "text-[#ff6ec7] border-[rgba(255,110,199,0.35)] hover:bg-[rgba(255,110,199,0.08)]", organizations: "text-[#ff8a4d] border-[rgba(255,138,77,0.35)] hover:bg-[rgba(255,138,77,0.08)]", locations: "text-[#3fde6a] border-[rgba(63,222,106,0.35)] hover:bg-[rgba(63,222,106,0.08)]", events: "text-[#ffa500] border-[rgba(255,165,0,0.35)] hover:bg-[rgba(255,165,0,0.08)]", "uap-objects": "text-[#ff3344] border-[rgba(255,51,68,0.45)] hover:bg-[rgba(255,51,68,0.08)] font-semibold", vehicles: "text-[#5b9bd5] border-[rgba(91,155,213,0.35)] hover:bg-[rgba(91,155,213,0.08)]", operations: "text-[#9b5de5] border-[rgba(155,93,229,0.35)] hover:bg-[rgba(155,93,229,0.08)]", concepts: "text-[#06d6a0] border-[rgba(6,214,160,0.35)] hover:bg-[rgba(6,214,160,0.08)]", document: "text-[#f5c542] border-[rgba(245,197,66,0.35)] hover:bg-[rgba(245,197,66,0.08)]", page: "text-[#7fdbff] border-[rgba(127,219,255,0.35)] hover:bg-[rgba(127,219,255,0.08)]", table: "text-[#1e9eb5] border-[rgba(30,158,181,0.35)]", image: "text-[#ffeb99] border-[rgba(255,235,153,0.35)]", unknown: "text-[#c8d4e6] border-[rgba(127,219,255,0.18)]", }; export interface WikiLinkResolved { href: string; cls: keyof typeof COLOR_BY_CLASS; display: string; } /** Parses `[[X]]`, `[[X|alias]]`, or bare `X` into a route + display. */ export function resolveWikiLink(input: string): WikiLinkResolved { let target = input.trim(); let alias: string | undefined; const m = target.match(/^\[\[(.+?)\]\]$/); if (m) target = m[1]; if (target.includes("|")) { const [t, a] = target.split("|"); target = t; alias = a; } const display = (alias ?? target).trim(); if (target.startsWith("table/")) return { href: `/t/${target.slice(6)}`, cls: "table", display }; if (target.startsWith("image/")) return { href: `/i/${target.slice(6)}`, cls: "image", display }; // doc-id/pNNN const pageMatch = target.match(/^([a-z0-9-]+)\/p\d{3}$/); if (pageMatch) return { href: `/d/${target}`, cls: "page", display }; // class/id const slash = target.match(/^([a-z-]+)\/([A-Za-z0-9._-]+)$/); if (slash) { const cls = CLASS_MAP[slash[1]]; if (cls) return { href: `/e/${cls}/${slash[2]}`, cls: cls as keyof typeof COLOR_BY_CLASS, display }; } // bare doc-id return { href: `/d/${target}`, cls: "document", display }; } export function FmWikiLink({ target, size = "sm" }: { target: string; size?: "xs" | "sm" | "md" }) { const { href, cls, display } = resolveWikiLink(target); const sz = size === "xs" ? "text-[10px] px-1.5 py-0.5" : size === "md" ? "text-sm px-2.5 py-1" : "text-xs px-2 py-0.5"; return ( {display} ); } export function FmWikiLinkList({ items, size = "sm", emptyLabel = "—", max = 64, }: { items: string[] | undefined; size?: "xs" | "sm" | "md"; emptyLabel?: string; max?: number; }) { if (!items || items.length === 0) return {emptyLabel}; const shown = items.slice(0, max); const overflow = items.length - shown.length; return (
{shown.map((t, i) => )} {overflow > 0 && ( +{overflow} )}
); }