refactor(glossaire): centralize glossary relation helpers
All checks were successful
CI / build-and-anchors (pull_request) Successful in 42s
SMOKE / smoke (push) Successful in 3s
CI / build-and-anchors (push) Successful in 41s

This commit is contained in:
2026-03-25 14:15:39 +01:00
parent a38f585f3d
commit ad06b34a85
5 changed files with 473 additions and 496 deletions

View File

@@ -1,258 +1,46 @@
---
import {
familyOf,
getContextualTheory,
getDisplayDomain,
getDisplayFamily,
getDisplayLevel,
getEntriesOfSameFamily,
getFondamentaux,
getRelationSections,
getSameFamilyTitle,
hrefOfGlossaryEntry,
slugOfGlossaryEntry,
} from "../lib/glossary";
const {
currentEntry,
allEntries = [],
} = Astro.props;
const slugOf = (entry) => String(entry.id).replace(/\.(md|mdx)$/i, "");
const hrefOf = (entry) => `/glossaire/${slugOf(entry)}/`;
const collator = new Intl.Collator("fr", { sensitivity: "base", numeric: true });
const bySlug = new Map(allEntries.map((entry) => [slugOf(entry), entry]));
const currentSlug = slugOf(currentEntry);
const fondamentauxWanted = [
"archicratie",
"tension",
"arcalite",
"cratialite",
"archicration",
"co-viabilite",
];
const fondamentaux = fondamentauxWanted
.map((slug) => bySlug.get(slug))
.filter(Boolean);
function resolveList(slugs = []) {
return slugs
.map((slug) => bySlug.get(String(slug || "").trim()))
.filter(Boolean);
}
function uniqueBySlug(entries) {
const seen = new Set();
const out = [];
for (const entry of entries) {
const slug = slugOf(entry);
if (seen.has(slug)) continue;
seen.add(slug);
out.push(entry);
}
return out;
}
function sortByTerm(entries = []) {
return [...entries].sort((a, b) => collator.compare(a.data.term, b.data.term));
}
function familyOf(entry) {
return entry?.data?.family ?? "";
}
function kindOf(entry) {
return entry?.data?.kind ?? "";
}
const relatedEntries = sortByTerm(
uniqueBySlug(resolveList(currentEntry.data.related ?? []))
);
const opposedEntries = sortByTerm(
uniqueBySlug(resolveList(currentEntry.data.opposedTo ?? []))
);
const seeAlsoEntries = sortByTerm(
uniqueBySlug(resolveList(currentEntry.data.seeAlso ?? []))
);
const familyLabels = {
"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",
};
const kindLabels = {
concept: "Concept",
diagnostic: "Diagnostic",
topologie: "Topologie",
verbe: "Verbe",
paradigme: "Paradigme",
doctrine: "Doctrine",
dispositif: "Dispositif",
figure: "Figure",
qualification: "Qualification",
epistemologie: "Épistémologie",
};
const domainLabels = {
transversal: "Transversal",
theorie: "Théorie",
"cas-ia": "Cas IA",
};
const levelLabels = {
fondamental: "Fondamental",
intermediaire: "Intermédiaire",
avance: "Avancé",
};
const currentSlug = slugOfGlossaryEntry(currentEntry);
const currentFamily = familyOf(currentEntry);
const displayFamily =
familyLabels[currentFamily] ??
kindLabels[currentEntry.data.kind] ??
"Fiche";
const displayDomain =
domainLabels[currentEntry.data.domain] ??
currentEntry.data.domain;
const fondamentaux = getFondamentaux(allEntries);
const displayLevel =
levelLabels[currentEntry.data.level] ??
currentEntry.data.level;
const displayFamily = getDisplayFamily(currentEntry);
const displayDomain = getDisplayDomain(currentEntry);
const displayLevel = getDisplayLevel(currentEntry);
function entriesOfSameFamily(entry) {
const family = familyOf(entry);
const sameFamilyEntries = getEntriesOfSameFamily(currentEntry, allEntries);
const sameFamilyTitle = getSameFamilyTitle(currentEntry);
if (!family) return [];
const contextualTheory = getContextualTheory(currentEntry, allEntries);
if (family === "concept-fondamental") {
return fondamentaux;
}
return sortByTerm(
allEntries.filter((item) => familyOf(item) === family)
);
}
const sameFamilyEntries = entriesOfSameFamily(currentEntry);
const familySectionTitles = {
"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 sameFamilyTitle =
familySectionTitles[currentFamily] ?? "Même famille";
function isTheoryEntry(entry) {
const family = familyOf(entry);
const kind = kindOf(entry);
return (
family === "paradigme" ||
family === "doctrine" ||
kind === "paradigme" ||
kind === "doctrine"
);
}
function contextualTheoryFor(entry) {
const fromRelations = uniqueBySlug([
...resolveList(entry.data.related ?? []),
...resolveList(entry.data.seeAlso ?? []),
...resolveList(entry.data.opposedTo ?? []),
])
.filter((item) => slugOf(item) !== currentSlug)
.filter((item) => isTheoryEntry(item));
if (fromRelations.length > 0) {
return sortByTerm(fromRelations).slice(0, 6);
}
if (familyOf(entry) === "paradigme") {
const preferred = [
"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",
];
return uniqueBySlug(
preferred
.filter((slug) => slug !== currentSlug)
.map((slug) => bySlug.get(slug))
.filter(Boolean)
).slice(0, 8);
}
if (familyOf(entry) === "doctrine") {
const preferred = [
"contractualisme-hobbesien",
"droit-naturel-et-propriete",
"volonte-generale",
"decisionnisme-souverain",
];
return uniqueBySlug(
preferred
.filter((slug) => slug !== currentSlug)
.map((slug) => bySlug.get(slug))
.filter(Boolean)
).slice(0, 6);
}
return [];
}
const contextualTheory = contextualTheoryFor(currentEntry);
const showNoyau = currentFamily !== "concept-fondamental" && fondamentaux.length > 0;
const showNoyau =
currentFamily !== "concept-fondamental" &&
fondamentaux.length > 0;
const showSameFamily =
sameFamilyEntries.length > 0 && currentFamily !== "concept-fondamental";
sameFamilyEntries.length > 0 &&
currentFamily !== "concept-fondamental";
const relationSections = [
{
title: "Concepts liés",
items: relatedEntries,
},
{
title: "En tension avec",
items: opposedEntries,
},
{
title: "Voir aussi",
items: seeAlsoEntries,
},
].filter((section) => section.items.length > 0);
const relationSections = getRelationSections(currentEntry, allEntries);
---
<nav class="glossary-aside" aria-label="Navigation du glossaire">
@@ -286,11 +74,11 @@ const relationSections = [
<h2 class="glossary-aside__heading">Noyau archicratique</h2>
<ul class="glossary-aside__list">
{fondamentaux.map((entry) => {
const active = slugOf(entry) === currentSlug;
const active = slugOfGlossaryEntry(entry) === currentSlug;
return (
<li>
<a
href={hrefOf(entry)}
href={hrefOfGlossaryEntry(entry)}
aria-current={active ? "page" : undefined}
class={active ? "is-active" : undefined}
>
@@ -308,11 +96,11 @@ const relationSections = [
<h2 class="glossary-aside__heading">{sameFamilyTitle}</h2>
<ul class="glossary-aside__list">
{sameFamilyEntries.map((entry) => {
const active = slugOf(entry) === currentSlug;
const active = slugOfGlossaryEntry(entry) === currentSlug;
return (
<li>
<a
href={hrefOf(entry)}
href={hrefOfGlossaryEntry(entry)}
aria-current={active ? "page" : undefined}
class={active ? "is-active" : undefined}
>
@@ -334,7 +122,7 @@ const relationSections = [
<h3 class="glossary-aside__subheading">{section.title}</h3>
<ul class="glossary-aside__list">
{section.items.map((entry) => (
<li><a href={hrefOf(entry)}>{entry.data.term}</a></li>
<li><a href={hrefOfGlossaryEntry(entry)}>{entry.data.term}</a></li>
))}
</ul>
</>
@@ -347,7 +135,7 @@ const relationSections = [
<h2 class="glossary-aside__heading">Paysage théorique</h2>
<ul class="glossary-aside__list">
{contextualTheory.map((entry) => (
<li><a href={hrefOf(entry)}>{entry.data.term}</a></li>
<li><a href={hrefOfGlossaryEntry(entry)}>{entry.data.term}</a></li>
))}
</ul>
</section>

View File

@@ -1,76 +1,21 @@
---
import {
countGlossaryEntriesByFamily,
countGlossaryEntriesByKind,
getFondamentaux,
hrefOfGlossaryEntry,
} from "../lib/glossary";
const {
allEntries = [],
} = Astro.props;
const slugOf = (entry) => String(entry.id).replace(/\.(md|mdx)$/i, "");
const hrefOf = (entry) => `/glossaire/${slugOf(entry)}/`;
const collator = new Intl.Collator("fr", { sensitivity: "base", numeric: true });
const bySlug = new Map(allEntries.map((entry) => [slugOf(entry), entry]));
function sortByTerm(list = []) {
return [...list].sort((a, b) => collator.compare(a.data.term, b.data.term));
}
function familyOf(entry) {
const explicit = entry?.data?.family;
if (explicit) return explicit;
const slug = slugOf(entry);
const kind = entry?.data?.kind;
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 (
[
"archicratie",
"arcalite",
"cratialite",
"archicration",
"co-viabilite",
"tension",
].includes(slug)
) {
return "concept-fondamental";
}
if (slug === "archicrations-differentielles-et-formes-hybrides") {
return "topologie";
}
if (kind === "topologie" && slug.startsWith("archicrations-")) {
return "meta-regime";
}
return "";
}
const fondamentauxWanted = [
"archicratie",
"arcalite",
"cratialite",
"archicration",
"co-viabilite",
"tension",
];
const fondamentaux = sortByTerm(
fondamentauxWanted
.map((slug) => bySlug.get(slug))
.filter(Boolean)
);
const fondamentaux = getFondamentaux(allEntries);
const totalEntries = allEntries.length;
const paradigmesCount = allEntries.filter((entry) => entry.data.kind === "paradigme").length;
const doctrinesCount = allEntries.filter((entry) => entry.data.kind === "doctrine").length;
const metaRegimesCount = allEntries.filter((entry) => familyOf(entry) === "meta-regime").length;
const paradigmesCount = countGlossaryEntriesByKind(allEntries, "paradigme");
const doctrinesCount = countGlossaryEntriesByKind(allEntries, "doctrine");
const metaRegimesCount = countGlossaryEntriesByFamily(allEntries, "meta-regime");
const portalLinks = [
{ href: "/glossaire/concepts-fondamentaux/", label: "Concepts fondamentaux" },
@@ -115,7 +60,7 @@ const portalLinks = [
<h2 class="glossary-home-aside__heading">Noyau archicratique</h2>
<ul class="glossary-home-aside__list">
{fondamentaux.map((entry) => (
<li><a href={hrefOf(entry)}>{entry.data.term}</a></li>
<li><a href={hrefOfGlossaryEntry(entry)}>{entry.data.term}</a></li>
))}
</ul>
</section>

385
src/lib/glossary.ts Normal file
View File

@@ -0,0 +1,385 @@
import type { CollectionEntry } from "astro:content";
export type GlossaryEntry = CollectionEntry<"glossaire">;
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",
};
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 resolveGlossaryEntries(
slugs: string[] = [],
allEntries: GlossaryEntry[] = [],
): GlossaryEntry[] {
const bySlug = buildGlossaryBySlug(allEntries);
const resolved = slugs
.map((slug) => bySlug.get(normalizeGlossarySlug(slug)))
.filter(Boolean) as GlossaryEntry[];
return sortGlossaryEntries(uniqueGlossaryEntries(resolved));
}
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 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 type GlossaryRelationBlock = {
title: string;
items: GlossaryEntry[];
className: string;
};
export function getRelationBlocks(
entry: GlossaryEntry,
allEntries: GlossaryEntry[] = [],
): GlossaryRelationBlock[] {
const relatedEntries = resolveGlossaryEntries(entry.data.related ?? [], allEntries);
const opposedEntries = resolveGlossaryEntries(entry.data.opposedTo ?? [], allEntries);
const seeAlsoEntries = resolveGlossaryEntries(entry.data.seeAlso ?? [], allEntries);
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[] = [],
): Array<{ title: string; items: GlossaryEntry[] }> {
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"
);
}
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 getContextualTheory(
entry: GlossaryEntry,
allEntries: GlossaryEntry[] = [],
): GlossaryEntry[] {
const currentSlug = slugOfGlossaryEntry(entry);
const bySlug = buildGlossaryBySlug(allEntries);
const fromRelations = uniqueGlossaryEntries([
...resolveGlossaryEntries(entry.data.related ?? [], allEntries),
...resolveGlossaryEntries(entry.data.seeAlso ?? [], allEntries),
...resolveGlossaryEntries(entry.data.opposedTo ?? [], allEntries),
])
.filter((item) => slugOfGlossaryEntry(item) !== currentSlug)
.filter((item) => isTheoryEntry(item));
if (fromRelations.length > 0) {
return sortGlossaryEntries(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 [];
}

View File

@@ -2,6 +2,14 @@
import GlossaryLayout from "../../layouts/GlossaryLayout.astro";
import GlossaryAside from "../../components/GlossaryAside.astro";
import { getCollection, render } from "astro:content";
import {
getDisplayDomain,
getDisplayFamily,
getDisplayLevel,
getRelationBlocks,
hrefOfGlossaryEntry,
normalizeGlossarySlug,
} from "../../lib/glossary";
export async function getStaticPaths() {
const entries = await getCollection("glossaire");
@@ -9,20 +17,12 @@ export async function getStaticPaths() {
const seen = new Set();
for (const entry of entries) {
const canonicalSlug = String(entry.id || "")
.trim()
.replace(/^\/+|\/+$/g, "")
.replace(/\.(md|mdx)$/i, "")
.toLowerCase();
const canonicalSlug = normalizeGlossarySlug(entry.id);
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(canonicalSlug)) continue;
const addPath = (rawSlug) => {
const requestedSlug = String(rawSlug || "")
.trim()
.replace(/^\/+|\/+$/g, "")
.replace(/\.(md|mdx)$/i, "")
.toLowerCase();
const requestedSlug = normalizeGlossarySlug(rawSlug);
if (!requestedSlug) return;
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(requestedSlug)) return;
@@ -56,108 +56,11 @@ const { Content } = await render(entry);
const isAliasRoute = requestedSlug !== canonicalSlug;
const canonicalHref = `/glossaire/${canonicalSlug}/`;
const slugOf = (item) =>
String(item.id || "")
.trim()
.replace(/^\/+|\/+$/g, "")
.replace(/\.(md|mdx)$/i, "");
const relationBlocks = getRelationBlocks(entry, allEntries);
const hrefOf = (item) => `/glossaire/${slugOf(item)}/`;
const collator = new Intl.Collator("fr", { sensitivity: "base", numeric: true });
const bySlug = new Map(
allEntries.map((item) => [slugOf(item).toLowerCase(), item])
);
function resolveEntries(slugs = []) {
const seen = new Set();
return slugs
.map((slug) => bySlug.get(String(slug || "").trim().toLowerCase()))
.filter(Boolean)
.filter((item) => {
const slug = slugOf(item);
if (seen.has(slug)) return false;
seen.add(slug);
return true;
})
.sort((a, b) => collator.compare(a.data.term, b.data.term));
}
const relatedEntries = resolveEntries(entry.data.related ?? []);
const opposedEntries = resolveEntries(entry.data.opposedTo ?? []);
const seeAlsoEntries = resolveEntries(entry.data.seeAlso ?? []);
const relationBlocks = [
{
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);
const familyLabels = {
"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",
};
const kindLabels = {
concept: "Concept",
diagnostic: "Diagnostic",
topologie: "Topologie",
verbe: "Verbe",
paradigme: "Paradigme",
doctrine: "Doctrine",
dispositif: "Dispositif",
figure: "Figure",
qualification: "Qualification",
epistemologie: "Épistémologie",
};
const domainLabels = {
transversal: "Transversal",
theorie: "Théorie",
"cas-ia": "Cas IA",
};
const levelLabels = {
fondamental: "Fondamental",
intermediaire: "Intermédiaire",
avance: "Avancé",
};
const familyKey = entry.data.family ?? "";
const displayFamily =
familyLabels[familyKey] ??
kindLabels[entry.data.kind] ??
"Fiche";
const displayDomain = entry.data.domain
? (domainLabels[entry.data.domain] ?? entry.data.domain)
: "";
const displayLevel = entry.data.level
? (levelLabels[entry.data.level] ?? entry.data.level)
: "";
const displayFamily = getDisplayFamily(entry);
const displayDomain = getDisplayDomain(entry);
const displayLevel = getDisplayLevel(entry);
const hasScholarlyMeta =
(entry.data.mobilizedAuthors?.length ?? 0) > 0 ||
@@ -241,7 +144,7 @@ const hasScholarlyMeta =
<ul>
{block.items.map((item) => (
<li>
<a href={hrefOf(item)}>{item.data.term}</a>
<a href={hrefOfGlossaryEntry(item)}>{item.data.term}</a>
<span> — {item.data.definitionShort}</span>
</li>
))}

View File

@@ -2,74 +2,30 @@
import GlossaryLayout from "../../layouts/GlossaryLayout.astro";
import GlossaryHomeAside from "../../components/GlossaryHomeAside.astro";
import { getCollection } from "astro:content";
import {
buildGlossaryBySlug,
countGlossaryEntriesByKind,
familyOf,
getGlossaryEntriesByFamily,
hrefOfGlossaryEntry,
sortGlossaryEntries,
} from "../../lib/glossary";
const entries = await getCollection("glossaire");
const slugOf = (entry) => String(entry.id).replace(/\.(md|mdx)$/i, "");
const hrefOf = (entry) => `/glossaire/${slugOf(entry)}/`;
const bySlug = buildGlossaryBySlug(entries);
const collator = new Intl.Collator("fr", { sensitivity: "base", numeric: true });
const bySlug = new Map(entries.map((entry) => [slugOf(entry), entry]));
function sortByTerm(list = []) {
return [...list].sort((a, b) => collator.compare(a.data.term, b.data.term));
}
function familyOf(entry) {
const explicit = entry?.data?.family;
if (explicit) return explicit;
const slug = slugOf(entry);
const kind = entry?.data?.kind;
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 ([
"archicratie",
"arcalite",
"cratialite",
"archicration",
"co-viabilite",
"tension",
].includes(slug)) {
return "concept-fondamental";
}
if (slug === "archicrations-differentielles-et-formes-hybrides") {
return "topologie";
}
if (kind === "topologie" && slug.startsWith("archicrations-")) {
return "meta-regime";
}
return "";
}
const fondamentaux = sortByTerm(
entries.filter((entry) => familyOf(entry) === "concept-fondamental")
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");
const scenes = sortByTerm(
entries.filter((entry) => familyOf(entry) === "scene")
);
const dynamiques = sortByTerm(
entries.filter((entry) => ["dynamique", "pathologie"].includes(familyOf(entry)))
);
const metaRegimes = sortByTerm(
entries.filter((entry) => familyOf(entry) === "meta-regime")
);
const paradigmesCount = entries.filter((entry) => entry.data.kind === "paradigme").length;
const doctrinesCount = entries.filter((entry) => entry.data.kind === "doctrine").length;
const paradigmesCount = countGlossaryEntriesByKind(entries, "paradigme");
const doctrinesCount = countGlossaryEntriesByKind(entries, "doctrine");
const metaRegimesPreview = metaRegimes.slice(0, 6);
@@ -132,13 +88,13 @@ const indexCompletPageHref = "/glossaire/index-complet/";
<div class="glossary-map__title">Forces en composition</div>
<div class="glossary-map__roots">
{arcalite ? (
<a class="glossary-map__node" href={hrefOf(arcalite)}>ARCALITÉ</a>
<a class="glossary-map__node" href={hrefOfGlossaryEntry(arcalite)}>ARCALITÉ</a>
) : (
<span class="glossary-map__node">ARCALITÉ</span>
)}
{cratialite ? (
<a class="glossary-map__node" href={hrefOf(cratialite)}>CRATIALITÉ</a>
<a class="glossary-map__node" href={hrefOfGlossaryEntry(cratialite)}>CRATIALITÉ</a>
) : (
<span class="glossary-map__node">CRATIALITÉ</span>
)}
@@ -150,7 +106,7 @@ const indexCompletPageHref = "/glossaire/index-complet/";
<div class="glossary-map__stage">
<div class="glossary-map__title">Phénomène transversal</div>
{tension ? (
<a class="glossary-map__node glossary-map__node--wide" href={hrefOf(tension)}>
<a class="glossary-map__node glossary-map__node--wide" href={hrefOfGlossaryEntry(tension)}>
TENSION
</a>
) : (
@@ -163,7 +119,7 @@ const indexCompletPageHref = "/glossaire/index-complet/";
<div class="glossary-map__stage">
<div class="glossary-map__title">Comparution</div>
{sceneDepreuve ? (
<a class="glossary-map__node glossary-map__node--wide" href={hrefOf(sceneDepreuve)}>
<a class="glossary-map__node glossary-map__node--wide" href={hrefOfGlossaryEntry(sceneDepreuve)}>
MISE EN SCÈNE
</a>
) : (
@@ -176,7 +132,7 @@ const indexCompletPageHref = "/glossaire/index-complet/";
<div class="glossary-map__stage">
<div class="glossary-map__title">Opérateur régulateur</div>
{archicration ? (
<a class="glossary-map__node glossary-map__node--wide" href={hrefOf(archicration)}>
<a class="glossary-map__node glossary-map__node--wide" href={hrefOfGlossaryEntry(archicration)}>
ARCHICRATION
</a>
) : (
@@ -209,7 +165,7 @@ const indexCompletPageHref = "/glossaire/index-complet/";
<div class="glossary-cards">
{fondamentaux.map((entry) => (
<a class="glossary-card" href={hrefOf(entry)}>
<a class="glossary-card" href={hrefOfGlossaryEntry(entry)}>
<strong>{entry.data.term}</strong>
<span>{entry.data.definitionShort}</span>
</a>
@@ -265,7 +221,7 @@ const indexCompletPageHref = "/glossaire/index-complet/";
<div class="glossary-cards">
{scenes.map((entry) => (
<a class="glossary-card glossary-card--wide" href={hrefOf(entry)}>
<a class="glossary-card glossary-card--wide" href={hrefOfGlossaryEntry(entry)}>
<strong>{entry.data.term}</strong>
<span>{entry.data.definitionShort}</span>
</a>
@@ -288,7 +244,7 @@ const indexCompletPageHref = "/glossaire/index-complet/";
<div class="glossary-cards">
{dynamiques.map((entry) => (
<a class="glossary-card" href={hrefOf(entry)}>
<a class="glossary-card" href={hrefOfGlossaryEntry(entry)}>
<strong>{entry.data.term}</strong>
<span>{entry.data.definitionShort}</span>
</a>
@@ -316,7 +272,7 @@ const indexCompletPageHref = "/glossaire/index-complet/";
{metaRegimesPreview.length > 0 && (
<div class="glossary-cards">
{metaRegimesPreview.map((entry) => (
<a class="glossary-card" href={hrefOf(entry)}>
<a class="glossary-card" href={hrefOfGlossaryEntry(entry)}>
<strong>{entry.data.term}</strong>
<span>{entry.data.definitionShort}</span>
</a>