115 lines
3.1 KiB
JavaScript
115 lines
3.1 KiB
JavaScript
// astro.config.mjs
|
|
// @ts-check
|
|
import { defineConfig } from "astro/config";
|
|
import mdx from "@astrojs/mdx";
|
|
import sitemap from "@astrojs/sitemap";
|
|
|
|
import rehypeSlug from "rehype-slug";
|
|
import rehypeAutolinkHeadings from "rehype-autolink-headings";
|
|
|
|
import rehypeDetailsSections from "./scripts/rehype-details-sections.mjs";
|
|
import rehypeParagraphIds from "./src/plugins/rehype-paragraph-ids.js";
|
|
|
|
/**
|
|
* Cast minimal pour satisfaire @ts-check sans dépendre de types internes Astro/Unified.
|
|
* @param {unknown} x
|
|
* @returns {any}
|
|
*/
|
|
const asAny = (x) => /** @type {any} */ (x);
|
|
|
|
/**
|
|
* @param {any} node
|
|
* @param {string} cls
|
|
* @returns {boolean}
|
|
*/
|
|
function hasClass(node, cls) {
|
|
const cn = node?.properties?.className;
|
|
if (Array.isArray(cn)) return cn.includes(cls);
|
|
if (typeof cn === "string") return cn.split(/\s+/).includes(cls);
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Rehype plugin: retire les ids dupliqués en gardant en priorité:
|
|
* 1) span.details-anchor
|
|
* 2) h1..h6
|
|
* 3) sinon: premier rencontré
|
|
* @returns {(tree: any) => void}
|
|
*/
|
|
function rehypeDedupeIds() {
|
|
/** @param {any} tree */
|
|
return (tree) => {
|
|
/** @type {Map<string, Array<{node:any, pref:number, idx:number}>>} */
|
|
const occ = new Map();
|
|
let idx = 0;
|
|
|
|
/** @param {any} node */
|
|
const walk = (node) => {
|
|
if (!node || typeof node !== "object") return;
|
|
|
|
if (node.type === "element") {
|
|
const id = node.properties?.id;
|
|
if (typeof id === "string" && id) {
|
|
let pref = 2;
|
|
if (node.tagName === "span" && hasClass(node, "details-anchor")) pref = 0;
|
|
else if (/^h[1-6]$/.test(String(node.tagName || ""))) pref = 1;
|
|
|
|
const arr = occ.get(id) || [];
|
|
arr.push({ node, pref, idx: idx++ });
|
|
occ.set(id, arr);
|
|
}
|
|
|
|
const children = node.children;
|
|
if (Array.isArray(children)) for (const c of children) walk(c);
|
|
} else if (Array.isArray(node.children)) {
|
|
for (const c of node.children) walk(c);
|
|
}
|
|
};
|
|
|
|
walk(tree);
|
|
|
|
for (const [id, items] of occ.entries()) {
|
|
if (items.length <= 1) continue;
|
|
|
|
items.sort((a, b) => (a.pref - b.pref) || (a.idx - b.idx));
|
|
const keep = items[0];
|
|
|
|
for (let i = 1; i < items.length; i++) {
|
|
const n = items[i].node;
|
|
if (n?.properties?.id === id) delete n.properties.id;
|
|
}
|
|
|
|
// safety: on s'assure qu'un seul garde bien l'id
|
|
if (keep?.node?.properties) keep.node.properties.id = id;
|
|
}
|
|
};
|
|
}
|
|
|
|
export default defineConfig({
|
|
legacy: {
|
|
collectionsBackwardsCompat: true,
|
|
},
|
|
|
|
output: "static",
|
|
trailingSlash: "always",
|
|
site: process.env.PUBLIC_SITE ?? "http://localhost:4321",
|
|
|
|
integrations: [
|
|
// Important: MDX hérite du pipeline markdown (ids p-… + autres plugins)
|
|
mdx({ extendMarkdownConfig: true }),
|
|
sitemap({
|
|
filter: (page) => !page.includes("/api/") && !page.endsWith("/robots.txt"),
|
|
}),
|
|
],
|
|
|
|
markdown: {
|
|
rehypePlugins: [
|
|
asAny(rehypeSlug),
|
|
[asAny(rehypeAutolinkHeadings), { behavior: "append" }],
|
|
asAny(rehypeDetailsSections),
|
|
asAny(rehypeParagraphIds),
|
|
asAny(rehypeDedupeIds),
|
|
],
|
|
},
|
|
});
|