///
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;