1// Table of Contents Generator 2(function() { 3 'use strict'; 4 5 // Generate TOC from content headings 6 function generateTOC() { 7 const content = document.getElementById('content'); 8 const tocContainers = document.querySelectorAll('.toc-content'); 9 10 if (!content || tocContainers.length === 0) return; 11 12 // Find all h2 and h3 headings with IDs 13 const headings = content.querySelectorAll('h2[id], h3[id], h4[id], h5[id], h6[id]'); 14 15 if (headings.length === 0) { 16 // Hide TOC wrappers if no headings found 17 document.querySelectorAll('.toc-wrapper').forEach(el => el.style.display = 'none'); 18 return; 19 } 20 21 // Build TOC HTML 22 const tocHTML = buildTOCHTML(headings); 23 24 // Insert TOC into all containers 25 tocContainers.forEach(container => { 26 container.innerHTML = tocHTML; 27 }); 28 29 // Set up scroll tracking 30 setupScrollTracking(headings); 31 } 32 33 function buildTOCHTML(headings) { 34 let html = '<ul class="space-y-3 text-sm list-disc pl-5 marker:text-stone-400 dark:marker:text-stone-500">'; 35 let currentLevel = 2; 36
37 headings.forEach(heading => { 38 const level = parseInt(heading.tagName.substr(1)); 39 const text = heading.textContent; 40 const id = heading.id; 41 42 if (level > currentLevel) { 43 html += '<ul class="space-y-3 pl-4 mt-2 list-disc marker:text-stone-400 dark:marker:text-stone-500">'; 44 } else if (level < currentLevel) { 45 html += '</ul>'; 46 } 47 48 html += `<li class="mb-3"><a href="#${id}" class="toc-link text-stone-700 dark:text-stone-300 no-underline transition-colors" data-heading-id="${id}">${text}</a></li>`; 49 currentLevel = level; 50 }); 51 52 // Close any open ul tags 53 while (currentLevel > 2) { 54 html += '</ul>'; 55 currentLevel--; 56 } 57 58 html += '</ul>'; 59 return html; 60 } 61 62 // Set up scroll tracking 63 function setupScrollTracking(headings) { 64 // Convert NodeList to Array for easier manipulation 65 const headingsArray = Array.from(headings); 66 67 // Update active heading on scroll 68 function updateActiveHeading() { 69 // Threshold: top of viewport + small offset (e.g., 100px for header) 70 const scrollOffset = 100; 71 72 // Find the heading closest to the top of the viewport 73 let activeHeading = null; 74 75 for (let i = headingsArray.length - 1; i >= 0; i--) { 76 const heading = headingsArray[i]; 77 const rect = heading.getBoundingClientRect(); 78 79 // Check if heading is above or at the scroll offset 80 if (rect.top <= scrollOffset) { 81 activeHeading = heading; 82 break; 83 } 84 } 85 86 // If no heading is above the offset, use the first one if it's visible 87 if (!activeHeading && headingsArray.length > 0) { 88 const firstRect = headingsArray[0].getBoundingClientRect(); 89 if (firstRect.top > scrollOffset) { 90 // We're above the first heading, no active heading 91 clearActiveLink(); 92 return; 93 } 94 } 95 96 if (activeHeading) { 97 setActiveLink(activeHeading.id); 98 } else { 99 clearActiveLink(); 100 } 101 } 102 103 // Update on scroll with throttling for performance 104 let ticking = false; 105 window.addEventListener('scroll', function() { 106 if (!ticking) { 107 window.requestAnimationFrame(function() { 108 updateActiveHeading(); 109 ticking = false; 110 }); 111 ticking = true; 112 } 113 }); 114 115 // Update on page load and hash change 116 updateActiveHeading(); 117 window.addEventListener('hashchange', function() { 118 setTimeout(updateActiveHeading, 100); 119 }); 120 } 121 122 // Set active link in TOC 123 function setActiveLink(id) { 124 // Remove active class from all links first 125 document.querySelectorAll('.toc-link').forEach(link => { 126 link.classList.remove('toc-active'); 127 }); 128 129 // Add active class to current link 130 const links = document.querySelectorAll(`.toc-link[data-heading-id="${id}"]`); 131 links.forEach(link => { 132 link.classList.add('toc-active'); 133 }); 134 } 135 136 // Clear active link 137 function clearActiveLink() { 138 document.querySelectorAll('.toc-link').forEach(link => { 139 link.classList.remove('toc-active'); 140 }); 141 } 142 143 // Run on page load 144 if (document.readyState === 'loading') { 145 document.addEventListener('DOMContentLoaded', generateTOC); 146 } else { 147 generateTOC(); 148 } 149})();
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.