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 = { "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 = { 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 = { transversal: "Transversal", theorie: "Théorie", "cas-ia": "Cas IA", }; export const LEVEL_LABELS: Record = { 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 = { "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 | null | undefined, ): string { return normalizeGlossarySlug(entry?.id ?? ""); } export function hrefOfGlossaryEntry( entry: Pick | null | undefined, ): string { const slug = slugOfGlossaryEntry(entry); return slug ? `/glossaire/${slug}/` : "/glossaire/"; } export function buildGlossaryBySlug( entries: GlossaryEntry[] = [], ): Map { 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(); 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 { const slugs = new Set(); for (const entry of entries) { const slug = slugOfGlossaryEntry(entry); if (!slug) continue; slugs.add(slug); } return slugs; } export function excludeGlossaryEntries( entries: GlossaryEntry[] = [], excluded: Iterable = [], ): 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(); 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"), }; }