tfrere HF Staff commited on
Commit
6b33c1d
·
1 Parent(s): 628c605

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: [ createBackIcon() ]
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: [ { type: 'text', value: String(idx + 1) } ]
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
- // Maintenant on peut mesurer avec confiance
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- // Throttle pour éviter trop d'appels pendant le scroll
450
- let scrollTimeout = null;
451
- let isScrolling = false;
 
452
 
453
  const onScroll = () => {
454
  // active link highlight
@@ -468,17 +520,31 @@ const { tableOfContentAutoCollapse = false } = Astro.props as Props;
468
  }
469
  }
470
 
471
- // Throttle : mettre à jour le collapse seulement si l'index change ET qu'on n'est pas déjà en train de traiter
472
- if (activeIdx !== prevActiveIdx && !isScrolling) {
473
- isScrolling = true;
474
- setCollapsedState(activeIdx);
475
-
476
- // Permettre une nouvelle mise à jour après un délai minimal
477
- clearTimeout(scrollTimeout);
478
- scrollTimeout = setTimeout(() => {
479
- isScrolling = false;
480
- }, 50); // 50ms entre chaque traitement
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