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

@@ -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>