Files
archicratie-edition/src/lib/glossary.ts
Archicratia 24bbfbc17f
All checks were successful
SMOKE / smoke (push) Successful in 6s
CI / build-and-anchors (push) Successful in 47s
CI / build-and-anchors (pull_request) Successful in 46s
feat(glossaire): deduplicate entry aside relation groups
2026-03-26 20:24:49 +01:00

608 lines
16 KiB
TypeScript

import type { CollectionEntry } from "astro:content";
export type GlossaryEntry = CollectionEntry<"glossaire">;
export type GlossaryPortalLink = {
href: string;
label: string;
};
export type GlossaryRelationSection = {
title: string;
items: GlossaryEntry[];
};
export type GlossaryRelationBlock = GlossaryRelationSection & {
className: string;
};
export type GlossaryHomeStats = {
totalEntries: number;
paradigmesCount: number;
doctrinesCount: number;
metaRegimesCount: number;
};
export type GlossaryEntryAsideData = {
displayFamily: string;
displayDomain: string;
displayLevel: string;
showNoyau: boolean;
showSameFamily: boolean;
fondamentaux: GlossaryEntry[];
sameFamilyTitle: string;
sameFamilyEntries: GlossaryEntry[];
relationSections: GlossaryRelationSection[];
contextualTheory: GlossaryEntry[];
};
export type GlossaryHomeData = {
fondamentaux: GlossaryEntry[];
scenes: GlossaryEntry[];
dynamiques: GlossaryEntry[];
metaRegimes: GlossaryEntry[];
metaRegimesPreview: GlossaryEntry[];
arcalite?: GlossaryEntry;
cratialite?: GlossaryEntry;
tension?: GlossaryEntry;
sceneDepreuve?: GlossaryEntry;
archicration?: GlossaryEntry;
};
export const GLOSSARY_COLLATOR = new Intl.Collator("fr", {
sensitivity: "base",
numeric: true,
});
export const FAMILY_LABELS: Record<string, string> = {
"concept-fondamental": "Concept fondamental",
scene: "Scène",
dynamique: "Dynamique",
pathologie: "Pathologie",
topologie: "Topologie",
"meta-regime": "Méta-régime",
paradigme: "Paradigme",
doctrine: "Doctrine",
verbe: "Verbe",
"dispositif-ia": "Dispositif IA",
"tension-irreductible": "Tension irréductible",
figure: "Figure",
qualification: "Qualification",
epistemologie: "Épistémologie",
};
export const KIND_LABELS: Record<string, string> = {
concept: "Concept",
diagnostic: "Diagnostic",
topologie: "Topologie",
verbe: "Verbe",
paradigme: "Paradigme",
doctrine: "Doctrine",
dispositif: "Dispositif",
figure: "Figure",
qualification: "Qualification",
epistemologie: "Épistémologie",
};
export const DOMAIN_LABELS: Record<string, string> = {
transversal: "Transversal",
theorie: "Théorie",
"cas-ia": "Cas IA",
};
export const LEVEL_LABELS: Record<string, string> = {
fondamental: "Fondamental",
intermediaire: "Intermédiaire",
avance: "Avancé",
};
export const FONDAMENTAUX_WANTED = [
"archicratie",
"arcalite",
"cratialite",
"archicration",
"co-viabilite",
"tension",
] as const;
export const FAMILY_SECTION_TITLES: Record<string, string> = {
"concept-fondamental": "Noyau archicratique",
scene: "Scènes archicratiques",
dynamique: "Dynamiques archicratiques",
pathologie: "Pathologies archicratiques",
topologie: "Topologies voisines",
"meta-regime": "Méta-régimes archicratiques",
paradigme: "Paradigmes voisins",
doctrine: "Doctrines fondatrices",
verbe: "Verbes de la scène",
"dispositif-ia": "Dispositifs IA",
"tension-irreductible": "Tensions irréductibles",
figure: "Figures archicratiques",
qualification: "Qualifications archicratiques",
epistemologie: "Outillage épistémologique",
};
const PREFERRED_PARADIGME_SLUGS = [
"gouvernementalite",
"gouvernementalite-algorithmique",
"cybernetique",
"biopolitique",
"domination-legale-rationnelle",
"democratie-deliberative",
"gouvernance-des-communs",
"agencement-machinique",
"pharmacologie-technique",
"preemption-algorithmique",
"dissensus-politique",
"lieu-vide-du-pouvoir",
"habitus-et-violence-symbolique",
"theorie-de-la-resonance",
"conatus-et-multitude",
"configuration-et-interdependance",
"technodiversite-et-cosmotechnie",
"grammatisation-et-proletarisation-cognitive",
] as const;
const PREFERRED_DOCTRINE_SLUGS = [
"contractualisme-hobbesien",
"droit-naturel-et-propriete",
"volonte-generale",
"decisionnisme-souverain",
] as const;
export function normalizeGlossarySlug(value: unknown): string {
return String(value ?? "")
.trim()
.replace(/^\/+|\/+$/g, "")
.replace(/\.(md|mdx)$/i, "")
.toLowerCase();
}
export function slugOfGlossaryEntry(
entry: Pick<GlossaryEntry, "id"> | null | undefined,
): string {
return normalizeGlossarySlug(entry?.id ?? "");
}
export function hrefOfGlossaryEntry(
entry: Pick<GlossaryEntry, "id"> | null | undefined,
): string {
const slug = slugOfGlossaryEntry(entry);
return slug ? `/glossaire/${slug}/` : "/glossaire/";
}
export function buildGlossaryBySlug(
entries: GlossaryEntry[] = [],
): Map<string, GlossaryEntry> {
return new Map(entries.map((entry) => [slugOfGlossaryEntry(entry), entry]));
}
export function sortGlossaryEntries(
entries: GlossaryEntry[] = [],
): GlossaryEntry[] {
return [...entries].sort((a, b) =>
GLOSSARY_COLLATOR.compare(a.data.term, b.data.term),
);
}
export function uniqueGlossaryEntries(
entries: GlossaryEntry[] = [],
): GlossaryEntry[] {
const seen = new Set<string>();
const out: GlossaryEntry[] = [];
for (const entry of entries) {
const slug = slugOfGlossaryEntry(entry);
if (!slug || seen.has(slug)) continue;
seen.add(slug);
out.push(entry);
}
return out;
}
export function slugsOfGlossaryEntries(
entries: GlossaryEntry[] = [],
): Set<string> {
const slugs = new Set<string>();
for (const entry of entries) {
const slug = slugOfGlossaryEntry(entry);
if (!slug) continue;
slugs.add(slug);
}
return slugs;
}
export function excludeGlossaryEntries(
entries: GlossaryEntry[] = [],
excluded: Iterable<string> = [],
): GlossaryEntry[] {
const excludedSlugs = new Set(
Array.from(excluded)
.map((value) => normalizeGlossarySlug(value))
.filter(Boolean),
);
return entries.filter((entry) => {
const slug = slugOfGlossaryEntry(entry);
return Boolean(slug) && !excludedSlugs.has(slug);
});
}
export function resolveGlossaryEntriesInSourceOrder(
slugs: string[] = [],
allEntries: GlossaryEntry[] = [],
): GlossaryEntry[] {
const bySlug = buildGlossaryBySlug(allEntries);
const seen = new Set<string>();
const resolved: GlossaryEntry[] = [];
for (const rawSlug of slugs) {
const slug = normalizeGlossarySlug(rawSlug);
if (!slug || seen.has(slug)) continue;
const entry = bySlug.get(slug);
if (!entry) continue;
seen.add(slug);
resolved.push(entry);
}
return resolved;
}
export function resolveGlossaryEntries(
slugs: string[] = [],
allEntries: GlossaryEntry[] = [],
): GlossaryEntry[] {
return sortGlossaryEntries(
resolveGlossaryEntriesInSourceOrder(slugs, allEntries),
);
}
export function rawFamilyOf(
entry: GlossaryEntry | null | undefined,
): string {
return String(entry?.data?.family ?? "");
}
export function kindOf(
entry: GlossaryEntry | null | undefined,
): string {
return String(entry?.data?.kind ?? "");
}
export function familyOf(
entry: GlossaryEntry | null | undefined,
): string {
const explicit = rawFamilyOf(entry);
if (explicit) return explicit;
const slug = slugOfGlossaryEntry(entry);
const kind = kindOf(entry);
if (kind === "paradigme") return "paradigme";
if (kind === "doctrine") return "doctrine";
if (kind === "verbe") return "verbe";
if (slug === "scene-depreuve") return "scene";
if (slug === "autarchicratie") return "pathologie";
if (slug === "obliteration-archicratique") return "dynamique";
if (
FONDAMENTAUX_WANTED.includes(
slug as (typeof FONDAMENTAUX_WANTED)[number],
)
) {
return "concept-fondamental";
}
if (slug === "archicrations-differentielles-et-formes-hybrides") {
return "topologie";
}
if (kind === "topologie" && slug.startsWith("archicrations-")) {
return "meta-regime";
}
return "";
}
export function getDisplayFamily(
entry: GlossaryEntry | null | undefined,
): string {
const familyKey = rawFamilyOf(entry) || familyOf(entry);
return FAMILY_LABELS[familyKey] ?? KIND_LABELS[kindOf(entry)] ?? "Fiche";
}
export function getDisplayDomain(
entry: GlossaryEntry | null | undefined,
): string {
const key = String(entry?.data?.domain ?? "");
return key ? (DOMAIN_LABELS[key] ?? key) : "";
}
export function getDisplayLevel(
entry: GlossaryEntry | null | undefined,
): string {
const key = String(entry?.data?.level ?? "");
return key ? (LEVEL_LABELS[key] ?? key) : "";
}
export function getFondamentaux(
entries: GlossaryEntry[] = [],
): GlossaryEntry[] {
const bySlug = buildGlossaryBySlug(entries);
return sortGlossaryEntries(
FONDAMENTAUX_WANTED
.map((slug) => bySlug.get(slug))
.filter(Boolean) as GlossaryEntry[],
);
}
export function getGlossaryEntriesByFamily(
entries: GlossaryEntry[] = [],
familyKey: string,
): GlossaryEntry[] {
return sortGlossaryEntries(
entries.filter((entry) => familyOf(entry) === familyKey),
);
}
export function countGlossaryEntriesByKind(
entries: GlossaryEntry[] = [],
kindKey: string,
): number {
return entries.filter((entry) => kindOf(entry) === kindKey).length;
}
export function countGlossaryEntriesByFamily(
entries: GlossaryEntry[] = [],
familyKey: string,
): number {
return entries.filter((entry) => familyOf(entry) === familyKey).length;
}
export function getGlossaryPortalLinks(): GlossaryPortalLink[] {
return [
{ href: "/glossaire/", label: "Accueil du glossaire" },
{ href: "/glossaire/concepts-fondamentaux/", label: "Concepts fondamentaux" },
{ href: "/glossaire/index-complet/", label: "Index complet" },
{
href: "/glossaire/paradigme-archicratique/",
label: "Paradigme archicratique",
},
{
href: "/glossaire/scenes-archicratiques/",
label: "Scènes archicratiques",
},
{
href: "/glossaire/dynamiques-archicratiques/",
label: "Dynamiques archicratiques",
},
{
href: "/glossaire/tensions-irreductibles/",
label: "Tensions irréductibles",
},
{
href: "/glossaire/archicrations/",
label: "Méta-régimes archicratiques",
},
{ href: "/glossaire/paradigmes/", label: "Paradigmes et doctrines" },
{ href: "/glossaire/verbes-de-la-scene/", label: "Verbes de la scène" },
];
}
export function getGlossaryHomeStats(
allEntries: GlossaryEntry[] = [],
): GlossaryHomeStats {
return {
totalEntries: allEntries.length,
paradigmesCount: countGlossaryEntriesByKind(allEntries, "paradigme"),
doctrinesCount: countGlossaryEntriesByKind(allEntries, "doctrine"),
metaRegimesCount: countGlossaryEntriesByFamily(allEntries, "meta-regime"),
};
}
export function getEntriesOfSameFamily(
entry: GlossaryEntry,
allEntries: GlossaryEntry[] = [],
): GlossaryEntry[] {
const familyKey = familyOf(entry);
if (!familyKey) return [];
if (familyKey === "concept-fondamental") {
return getFondamentaux(allEntries);
}
return getGlossaryEntriesByFamily(allEntries, familyKey);
}
export function getSameFamilyTitle(
entry: GlossaryEntry,
): string {
return FAMILY_SECTION_TITLES[familyOf(entry)] ?? "Même famille";
}
export function getRelationBlocks(
entry: GlossaryEntry,
allEntries: GlossaryEntry[] = [],
): GlossaryRelationBlock[] {
const currentSlug = slugOfGlossaryEntry(entry);
const relatedEntries = resolveGlossaryEntriesInSourceOrder(
entry.data.related ?? [],
allEntries,
).filter((item) => slugOfGlossaryEntry(item) !== currentSlug);
const opposedEntries = resolveGlossaryEntriesInSourceOrder(
entry.data.opposedTo ?? [],
allEntries,
).filter((item) => slugOfGlossaryEntry(item) !== currentSlug);
const seeAlsoEntries = resolveGlossaryEntriesInSourceOrder(
entry.data.seeAlso ?? [],
allEntries,
).filter((item) => slugOfGlossaryEntry(item) !== currentSlug);
return [
{
title: "Concepts liés",
items: relatedEntries,
className: "is-related",
},
{
title: "En tension avec",
items: opposedEntries,
className: "is-opposed",
},
{
title: "Voir aussi",
items: seeAlsoEntries,
className: "is-see-also",
},
].filter((block) => block.items.length > 0);
}
export function getRelationSections(
entry: GlossaryEntry,
allEntries: GlossaryEntry[] = [],
): GlossaryRelationSection[] {
return getRelationBlocks(entry, allEntries).map(({ title, items }) => ({
title,
items,
}));
}
function isTheoryEntry(entry: GlossaryEntry): boolean {
const familyKey = familyOf(entry);
const kindKey = kindOf(entry);
return (
familyKey === "paradigme" ||
familyKey === "doctrine" ||
kindKey === "paradigme" ||
kindKey === "doctrine"
);
}
export function getContextualTheory(
entry: GlossaryEntry,
allEntries: GlossaryEntry[] = [],
): GlossaryEntry[] {
const currentSlug = slugOfGlossaryEntry(entry);
const bySlug = buildGlossaryBySlug(allEntries);
const fromRelations = uniqueGlossaryEntries([
...resolveGlossaryEntriesInSourceOrder(entry.data.related ?? [], allEntries),
...resolveGlossaryEntriesInSourceOrder(entry.data.seeAlso ?? [], allEntries),
...resolveGlossaryEntriesInSourceOrder(entry.data.opposedTo ?? [], allEntries),
])
.filter((item) => slugOfGlossaryEntry(item) !== currentSlug)
.filter((item) => isTheoryEntry(item));
if (fromRelations.length > 0) {
return fromRelations.slice(0, 6);
}
if (familyOf(entry) === "paradigme") {
return uniqueGlossaryEntries(
PREFERRED_PARADIGME_SLUGS
.filter((slug) => slug !== currentSlug)
.map((slug) => bySlug.get(slug))
.filter(Boolean) as GlossaryEntry[],
).slice(0, 8);
}
if (familyOf(entry) === "doctrine") {
return uniqueGlossaryEntries(
PREFERRED_DOCTRINE_SLUGS
.filter((slug) => slug !== currentSlug)
.map((slug) => bySlug.get(slug))
.filter(Boolean) as GlossaryEntry[],
).slice(0, 6);
}
return [];
}
export function getGlossaryEntryAsideData(
currentEntry: GlossaryEntry,
allEntries: GlossaryEntry[] = [],
): GlossaryEntryAsideData {
const currentFamily = familyOf(currentEntry);
const currentSlug = slugOfGlossaryEntry(currentEntry);
const fondamentaux = getFondamentaux(allEntries);
const sameFamilyTitle = getSameFamilyTitle(currentEntry);
const relationSections = getRelationSections(currentEntry, allEntries);
const relationEntries = uniqueGlossaryEntries(
relationSections.flatMap((section) => section.items),
);
const relationSlugs = slugsOfGlossaryEntries(relationEntries);
const contextualTheory = excludeGlossaryEntries(
getContextualTheory(currentEntry, allEntries),
new Set([currentSlug, ...relationSlugs]),
).slice(0, 6);
const contextualTheorySlugs = slugsOfGlossaryEntries(contextualTheory);
const sameFamilyEntries = excludeGlossaryEntries(
getEntriesOfSameFamily(currentEntry, allEntries),
new Set([currentSlug, ...relationSlugs, ...contextualTheorySlugs]),
).slice(0, 8);
const showNoyau =
currentFamily !== "concept-fondamental" &&
fondamentaux.length > 0;
const showSameFamily =
currentFamily !== "concept-fondamental" &&
sameFamilyEntries.length > 0;
return {
displayFamily: getDisplayFamily(currentEntry),
displayDomain: getDisplayDomain(currentEntry),
displayLevel: getDisplayLevel(currentEntry),
showNoyau,
showSameFamily,
fondamentaux,
sameFamilyTitle,
sameFamilyEntries: showSameFamily ? sameFamilyEntries : [],
relationSections,
contextualTheory,
};
}
export function getGlossaryHomeData(
entries: GlossaryEntry[] = [],
): GlossaryHomeData {
const bySlug = buildGlossaryBySlug(entries);
const fondamentaux = getGlossaryEntriesByFamily(entries, "concept-fondamental");
const scenes = getGlossaryEntriesByFamily(entries, "scene");
const dynamiques = sortGlossaryEntries(
entries.filter((entry) =>
["dynamique", "pathologie"].includes(familyOf(entry)),
),
);
const metaRegimes = getGlossaryEntriesByFamily(entries, "meta-regime");
return {
fondamentaux,
scenes,
dynamiques,
metaRegimes,
metaRegimesPreview: metaRegimes.slice(0, 6),
arcalite: bySlug.get("arcalite"),
cratialite: bySlug.get("cratialite"),
tension: bySlug.get("tension"),
sceneDepreuve: bySlug.get("scene-depreuve"),
archicration: bySlug.get("archicration"),
};
}