1(function ($, Drupal) { 2 3 $(document).ready(function () { 4 createAnchors(); 5 6 if ($('.sidebar.toc .anchor-list li').length > 0) { 7 if ($(window).width() > 1573) { 8 $('.sidebar.toc').css('display', 'block'); 9 } 10 } 11 else { 12 $('.sidebar.toc:not(.sidebar--infos)').addClass('is-empty'); 13 } 14 }) 15 16 $(window).resize(function () { 17 if ($('.sidebar.toc .anchor-list li').length > 0) { 18 if ($(window).width() > 1573) { 19 $('.sidebar.toc').show(); 20 } 21 } 22 }) 23 24 Drupal.behaviors.toc = { 25 attach: () => { 26 if ($('.mobile-toggles-wrapper button').length > 0) { 27 $('.mobile-toggles-wrapper button').unbind().click(function () { 28 $('.sidebar.toc').css('display', 'flex'); 29 }) 30 } 31 if ($('.toc__header .close-button').length > 0) { 32 $('.toc__header .close-button').unbind().click(function () { 33 $('.sidebar.toc').fadeOut(); 34 }) 35 } 36 } 37 }; 38 39 /** 40 * Build the anchor navigation. 41 * 42 * @return {void}; 43 */ 44 function createAnchors() { 45 // Content wrapper. 46 const content = document.querySelector('.node-content'); 47 const anchorList = document.getElementById('anchor-list'); 48 49 if (!content || !anchorList) { 50 return; 51 } 52 53 // Get headings. 54 const anchorTarget = [ 55 '.node-content h2:not(.node--card-image__title):not(.toc__title)', 56 ]; 57 const anchorSelector = anchorTarget.join(','); 58 let headingList = content.querySelectorAll(anchorSelector); 59 60 // Convert NodeList to an array. 61 headingList = Array.prototype.slice.call(headingList); 62 activeAnchorLinks(headingList); 63 64 if (!headingList.length) { 65 document.getElementById('sidebar').style.display = 'none'; 66 return; 67 } 68 69 generateAnchorList(headingList, anchorList); 70 handleAnchorScroll(anchorList); 71 } 72 73 function generateAnchorList(headingList, anchorList) { 74 // Create anchors. 75 let i = 0; 76 [].forEach.call(headingList, (heading) => { 77 let anchorText = heading.textContent; 78 // Remove space & empty lines. 79 anchorText = anchorText.trim(); 80 81 if (!anchorText) { 82 return; 83 } 84 85 // Add unique id on headings. 86 let id = `a-${i}`; 87 if (document.getElementById(id)) { 88 id += `-${i + 1}`; 89 } 90 91 // Add the id on the headings. 92 heading.setAttribute('id', id); 93 94 // Generate link toward the anchor. 95 const link = document.createElement('a'); 96 link.setAttribute('href', `#${id}`); 97 link.textContent = anchorText; 98 99 const li = document.createElement('li'); 100 101 // Active the first link. 102 if (i === 0) { 103 link.classList.add('anchor--is-active'); 104 li.classList.add('anchor-parent--is-active'); 105 } 106 107 li.appendChild(link); 108 anchorList.appendChild(li); 109 i++; 110 111 }); 112 } 113 114 function handleAnchorScroll(anchorList) { 115 // Smooth scrool. 116 const anchorItemList = anchorList.querySelectorAll('a'); 117 [].forEach.call(anchorItemList, (link) => { 118 link.addEventListener('click', (e) => { 119 e.preventDefault(); 120 const a = link; 121 const viewportRatio = 0.3; 122 let anchorId = a.getAttribute('href'); 123 124 [].forEach.call(anchorList.querySelectorAll('a.anchor--is-active-forced'), (activeLink) => { 125 activeLink.classList.remove('anchor--is-active-forced'); 126 }); 127 a.classList.add('anchor--is-active-forced'); 128 129 // Remove the active class after scroll. 130 const scrollDuration = 500; 131 setTimeout(() => { 132 a.classList.remove('anchor--is-active-forced'); 133 }, scrollDuration); 134 135 anchorId = anchorId.substring(1, anchorId.length); 136 const anchor = document.getElementById(anchorId); 137 const bodyRect = document.body.getBoundingClientRect().top; 138 const screenHeight = window.innerHeight || document.documentElement.clientHeight; 139 const anchorOffset = anchor.getBoundingClientRect().top - bodyRect - viewportRatio * screenHeight; 140 const anchorBlock = document.getElementById('anchor-list'); 141 142 // Collapse menu. 143 anchorBlock.classList.remove('anchors--are-expanded'); 144 145 // Smooth. 146 window.scrollTo(0, anchorOffset); 147 // window.scroll({ 148 // top: anchorOffset,
149 // left: 0, 150 // }); 151 }); 152 }); 153 } 154 155 /** 156 * Determine if the specified DOM object is in the viewport. 157 * 158 * @param {Object} el - DOM object 159 * @returns {Bool} True if element is in the viewport. 160 */ 161 function isInViewport(el) { 162 const bounding = el.getBoundingClientRect(); 163 const viewportRatio = 0.4; 164 165 // is above. 166 if (bounding.top + bounding.height <= 0) { 167 return false; 168 } 169 170 const screenHeight = window.innerHeight || document.documentElement.clientHeight; 171 172 // is bellow. 173 if (bounding.top > screenHeight * viewportRatio) { 174 return false; 175 } 176 177 // is in the viewport. 178 return true; 179 } 180 181 /** 182 * Highlight the current anchor. 183 * 184 * @param {NodeList} headingList 185 * Set of headings. 186 * 187 * @return {void}; 188 */ 189 function activeAnchorLinks(headingList) { 190 window.addEventListener('scroll', (e) => { 191 [].forEach.call(headingList, (heading) => { 192 if (isInViewport(heading)) { 193 const id = heading.getAttribute('id'); 194 const matchingLink = document.querySelector(`#anchor-list a[href="#${id}"]`); 195 196 // Remove the active state. 197 if (matchingLink) { 198 const anchorList = document.querySelectorAll('#anchor-list a'); 199 [].forEach.call(anchorList, (anchor) => { 200 anchor.classList.remove('anchor--is-active'); 201 anchor.parentNode.classList.remove('anchor-parent--is-active'); 202 }); 203 204 matchingLink.classList.add('anchor--is-active'); 205 matchingLink.parentNode.classList.add('anchor-parent--is-active'); 206 } 207 } 208 }); 209 }); 210 } 211 212})(jQuery, Drupal, drupalSettings);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.