Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
update citations and footer
Browse files
app/astro.config.mjs
CHANGED
|
@@ -12,6 +12,7 @@ import rehypeCitation from 'rehype-citation';
|
|
| 12 |
import rehypeCodeCopy from './plugins/rehype/code-copy.mjs';
|
| 13 |
import rehypeReferencesAndFootnotes from './plugins/rehype/post-citation.mjs';
|
| 14 |
import remarkIgnoreCitationsInCode from './plugins/remark/ignore-citations-in-code.mjs';
|
|
|
|
| 15 |
import remarkDirective from 'remark-directive';
|
| 16 |
import remarkOutputContainer from './plugins/remark/output-container.mjs';
|
| 17 |
import rehypeRestoreAtInCode from './plugins/rehype/restore-at-in-code.mjs';
|
|
@@ -47,6 +48,7 @@ export default defineConfig({
|
|
| 47 |
}
|
| 48 |
},
|
| 49 |
remarkPlugins: [
|
|
|
|
| 50 |
remarkIgnoreCitationsInCode,
|
| 51 |
remarkMath,
|
| 52 |
[remarkFootnotes, { inlineNotes: true }],
|
|
@@ -63,6 +65,8 @@ export default defineConfig({
|
|
| 63 |
bibliography: 'src/content/bibliography.bib',
|
| 64 |
linkCitations: true,
|
| 65 |
csl: "apa",
|
|
|
|
|
|
|
| 66 |
}],
|
| 67 |
rehypeReferencesAndFootnotes,
|
| 68 |
rehypeRestoreAtInCode,
|
|
|
|
| 12 |
import rehypeCodeCopy from './plugins/rehype/code-copy.mjs';
|
| 13 |
import rehypeReferencesAndFootnotes from './plugins/rehype/post-citation.mjs';
|
| 14 |
import remarkIgnoreCitationsInCode from './plugins/remark/ignore-citations-in-code.mjs';
|
| 15 |
+
import remarkUnwrapCitationLinks from './plugins/remark/unwrap-citation-links.mjs';
|
| 16 |
import remarkDirective from 'remark-directive';
|
| 17 |
import remarkOutputContainer from './plugins/remark/output-container.mjs';
|
| 18 |
import rehypeRestoreAtInCode from './plugins/rehype/restore-at-in-code.mjs';
|
|
|
|
| 48 |
}
|
| 49 |
},
|
| 50 |
remarkPlugins: [
|
| 51 |
+
remarkUnwrapCitationLinks,
|
| 52 |
remarkIgnoreCitationsInCode,
|
| 53 |
remarkMath,
|
| 54 |
[remarkFootnotes, { inlineNotes: true }],
|
|
|
|
| 65 |
bibliography: 'src/content/bibliography.bib',
|
| 66 |
linkCitations: true,
|
| 67 |
csl: "apa",
|
| 68 |
+
noCite: false,
|
| 69 |
+
suppressBibliography: false,
|
| 70 |
}],
|
| 71 |
rehypeReferencesAndFootnotes,
|
| 72 |
rehypeRestoreAtInCode,
|
app/plugins/rehype/post-citation.mjs
CHANGED
|
@@ -103,7 +103,7 @@ export default function rehypeReferencesAndFootnotes() {
|
|
| 103 |
type: 'element',
|
| 104 |
tagName: 'a',
|
| 105 |
properties: { href: `#${keys[0]}`, 'aria-label': ariaLabel },
|
| 106 |
-
children: [
|
| 107 |
};
|
| 108 |
small.children.push(a);
|
| 109 |
} else {
|
|
@@ -115,7 +115,7 @@ export default function rehypeReferencesAndFootnotes() {
|
|
| 115 |
type: 'element',
|
| 116 |
tagName: 'a',
|
| 117 |
properties: { href: `#${backId}`, 'aria-label': ariaLabel },
|
| 118 |
-
children: [
|
| 119 |
});
|
| 120 |
if (idx < keys.length - 1) small.children.push({ type: 'text', value: ', ' });
|
| 121 |
});
|
|
@@ -278,8 +278,12 @@ export default function rehypeReferencesAndFootnotes() {
|
|
| 278 |
walk(tree, null, (node) => {
|
| 279 |
if (found) return;
|
| 280 |
if (!isElement(node)) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 281 |
const id = getAttr(node, 'id');
|
| 282 |
-
if (id === 'references' || hasClass(node, 'references') || hasClass(node, 'bibliography')) {
|
| 283 |
found = node;
|
| 284 |
}
|
| 285 |
});
|
|
@@ -319,6 +323,10 @@ export default function rehypeReferencesAndFootnotes() {
|
|
| 319 |
const refIdToExternalHref = new Map();
|
| 320 |
|
| 321 |
if (refsRoot) {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 322 |
refsOl = toOrderedList(refsRoot);
|
| 323 |
// Collect item ids and linkify their content
|
| 324 |
for (const li of getChildren(refsOl)) {
|
|
|
|
| 103 |
type: 'element',
|
| 104 |
tagName: 'a',
|
| 105 |
properties: { href: `#${keys[0]}`, 'aria-label': ariaLabel },
|
| 106 |
+
children: [createBackIcon()]
|
| 107 |
};
|
| 108 |
small.children.push(a);
|
| 109 |
} else {
|
|
|
|
| 115 |
type: 'element',
|
| 116 |
tagName: 'a',
|
| 117 |
properties: { href: `#${backId}`, 'aria-label': ariaLabel },
|
| 118 |
+
children: [{ type: 'text', value: String(idx + 1) }]
|
| 119 |
});
|
| 120 |
if (idx < keys.length - 1) small.children.push({ type: 'text', value: ', ' });
|
| 121 |
});
|
|
|
|
| 278 |
walk(tree, null, (node) => {
|
| 279 |
if (found) return;
|
| 280 |
if (!isElement(node)) return;
|
| 281 |
+
|
| 282 |
+
// Ignore headers (h1, h2, h3, h4, h5, h6) - we only want container elements
|
| 283 |
+
if (/^h[1-6]$/i.test(node.tagName)) return;
|
| 284 |
+
|
| 285 |
const id = getAttr(node, 'id');
|
| 286 |
+
if (id === 'references' || id === 'refs' || hasClass(node, 'references') || hasClass(node, 'bibliography')) {
|
| 287 |
found = node;
|
| 288 |
}
|
| 289 |
});
|
|
|
|
| 323 |
const refIdToExternalHref = new Map();
|
| 324 |
|
| 325 |
if (refsRoot) {
|
| 326 |
+
// Add a unique id to avoid collisions with user-created headers
|
| 327 |
+
setAttr(refsRoot, 'id', 'bibliography-references-list');
|
| 328 |
+
setAttr(refsRoot, 'data-bibliography-block', 'true');
|
| 329 |
+
|
| 330 |
refsOl = toOrderedList(refsRoot);
|
| 331 |
// Collect item ids and linkify their content
|
| 332 |
for (const li of getChildren(refsOl)) {
|
app/plugins/remark/unwrap-citation-links.mjs
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Plugin remark pour transformer les liens markdown contenant des citations en citations simples
|
| 2 |
+
// Transforme [@reference](url) en [@reference]
|
| 3 |
+
export default function remarkUnwrapCitationLinks() {
|
| 4 |
+
return (tree) => {
|
| 5 |
+
// Fonction helper pour extraire le contenu textuel d'un nœud
|
| 6 |
+
const getTextContent = (node) => {
|
| 7 |
+
if (!node) return '';
|
| 8 |
+
if (node.type === 'text') return node.value || '';
|
| 9 |
+
if (Array.isArray(node.children)) {
|
| 10 |
+
return node.children.map(getTextContent).join('');
|
| 11 |
+
}
|
| 12 |
+
return '';
|
| 13 |
+
};
|
| 14 |
+
|
| 15 |
+
const visit = (node, parent) => {
|
| 16 |
+
if (!node || typeof node !== 'object') return;
|
| 17 |
+
|
| 18 |
+
// Parcourir les enfants d'abord (post-order traversal)
|
| 19 |
+
const children = Array.isArray(node.children) ? node.children : [];
|
| 20 |
+
for (let i = 0; i < children.length; i++) {
|
| 21 |
+
const child = children[i];
|
| 22 |
+
visit(child, node);
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
// Si c'est un nœud de type 'link', vérifier son contenu
|
| 26 |
+
if (node.type === 'link' && parent && Array.isArray(parent.children)) {
|
| 27 |
+
// Récupérer le contenu textuel du lien
|
| 28 |
+
const textContent = getTextContent(node);
|
| 29 |
+
|
| 30 |
+
// Debug
|
| 31 |
+
console.log('🔍 Link trouvé:', {
|
| 32 |
+
text: textContent,
|
| 33 |
+
url: node.url,
|
| 34 |
+
matches: /^@\w+/.test(textContent.trim())
|
| 35 |
+
});
|
| 36 |
+
|
| 37 |
+
// Vérifier si c'est une citation (commence par @)
|
| 38 |
+
if (textContent && /^@\w+/.test(textContent.trim())) {
|
| 39 |
+
// Trouver l'index du nœud dans son parent
|
| 40 |
+
const index = parent.children.indexOf(node);
|
| 41 |
+
|
| 42 |
+
if (index !== -1) {
|
| 43 |
+
console.log('✅ Transformation:', textContent);
|
| 44 |
+
// Remplacer le nœud link par un nœud text simple
|
| 45 |
+
parent.children[index] = {
|
| 46 |
+
type: 'text',
|
| 47 |
+
value: textContent.trim()
|
| 48 |
+
};
|
| 49 |
+
}
|
| 50 |
+
}
|
| 51 |
+
}
|
| 52 |
+
};
|
| 53 |
+
|
| 54 |
+
visit(tree, null);
|
| 55 |
+
};
|
| 56 |
+
}
|
| 57 |
+
|
app/src/components/Footer.astro
CHANGED
|
@@ -130,7 +130,10 @@ const { citationText, bibtex, licence, doi } = Astro.props as Props;
|
|
| 130 |
};
|
| 131 |
|
| 132 |
const referencesEl = findFirstOutsideFooter([
|
|
|
|
|
|
|
| 133 |
"#references",
|
|
|
|
| 134 |
".references",
|
| 135 |
".bibliography",
|
| 136 |
]);
|
|
|
|
| 130 |
};
|
| 131 |
|
| 132 |
const referencesEl = findFirstOutsideFooter([
|
| 133 |
+
"#bibliography-references-list",
|
| 134 |
+
"[data-bibliography-block]",
|
| 135 |
"#references",
|
| 136 |
+
"#refs",
|
| 137 |
".references",
|
| 138 |
".bibliography",
|
| 139 |
]);
|
app/src/components/TableOfContents.astro
CHANGED
|
@@ -160,6 +160,11 @@ const { tableOfContentAutoCollapse = false } = Astro.props as Props;
|
|
| 160 |
// Temporarily set height to auto to measure scrollHeight reliably
|
| 161 |
const prev = el.style.height;
|
| 162 |
el.style.height = "auto";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
const h = el.scrollHeight;
|
| 164 |
el.style.height = prev || "";
|
| 165 |
return h;
|
|
@@ -401,7 +406,53 @@ const { tableOfContentAutoCollapse = false } = Astro.props as Props;
|
|
| 401 |
topLevelChanges.forEach(({ li, sub, shouldBeExpanded }) => {
|
| 402 |
if (shouldBeExpanded) {
|
| 403 |
li.classList.remove("collapsed");
|
| 404 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 405 |
const target = measure(sub);
|
| 406 |
animateTo(sub, target);
|
| 407 |
} else {
|
|
@@ -446,9 +497,10 @@ const { tableOfContentAutoCollapse = false } = Astro.props as Props;
|
|
| 446 |
if (mq.addEventListener) mq.addEventListener("change", onMqChange);
|
| 447 |
else if (mq.addListener) mq.addListener(onMqChange);
|
| 448 |
|
| 449 |
-
//
|
| 450 |
-
let
|
| 451 |
-
let
|
|
|
|
| 452 |
|
| 453 |
const onScroll = () => {
|
| 454 |
// active link highlight
|
|
@@ -468,17 +520,31 @@ const { tableOfContentAutoCollapse = false } = Astro.props as Props;
|
|
| 468 |
}
|
| 469 |
}
|
| 470 |
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 482 |
};
|
| 483 |
|
| 484 |
// If auto-collapse, collapse immediately (expand first section) before any scroll
|
|
|
|
| 160 |
// Temporarily set height to auto to measure scrollHeight reliably
|
| 161 |
const prev = el.style.height;
|
| 162 |
el.style.height = "auto";
|
| 163 |
+
|
| 164 |
+
// Force un reflow pour que le navigateur calcule les wraps de texte
|
| 165 |
+
void el.offsetHeight;
|
| 166 |
+
|
| 167 |
+
// Maintenant scrollHeight inclut la vraie hauteur avec tous les line wraps
|
| 168 |
const h = el.scrollHeight;
|
| 169 |
el.style.height = prev || "";
|
| 170 |
return h;
|
|
|
|
| 406 |
topLevelChanges.forEach(({ li, sub, shouldBeExpanded }) => {
|
| 407 |
if (shouldBeExpanded) {
|
| 408 |
li.classList.remove("collapsed");
|
| 409 |
+
|
| 410 |
+
// CRITIQUE : Avant de mesurer, mettre ABSOLUMENT TOUS les sous-éléments
|
| 411 |
+
// dans leur état final (expanded OU collapsed) de manière synchrone
|
| 412 |
+
const allInnerItems = sub.querySelectorAll(
|
| 413 |
+
"li[data-heading-idx]",
|
| 414 |
+
);
|
| 415 |
+
|
| 416 |
+
// D'abord, désactiver toutes les transitions
|
| 417 |
+
allInnerItems.forEach((innerLi) => {
|
| 418 |
+
const innerSub = innerLi.querySelector(":scope > ul");
|
| 419 |
+
if (innerSub) {
|
| 420 |
+
innerSub.style.transition = "none";
|
| 421 |
+
}
|
| 422 |
+
});
|
| 423 |
+
|
| 424 |
+
// Ensuite, mettre chaque élément dans son état final
|
| 425 |
+
allInnerItems.forEach((innerLi) => {
|
| 426 |
+
const innerIdx = Number(
|
| 427 |
+
innerLi.getAttribute("data-heading-idx") || "-1",
|
| 428 |
+
);
|
| 429 |
+
const innerSub = innerLi.querySelector(":scope > ul");
|
| 430 |
+
if (innerSub) {
|
| 431 |
+
if (newActiveAncestors.has(innerIdx)) {
|
| 432 |
+
// Cet élément devrait être expanded
|
| 433 |
+
innerLi.classList.remove("collapsed");
|
| 434 |
+
innerSub.style.height = "auto";
|
| 435 |
+
} else {
|
| 436 |
+
// Cet élément devrait être collapsed
|
| 437 |
+
innerLi.classList.add("collapsed");
|
| 438 |
+
innerSub.style.height = "0px";
|
| 439 |
+
}
|
| 440 |
+
}
|
| 441 |
+
});
|
| 442 |
+
|
| 443 |
+
// Forcer un reflow global pour que TOUT soit calculé
|
| 444 |
+
void sub.offsetHeight;
|
| 445 |
+
|
| 446 |
+
// Réactiver les transitions
|
| 447 |
+
allInnerItems.forEach((innerLi) => {
|
| 448 |
+
const innerSub = innerLi.querySelector(":scope > ul");
|
| 449 |
+
if (innerSub) {
|
| 450 |
+
innerSub.style.transition = "";
|
| 451 |
+
}
|
| 452 |
+
});
|
| 453 |
+
|
| 454 |
+
// Maintenant on peut mesurer avec confiance : tous les éléments
|
| 455 |
+
// sont dans leur état final définitif
|
| 456 |
const target = measure(sub);
|
| 457 |
animateTo(sub, target);
|
| 458 |
} else {
|
|
|
|
| 497 |
if (mq.addEventListener) mq.addEventListener("change", onMqChange);
|
| 498 |
else if (mq.addListener) mq.addListener(onMqChange);
|
| 499 |
|
| 500 |
+
// Debounce pour traiter la dernière mise à jour après que le scroll se stabilise
|
| 501 |
+
let scrollDebounceTimer = null;
|
| 502 |
+
let lastRequestedIdx = -1;
|
| 503 |
+
let isProcessing = false;
|
| 504 |
|
| 505 |
const onScroll = () => {
|
| 506 |
// active link highlight
|
|
|
|
| 520 |
}
|
| 521 |
}
|
| 522 |
|
| 523 |
+
if (activeIdx === prevActiveIdx) return;
|
| 524 |
+
|
| 525 |
+
// Sauvegarder la dernière demande
|
| 526 |
+
lastRequestedIdx = activeIdx;
|
| 527 |
+
|
| 528 |
+
// Si on est en train de traiter, ne rien faire (on traitera la dernière demande après)
|
| 529 |
+
if (isProcessing) return;
|
| 530 |
+
|
| 531 |
+
// Debounce : attendre un peu que le scroll se stabilise
|
| 532 |
+
clearTimeout(scrollDebounceTimer);
|
| 533 |
+
scrollDebounceTimer = setTimeout(() => {
|
| 534 |
+
// Traiter la dernière demande
|
| 535 |
+
if (lastRequestedIdx !== prevActiveIdx) {
|
| 536 |
+
isProcessing = true;
|
| 537 |
+
setCollapsedState(lastRequestedIdx);
|
| 538 |
+
// Le processing flag sera réinitialisé après les animations
|
| 539 |
+
setTimeout(() => {
|
| 540 |
+
isProcessing = false;
|
| 541 |
+
// Si une nouvelle demande est arrivée pendant qu'on traitait, la traiter maintenant
|
| 542 |
+
if (lastRequestedIdx !== prevActiveIdx) {
|
| 543 |
+
onScroll();
|
| 544 |
+
}
|
| 545 |
+
}, 250); // Attendre que les animations soient lancées
|
| 546 |
+
}
|
| 547 |
+
}, 100); // Debounce de 100ms
|
| 548 |
};
|
| 549 |
|
| 550 |
// If auto-collapse, collapse immediately (expand first section) before any scroll
|