feat/glossaire-entry-relations-rendering #293
32
src/components/GlossaryRelationCards.astro
Normal file
32
src/components/GlossaryRelationCards.astro
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
import type { GlossaryRelationBlock } from "../lib/glossary";
|
||||
import { hrefOfGlossaryEntry } from "../lib/glossary";
|
||||
|
||||
interface Props {
|
||||
relationBlocks: GlossaryRelationBlock[];
|
||||
}
|
||||
|
||||
const { relationBlocks = [] } = Astro.props;
|
||||
---
|
||||
|
||||
{relationBlocks.length > 0 && (
|
||||
<section class="glossary-relations" aria-label="Relations conceptuelles">
|
||||
<h2>Relations conceptuelles</h2>
|
||||
|
||||
<div class="glossary-relations-grid">
|
||||
{relationBlocks.map((block) => (
|
||||
<section class={`glossary-relations-card ${block.className}`}>
|
||||
<h3>{block.title}</h3>
|
||||
<ul>
|
||||
{block.items.map((item) => (
|
||||
<li>
|
||||
<a href={hrefOfGlossaryEntry(item)}>{item.data.term}</a>
|
||||
<span> — {item.data.definitionShort}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
@@ -128,17 +128,35 @@ export function uniqueGlossaryEntries(
|
||||
return out;
|
||||
}
|
||||
|
||||
export function resolveGlossaryEntries(
|
||||
export function resolveGlossaryEntriesInSourceOrder(
|
||||
slugs: string[] = [],
|
||||
allEntries: GlossaryEntry[] = [],
|
||||
): GlossaryEntry[] {
|
||||
const bySlug = buildGlossaryBySlug(allEntries);
|
||||
const seen = new Set<string>();
|
||||
const resolved: GlossaryEntry[] = [];
|
||||
|
||||
const resolved = slugs
|
||||
.map((slug) => bySlug.get(normalizeGlossarySlug(slug)))
|
||||
.filter(Boolean) as GlossaryEntry[];
|
||||
for (const rawSlug of slugs) {
|
||||
const slug = normalizeGlossarySlug(rawSlug);
|
||||
if (!slug || seen.has(slug)) continue;
|
||||
|
||||
return sortGlossaryEntries(uniqueGlossaryEntries(resolved));
|
||||
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(
|
||||
@@ -268,31 +286,44 @@ export type GlossaryRelationBlock = {
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
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,
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
---
|
||||
import GlossaryLayout from "../../layouts/GlossaryLayout.astro";
|
||||
import GlossaryAside from "../../components/GlossaryAside.astro";
|
||||
import GlossaryRelationCards from "../../components/GlossaryRelationCards.astro";
|
||||
import { getCollection, render } from "astro:content";
|
||||
import {
|
||||
getDisplayDomain,
|
||||
getDisplayFamily,
|
||||
getDisplayLevel,
|
||||
getRelationBlocks,
|
||||
hrefOfGlossaryEntry,
|
||||
normalizeGlossarySlug,
|
||||
} from "../../lib/glossary";
|
||||
|
||||
@@ -133,27 +133,7 @@ const hasScholarlyMeta =
|
||||
<Content />
|
||||
</div>
|
||||
|
||||
{relationBlocks.length > 0 && (
|
||||
<section class="glossary-relations" aria-label="Relations conceptuelles">
|
||||
<h2>Relations conceptuelles</h2>
|
||||
|
||||
<div class="glossary-relations-grid">
|
||||
{relationBlocks.map((block) => (
|
||||
<section class={`glossary-relations-card ${block.className}`}>
|
||||
<h3>{block.title}</h3>
|
||||
<ul>
|
||||
{block.items.map((item) => (
|
||||
<li>
|
||||
<a href={hrefOfGlossaryEntry(item)}>{item.data.term}</a>
|
||||
<span> — {item.data.definitionShort}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
<GlossaryRelationCards relationBlocks={relationBlocks} />
|
||||
</GlossaryLayout>
|
||||
|
||||
<script is:inline>
|
||||
|
||||
Reference in New Issue
Block a user