|
| 1 | +(() => { |
| 2 | + const root = document.querySelector('article.content'); |
| 3 | + if (!root) return; |
| 4 | + |
| 5 | + const isHeading = el => el && el.nodeType === 1 && /^H[1-6]$/.test(el.tagName); |
| 6 | + const headers = root.querySelectorAll('h2, h3, h4, h5, h6'); |
| 7 | + const ids = new Set(); |
| 8 | + headers.forEach(h => { |
| 9 | + let id = h.id || h.textContent.trim().toLowerCase().replace(/\W+/g, '-'); |
| 10 | + let base = id; |
| 11 | + let i = 2; |
| 12 | + while (ids.has(id)) id = `${base}-${i++}`; |
| 13 | + ids.add(id); |
| 14 | + h.id = id; |
| 15 | + }); |
| 16 | + |
| 17 | + for (const h of Array.from(headers)) { |
| 18 | + // Idempotence : si ce titre est déjà le 1er enfant d'un .section-wrapper, on ne fait rien |
| 19 | + if (h.parentElement?.classList.contains('section-wrapper') && |
| 20 | + h.parentElement.firstElementChild === h) { |
| 21 | + continue; |
| 22 | + } |
| 23 | + |
| 24 | + const wrapper = document.createElement('div'); |
| 25 | + wrapper.className = 'section-wrapper'; |
| 26 | + // (optionnel) pour debug : |
| 27 | + wrapper.dataset.heading = h.tagName.toLowerCase(); |
| 28 | + if (h.id) wrapper.dataset.anchor = h.id; |
| 29 | + |
| 30 | + // Insérer le wrapper juste avant le titre |
| 31 | + h.parentNode.insertBefore(wrapper, h); |
| 32 | + |
| 33 | + // Déplacer le titre dans le wrapper |
| 34 | + wrapper.appendChild(h); |
| 35 | + |
| 36 | + // Puis déplacer tout ce qui suit IMMÉDIATEMENT jusqu'au prochain heading (quel que soit le niveau) |
| 37 | + // -> ainsi on n'englobe jamais les sous-titres |
| 38 | + let node = wrapper.nextSibling; // ancien "nextSibling" du h2, devenu celui du wrapper |
| 39 | + while (node) { |
| 40 | + const next = node.nextSibling; // mémoriser avant déplacement/arrêt |
| 41 | + |
| 42 | + // Si on tombe sur un titre (h1..h6), on s'arrête (le prochain wrapper le prendra en charge) |
| 43 | + if (node.nodeType === 1 && isHeading(node)) break; |
| 44 | + |
| 45 | + // Sinon, c'est du contenu d'intro : on le rapatrie dans ce wrapper |
| 46 | + wrapper.appendChild(node); |
| 47 | + |
| 48 | + node = next; |
| 49 | + } |
| 50 | + } |
| 51 | +})(); |
| 52 | + |
1 | 53 | (() => { |
2 | 54 | let contentHeaders= document.querySelectorAll("main h2[id]"); |
3 | 55 | if (!document.querySelector('html').classList.contains('homepage') && contentHeaders) { |
|
71 | 123 | const menuLinks = document.querySelectorAll('#onthispage a'); |
72 | 124 | const observer = new IntersectionObserver(entries => { |
73 | 125 | entries.forEach(entry => { |
74 | | - const id = entry.target.getAttribute("id"); |
| 126 | + const id = entry.target.getAttribute("data-anchor"); |
75 | 127 | const link = document.querySelector(`#onthispage a[href="#${id}"]`); |
76 | 128 |
|
77 | 129 | if (entry.isIntersecting) { |
|
80 | 132 | } |
81 | 133 | }); |
82 | 134 | }, { |
83 | | - rootMargin: "-50% 0px -50% 0px", // trigger when the section is centered in viewport |
| 135 | + root: null, |
| 136 | + rootMargin: "0px 0px -100% 0px", |
84 | 137 | threshold: 0 |
85 | 138 | }); |
86 | 139 |
|
87 | | - sections.forEach(section => observer.observe(section)); |
| 140 | + sections.forEach(section => observer.observe(section.parentElement)); |
88 | 141 | } |
89 | 142 |
|
90 | 143 | // generate code snippet copy/paste |
|
111 | 164 | notification.classList.remove('bg-black'); |
112 | 165 | }, 500); |
113 | 166 | } catch (err) { |
114 | | - console.error('Failed to copy: ', err); |
115 | 167 | notification.innerHTML = 'Copy failed!'; |
116 | 168 | notification.classList.add('bg-red-800'); |
117 | 169 | notification.classList.remove('hidden'); |
|
0 commit comments