// 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>} */ 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), ], }, });