1/** 2 * Wasabi Article Integrator - Interactive Table of Contents & Sidebar Smooth Scroll 3 */ 4(function() { 5 'use strict'; 6 7 function getStickyHeaderHeight() { 8 var stickyHeader = document.querySelector('.header.-sticky, header.header, .header, .site-header'); 9 if (stickyHeader) { 10 var height = stickyHeader.offsetHeight; 11 if (height > 30) { 12 return height + 20; 13 } 14 } 15 return 100; // Default offset for sticky header 16 } 17 18 function findTargetElement(hash) { 19 if (!hash || hash === '#' || hash.length < 2) { 20 return null; 21 } 22 23 var id = hash.substring(1); 24 25 // 1. Direct getElementById 26 var el = document.getElementById(id); 27 if (el) return el; 28 29 // 2. CSS escape querySelector 30 try { 31 if (window.CSS && window.CSS.escape) { 32 el = document.querySelector('#' + window.CSS.escape(id)); 33 if (el) return el; 34 } 35 } catch (e) {} 36 37 // 3. Decoded URL ID 38 try { 39 var decoded = decodeURIComponent(id); 40 el = document.getElementById(decoded); 41 if (el) return el; 42 if (window.CSS && window.CSS.escape) { 43 el = document.querySelector('#' + window.CSS.escape(decoded)); 44 if (el) return el; 45 } 46 } catch (e) {} 47 48 // 4. Fallback anchor name 49 var anchors = document.getElementsByName(id); 50 if (anchors && anchors.length > 0) { 51 return anchors[0]; 52 } 53 54 return null; 55 } 56 57 function centerLinkInContainer(container, link) { 58 if (!container || !link || container.scrollHeight <= container.clientHeight) { 59 return; 60 } 61 62 var containerRect = container.getBoundingClientRect(); 63 var linkRect = link.getBoundingClientRect(); 64 var currentScroll = container.scrollTop; 65 var offsetToCenter = (linkRect.top - containerRect.top) - (container.clientHeight / 2) + (linkRect.height / 2); 66 var targetScroll = currentScroll + offsetToCenter; 67 var maxScroll = Math.max(0, container.scrollHeight - container.clientHeight); 68 69 targetScroll = Math.max(0, Math.min(maxScroll, targetScroll)); 70 71 if (Math.abs(targetScroll - currentScroll) < 4) { 72 return; 73 } 74 75 if (typeof container.scrollTo === 'function') { 76 container.scrollTo({ 77 top: targetScroll, 78 behavior: 'smooth' 79 }); 80 } else { 81 container.scrollTop = targetScroll; 82 } 83 } 84 85 function initTocScroll() { 86 var selector = '.wasabi-sidebar-toc-container a[href^="#"], .article-nav-list a[href^="#"]'; 87 var links = document.querySelectorAll(selector); 88 89 links.forEach(function(link) { 90 link.addEventListener('click', function(e) { 91 var href = this.getAttribute('href'); 92 if (!href || href === '#') return; 93 94 var targetEl = findTargetElement(href); 95 if (targetEl) { 96 e.preventDefault(); 97 98 var headerOffset = getStickyHeaderHeight(); 99 var elementPosition = targetEl.getBoundingClientRect().top + window.pageYOffset; 100 var offsetPosition = elementPosition - headerOffset; 101 102 window.scrollTo({ 103 top: Math.max(0, offsetPosition), 104 behavior: 'smooth' 105 }); 106 107 // Set active link class 108 document.querySelectorAll('.wasabi-sidebar-toc-container a, .article-nav-list a').forEach(function(l) { 109 l.classList.remove('active'); 110 }); 111 this.classList.add('active'); 112 113 // Update URL state without page jump 114 if (history.pushState) { 115 history.pushState(null, null, href); 116 } 117 } 118 }); 119 }); 120 121 // ScrollSpy logic for TOC links 122 var tocLinks = document.querySelectorAll('.wasabi-sidebar-toc-container a[href^="#"]'); 123 var fullTextLink = document.querySelector('.article-nav-list a[href="#wasabi-article-section"]'); 124 var sidebarContainer = document.querySelector('.article-nav-sidebar'); 125 var fullTextSection = document.getElementById('wasabi-article-section'); 126 var lastActive = null; 127 128 if (tocLinks.length > 0) { 129 var sectionMap = []; 130 tocLinks.forEach(function(l) { 131 var href = l.getAttribute('href'); 132 var el = findTargetElement(href); 133 if (el) { 134 sectionMap.push({ link: l, element: el }); 135 } 136 }); 137 138 if (sectionMap.length > 0) { 139 var ticking = false;
140 var onScroll = function() { 141 var scrollPosition = window.pageYOffset || document.documentElement.scrollTop; 142 var headerOffset = getStickyHeaderHeight(); 143 var currentActive = null; 144 var fullTextActive = false; 145 146 for (var i = 0; i < sectionMap.length; i++) { 147 var sec = sectionMap[i]; 148 var top = sec.element.getBoundingClientRect().top + scrollPosition - headerOffset - 30; 149 if (scrollPosition >= top) { 150 currentActive = sec.link; 151 } 152 } 153 154 if (fullTextLink && fullTextSection) { 155 var fullTextTop = fullTextSection.getBoundingClientRect().top + scrollPosition - headerOffset - 30; 156 var fullTextBottom = fullTextTop + fullTextSection.offsetHeight; 157 fullTextActive = scrollPosition >= fullTextTop && scrollPosition < fullTextBottom; 158 } 159 160 if (!fullTextActive) { 161 currentActive = null; 162 } 163 164 if (currentActive) { 165 if (currentActive !== lastActive) { 166 document.querySelectorAll('.wasabi-sidebar-toc-container a, .article-nav-list a').forEach(function(l) { 167 l.classList.remove('active'); 168 }); 169 currentActive.classList.add('active'); 170 if (fullTextLink) { 171 fullTextLink.classList.add('active'); 172 } 173 lastActive = currentActive; 174 175 centerLinkInContainer(sidebarContainer, currentActive); 176 } 177 } else if (fullTextActive && fullTextLink) { 178 if (lastActive !== fullTextLink) { 179 document.querySelectorAll('.wasabi-sidebar-toc-container a, .article-nav-list a').forEach(function(l) { 180 l.classList.remove('active'); 181 }); 182 fullTextLink.classList.add('active'); 183 lastActive = fullTextLink; 184 centerLinkInContainer(sidebarContainer, fullTextLink); 185 } 186 } else { 187 if (lastActive) { 188 document.querySelectorAll('.wasabi-sidebar-toc-container a, .article-nav-list a').forEach(function(l) { 189 l.classList.remove('active'); 190 }); 191 lastActive = null; 192 } 193 } 194 }; 195 196 window.addEventListener('scroll', function() { 197 if (!ticking) { 198 window.requestAnimationFrame(function() { 199 onScroll(); 200 ticking = false; 201 }); 202 ticking = true; 203 } 204 }, { passive: true }); 205 206 onScroll(); 207 } 208 } 209 } 210 211 if (document.readyState === 'loading') { 212 document.addEventListener('DOMContentLoaded', initTocScroll); 213 } else { 214 initTocScroll(); 215 } 216})();
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.