refactor(glossaire): polish contextual aside navigation
This commit is contained in:
297
src/components/GlossaryAside.astro
Normal file
297
src/components/GlossaryAside.astro
Normal file
@@ -0,0 +1,297 @@
|
||||
---
|
||||
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(slug))
|
||||
.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;
|
||||
}
|
||||
|
||||
const relatedEntries = uniqueBySlug(resolveList(currentEntry.data.related ?? []))
|
||||
.sort((a, b) => collator.compare(a.data.term, b.data.term));
|
||||
|
||||
const opposedEntries = uniqueBySlug(resolveList(currentEntry.data.opposedTo ?? []))
|
||||
.sort((a, b) => collator.compare(a.data.term, b.data.term));
|
||||
|
||||
const seeAlsoEntries = uniqueBySlug(resolveList(currentEntry.data.seeAlso ?? []))
|
||||
.sort((a, b) => collator.compare(a.data.term, b.data.term));
|
||||
|
||||
const paradigmes = [...allEntries]
|
||||
.filter((e) => e.data.kind === "paradigme" && slugOf(e) !== currentSlug)
|
||||
.sort((a, b) => collator.compare(a.data.term, b.data.term));
|
||||
|
||||
function contextualParadigmsFor(entry) {
|
||||
const relatedParadigms = (entry.data.related ?? [])
|
||||
.map((slug) => bySlug.get(slug))
|
||||
.filter((e) => e && e.data.kind === "paradigme");
|
||||
|
||||
const seeAlsoParadigms = (entry.data.seeAlso ?? [])
|
||||
.map((slug) => bySlug.get(slug))
|
||||
.filter((e) => e && e.data.kind === "paradigme");
|
||||
|
||||
const opposedParadigms = (entry.data.opposedTo ?? [])
|
||||
.map((slug) => bySlug.get(slug))
|
||||
.filter((e) => e && e.data.kind === "paradigme");
|
||||
|
||||
const merged = uniqueBySlug([
|
||||
...relatedParadigms,
|
||||
...seeAlsoParadigms,
|
||||
...opposedParadigms,
|
||||
]);
|
||||
|
||||
if (merged.length > 0) {
|
||||
return merged.slice(0, 5);
|
||||
}
|
||||
|
||||
if (entry.data.kind === "paradigme") {
|
||||
const preferred = [
|
||||
"gouvernementalite",
|
||||
"gouvernementalite-algorithmique",
|
||||
"cybernetique",
|
||||
"biopolitique",
|
||||
"bureaucratie",
|
||||
"contractualisme-hobbesien",
|
||||
"liberalisme-proprietaire",
|
||||
"volonte-generale",
|
||||
];
|
||||
|
||||
return uniqueBySlug(
|
||||
preferred
|
||||
.filter((slug) => slug !== currentSlug)
|
||||
.map((slug) => bySlug.get(slug))
|
||||
.filter(Boolean)
|
||||
).slice(0, 5);
|
||||
}
|
||||
|
||||
return paradigmes.slice(0, 4);
|
||||
}
|
||||
|
||||
const contextualParadigms = contextualParadigmsFor(currentEntry);
|
||||
|
||||
const kindLabels = {
|
||||
concept: "Concept",
|
||||
diagnostic: "Diagnostic",
|
||||
topologie: "Topologie",
|
||||
verbe: "Verbe",
|
||||
paradigme: "Paradigme",
|
||||
};
|
||||
|
||||
const domainLabels = {
|
||||
transversal: "Transversal",
|
||||
theorie: "Théorie",
|
||||
"cas-ia": "Cas IA",
|
||||
};
|
||||
|
||||
const levelLabels = {
|
||||
fondamental: "Fondamental",
|
||||
intermediaire: "Intermédiaire",
|
||||
avance: "Avancé",
|
||||
};
|
||||
|
||||
const metaLabel = [
|
||||
kindLabels[currentEntry.data.kind] ?? currentEntry.data.kind,
|
||||
domainLabels[currentEntry.data.domain] ?? currentEntry.data.domain,
|
||||
levelLabels[currentEntry.data.level] ?? currentEntry.data.level,
|
||||
].join(" · ");
|
||||
---
|
||||
|
||||
<nav class="glossary-aside" aria-label="Navigation du glossaire">
|
||||
<div class="glossary-aside__block glossary-aside__block--intro">
|
||||
<a class="glossary-aside__back" href="/glossaire/">← Retour au glossaire</a>
|
||||
<div class="glossary-aside__title">Glossaire archicratique</div>
|
||||
<div class="glossary-aside__meta">{metaLabel}</div>
|
||||
</div>
|
||||
|
||||
{fondamentaux.length > 0 && (
|
||||
<section class="glossary-aside__block">
|
||||
<h2 class="glossary-aside__heading">Noyau archicratique</h2>
|
||||
<ul class="glossary-aside__list">
|
||||
{fondamentaux.map((entry) => {
|
||||
const active = slugOf(entry) === currentSlug;
|
||||
return (
|
||||
<li>
|
||||
<a
|
||||
href={hrefOf(entry)}
|
||||
aria-current={active ? "page" : undefined}
|
||||
class={active ? "is-active" : undefined}
|
||||
>
|
||||
{entry.data.term}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{(relatedEntries.length > 0 || opposedEntries.length > 0 || seeAlsoEntries.length > 0) && (
|
||||
<section class="glossary-aside__block">
|
||||
<h2 class="glossary-aside__heading">Autour de cette fiche</h2>
|
||||
|
||||
{relatedEntries.length > 0 && (
|
||||
<>
|
||||
<h3 class="glossary-aside__subheading">Liés</h3>
|
||||
<ul class="glossary-aside__list">
|
||||
{relatedEntries.map((entry) => (
|
||||
<li><a href={hrefOf(entry)}>{entry.data.term}</a></li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
|
||||
{opposedEntries.length > 0 && (
|
||||
<>
|
||||
<h3 class="glossary-aside__subheading">Opposés</h3>
|
||||
<ul class="glossary-aside__list">
|
||||
{opposedEntries.map((entry) => (
|
||||
<li><a href={hrefOf(entry)}>{entry.data.term}</a></li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
|
||||
{seeAlsoEntries.length > 0 && (
|
||||
<>
|
||||
<h3 class="glossary-aside__subheading">Voir aussi</h3>
|
||||
<ul class="glossary-aside__list">
|
||||
{seeAlsoEntries.map((entry) => (
|
||||
<li><a href={hrefOf(entry)}>{entry.data.term}</a></li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{contextualParadigms.length > 0 && (
|
||||
<section class="glossary-aside__block">
|
||||
<h2 class="glossary-aside__heading">
|
||||
{currentEntry.data.kind === "paradigme" ? "Paradigmes voisins" : "Paradigmes mobilisés"}
|
||||
</h2>
|
||||
<ul class="glossary-aside__list">
|
||||
{contextualParadigms.map((entry) => (
|
||||
<li><a href={hrefOf(entry)}>{entry.data.term}</a></li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
)}
|
||||
</nav>
|
||||
|
||||
<style>
|
||||
.glossary-aside{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.glossary-aside__block{
|
||||
border: 1px solid rgba(127,127,127,0.22);
|
||||
border-radius: 16px;
|
||||
padding: 12px;
|
||||
background: rgba(127,127,127,0.05);
|
||||
}
|
||||
|
||||
.glossary-aside__block--intro{
|
||||
padding-top: 11px;
|
||||
padding-bottom: 11px;
|
||||
}
|
||||
|
||||
.glossary-aside__back{
|
||||
display: inline-block;
|
||||
margin-bottom: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.glossary-aside__title{
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
letter-spacing: .2px;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.glossary-aside__meta{
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
line-height: 1.35;
|
||||
opacity: .78;
|
||||
}
|
||||
|
||||
.glossary-aside__heading{
|
||||
margin: 0 0 10px;
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
opacity: .9;
|
||||
}
|
||||
|
||||
.glossary-aside__subheading{
|
||||
margin: 12px 0 8px;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
opacity: .8;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .04em;
|
||||
}
|
||||
|
||||
.glossary-aside__list{
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.glossary-aside__list li{
|
||||
margin: 6px 0;
|
||||
}
|
||||
|
||||
.glossary-aside__list a{
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.glossary-aside__list a.is-active{
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark){
|
||||
.glossary-aside__block{
|
||||
background: rgba(255,255,255,0.04);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,9 +1,11 @@
|
||||
---
|
||||
import EditionLayout from "../../layouts/EditionLayout.astro";
|
||||
import GlossaryAside from "../../components/GlossaryAside.astro";
|
||||
import { getCollection, render } from "astro:content";
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const entries = await getCollection("glossaire");
|
||||
|
||||
return entries.map((entry) => ({
|
||||
params: { slug: String(entry.id).replace(/\.(md|mdx)$/i, "") },
|
||||
props: { entry },
|
||||
@@ -11,6 +13,7 @@ export async function getStaticPaths() {
|
||||
}
|
||||
|
||||
const { entry } = Astro.props;
|
||||
const allEntries = await getCollection("glossaire");
|
||||
const { Content } = await render(entry);
|
||||
---
|
||||
|
||||
@@ -18,11 +21,15 @@ const { Content } = await render(entry);
|
||||
title={entry.data.title}
|
||||
editionLabel="Glossaire"
|
||||
editionKey="glossaire"
|
||||
statusLabel="référentiel"
|
||||
statusLabel="Référentiel"
|
||||
statusKey="referentiel"
|
||||
level={1}
|
||||
version={entry.data.version}
|
||||
>
|
||||
<Fragment slot="aside">
|
||||
<GlossaryAside currentEntry={entry} allEntries={allEntries} />
|
||||
</Fragment>
|
||||
|
||||
<h1>{entry.data.term}</h1>
|
||||
<p><em>{entry.data.definitionShort}</em></p>
|
||||
<Content />
|
||||
|
||||
Reference in New Issue
Block a user