/// import { Cell, lift, NAME, OpaqueRef, pattern, UI } from "commontools"; export type MentionableCharm = { [NAME]?: string; isHidden?: boolean; mentioned: MentionableCharm[]; backlinks: MentionableCharm[]; }; export type WriteableBacklinks = { mentioned: WriteableBacklinks[]; backlinks: Cell; }; type Input = { allCharms: MentionableCharm[]; }; type Output = { mentionable: MentionableCharm[]; }; const computeIndex = lift< { allCharms: WriteableBacklinks[] }, void >( ({ allCharms }) => { const cs = allCharms ?? []; for (const c of cs) { c.backlinks?.set([]); } for (const c of cs) { const mentions = c.mentioned ?? []; for (const m of mentions) { m?.backlinks?.push(c); } } }, ); /** * BacklinksIndex builds a map of backlinks across all charms and exposes a * unified mentionable list for consumers like editors. * * Behavior: * - Backlinks are computed by scanning each charm's `mentioned` list and * mapping mention target -> list of source charms. * - Mentionable list is a union of: * - every charm in `allCharms` * - any items a charm exports via a `mentionable` property * (either an array of charms or a Cell of such an array) * * The backlinks map is keyed by a charm's `content` value (falling back to * its `[NAME]`). This mirrors how existing note patterns identify notes when * computing backlinks locally. */ const computeMentionable = lift< { allCharms: MentionableCharm[] }, MentionableCharm[] >(({ allCharms: charmList }) => { const cs = charmList ?? []; const out: MentionableCharm[] = []; for (const c of cs) { out.push(c); const exported = (c as unknown as { mentionable?: MentionableCharm[] | { get?: () => MentionableCharm[] }; }).mentionable; if (Array.isArray(exported)) { for (const m of exported) if (m) out.push(m); } else if (exported && typeof (exported as any).get === "function") { const arr = (exported as { get: () => MentionableCharm[] }).get() ?? []; for (const m of arr) if (m) out.push(m); } } return out; }); const BacklinksIndex = pattern(({ allCharms }) => { computeIndex({ allCharms: allCharms as unknown as OpaqueRef, }); // Compute mentionable list from allCharms reactively const mentionable = computeMentionable({ allCharms }); return { [NAME]: "BacklinksIndex", [UI]: undefined, mentionable, }; }); export default BacklinksIndex;