1// Populate the sidebar 2// 3// This is a script, and not included directly in the page, to control the total size of the book. 4// The TOC contains an entry for each page, so if each page includes a copy of the TOC, 5// the total size of the page becomes O(n**2). 6class MDBookSidebarScrollbox extends HTMLElement { 7 constructor() { 8 super(); 9 } 10 connectedCallback() { 11 this.innerHTML = '<ol class="chapter"><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="preface.html">Learn LLM Serving</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="setup.html">Setting Up the Environment</a></span></li><li class="chapter-item expanded "><li class="spacer"></li></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week1-overview.html"><strong aria-hidden="true">1.</strong> Week 1: From Matmul to Text</a></span><ol class="section"><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week1-01-attention.html"><strong aria-hidden="true">1.1.</strong> Attention and Multi-Head Attention</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week1-02-positional-encodings.html"><strong aria-hidden="true">1.2.</strong> Positional Encodings and RoPE</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week1-03-gqa.html"><strong aria-hidden="true">1.3.</strong> Grouped-Query and Multi-Query Attention</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week1-04-rmsnorm-and-mlp.html"><strong aria-hidden="true">1.4.</strong> RMSNorm and MLP</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week1-05-qwen3-model.html"><strong aria-hidden="true">1.5.</strong> The Qwen3 Model</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week1-06-generate-response.html"><strong aria-hidden="true">1.6.</strong> Generating the Response</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week1-07-sampling-prepare.html"><strong aria-hidden="true">1.7.</strong> Sampling and Preparing for Week 2</a></span></li></ol><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week2-overview.html"><strong aria-hidden="true">2.</strong> ð§ Week 2: A Faster Single Request</a></span><ol class="section"><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week2-01-kv-cache.html"><strong aria-hidden="true">2.1.</strong> ð§ Day 1: Cache and Measure</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week2-02-quantize-model.html"><strong aria-hidden="true">2.2.</strong> ð§ Day 2: Keep W4 Packed</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week2-03-simd-matrix-prefill.html"><strong aria-hidden="true">2.3.</strong> ð§ Day 3: SIMD Matrix Prefill</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week2-04-fused-model-kernels.html"><strong aria-hidden="true">2.4.</strong> ð§ Day 4: Fused Model Primitives</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week2-05-tiled-prefill-attention.html"><strong aria-hidden="true">2.5.</strong> ð§ Day 5: Tiled Dense Prefill Attention</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week2-02-benchmark-profile.html"><strong aria-hidden="true">2.6.</strong> Historical Week 2 lesson addresses</a></span><ol class="section"><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week2-03-quantize-model.html"><strong aria-hidden="true">2.6.1.</strong> Earlier quantization lesson</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week2-05-simd-matrix-prefill.html"><strong aria-hidden="true">2.6.2.</strong> Earlier SIMD-prefill lesson</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week2-06-operator-lab.html"><strong aria-hidden="true">2.6.3.</strong> Earlier bounded-decode lab</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week2-07-split-k-prefill.html"><strong aria-hidden="true">2.6.4.</strong> Earlier Split-K lab</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week2-advanced-profiling.html"><strong aria-hidden="true">2.6.5.</strong> Earlier optional macOS capture lab</a></span></li></ol></li></ol><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week3-overview.html"><strong aria-hidden="true">3.</strong> ð§ Week 3: Build a Mini vLLM</a></span><ol class="section"><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week3-01-continuous-batching.html"><strong aria-hidden="true">3.1.</strong> ð§ Continuous Batching</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week3-02-chunked-prefill.html"><strong aria-hidden="true">3.2.</strong> ð§ Chunked Prefill</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week3-03-paged-attention-part1.html"><strong aria-hidden="true">3.3.</strong> ð§ Paged KV Cache</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week3-04-paged-attention-part2.html"><strong aria-hidden="true">3.4.</strong> ð§ Direct Paged Attention</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week3-05-flash-attention.html"><strong aria-hidden="true">3.5.</strong> ð§ Paged FlashAttention</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week3-optional-moe.html"><strong aria-hidden="true">3.6.</strong> ð§ Day 6 (Optional): Mixture of Experts</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week3-optional-speculative-decoding.html"><strong aria-hidden="true">3.7.</strong> ð§ Day 7 (Optional): Speculative Decoding</a></span></li></ol><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week4-overview.html"><strong aria-hidden="true">4.</strong> ð§ Week 4: Build a Coding Agent</a></span><ol class="section"><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week4-01-agent-loop.html"><strong aria-hidden="true">4.1.</strong> ð§ Day 1: Validated Agent Loop</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week4-02-tools.html"><strong aria-hidden="true">4.2.</strong> ð§ Day 2: Inspect a Workspace</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week4-03-safe-editing.html"><strong aria-hidden="true">4.3.</strong> ð§ Day 3: Edit, Validate, and Record</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week4-04-sessions.html"><strong aria-hidden="true">4.4.</strong> ð§ Day 4: Checkpoint and Resume</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week4-05-compaction.html"><strong aria-hidden="true">4.5.</strong> ð§ Day 5: Compact Completed Work</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week4-06-steering.html"><strong aria-hidden="true">4.6.</strong> ð§ Day 6: Inspect and Steer a Paused Agent</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week4-07-evaluation.html"><strong aria-hidden="true">4.7.</strong> ð§ Day 7: Evaluate Observable Outcomes</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week4-08-fork-steer-select.html"><strong aria-hidden="true">4.8.</strong> ð§ Day 8: Fork, Steer, and Select</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="week4-09-bound-tool-evidence.html"><strong aria-hidden="true">4.9.</strong> ð§ Day 9: Bound Tool Evidence</a></span></li></ol><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="appendix-performance.html"><strong aria-hidden="true">5.</strong> ð§ Appendix: Performance Evidence Ledger</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="sponsor.html"><strong aria-hidden="true">6.</strong> Sponsored by Raft.build</a></span></li><li class="chapter-item expanded "><li class="spacer"></li></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="glossary.html">Glossary Index</a></span></li></ol>'; 12 // Set the current, active page, and reveal it if it's hidden 13 let current_page = document.location.href.toString().split('#')[0].split('?')[0]; 14 if (current_page.endsWith('/')) { 15 current_page += 'index.html'; 16 } 17 const links = Array.prototype.slice.call(this.querySelectorAll('a')); 18 const l = links.length; 19 for (let i = 0; i < l; ++i) { 20 const link = links[i]; 21 const href = link.getAttribute('href'); 22 if (href && !href.startsWith('#') && !/^(?:[a-z+]+:)?\/\//.test(href)) { 23 link.href = path_to_root + href; 24 } 25 // The 'index' page is supposed to alias the first chapter in the book. 26 // Check both with and without the '.html' suffix to be robust against pretty URLs 27 if (link.href.replace(/\.html$/, '') === current_page.replace(/\.html$/, '') 28 || i === 0 29 && path_to_root === '' 30 && current_page.endsWith('/index.html')) { 31 link.classList.add('active'); 32 let parent = link.parentElement; 33 while (parent) { 34 if (parent.tagName === 'LI' && parent.classList.contains('chapter-item')) { 35 parent.classList.add('expanded'); 36 } 37 parent = parent.parentElement; 38 } 39 } 40 } 41 // Track and set sidebar scroll position 42 this.addEventListener('click', e => { 43 if (e.target.tagName === 'A') { 44 const clientRect = e.target.getBoundingClientRect(); 45 const sidebarRect = this.getBoundingClientRect(); 46 sessionStorage.setItem('sidebar-scroll-offset', clientRect.top - sidebarRect.top); 47 } 48 }, { passive: true }); 49 const sidebarScrollOffset = sessionStorage.getItem('sidebar-scroll-offset'); 50 sessionStorage.removeItem('sidebar-scroll-offset'); 51 if (sidebarScrollOffset !== null) { 52 // preserve sidebar scroll position when navigating via links within sidebar 53 const activeSection = this.querySelector('.active'); 54 if (activeSection) { 55 const clientRect = activeSection.getBoundingClientRect(); 56 const sidebarRect = this.getBoundingClientRect(); 57 const currentOffset = clientRect.top - sidebarRect.top; 58 this.scrollTop += currentOffset - parseFloat(sidebarScrollOffset); 59 } 60 } else { 61 // scroll sidebar to current active section when navigating via 62 // 'next/previous chapter' buttons 63 const activeSection = document.querySelector('#mdbook-sidebar .active'); 64 if (activeSection) { 65 activeSection.scrollIntoView({ block: 'center' }); 66 } 67 } 68 // Toggle buttons 69 const sidebarAnchorToggles = document.querySelectorAll('.chapter-fold-toggle'); 70 function toggleSection(ev) { 71 ev.currentTarget.parentElement.parentElement.classList.toggle('expanded'); 72 }
73 Array.from(sidebarAnchorToggles).forEach(el => { 74 el.addEventListener('click', toggleSection); 75 }); 76 } 77} 78window.customElements.define('mdbook-sidebar-scrollbox', MDBookSidebarScrollbox); 79 80 81// --------------------------------------------------------------------------- 82// Support for dynamically adding headers to the sidebar. 83 84(function() { 85 // This is used to detect which direction the page has scrolled since the 86 // last scroll event. 87 let lastKnownScrollPosition = 0; 88 // This is the threshold in px from the top of the screen where it will 89 // consider a header the "current" header when scrolling down. 90 const defaultDownThreshold = 150; 91 // Same as defaultDownThreshold, except when scrolling up. 92 const defaultUpThreshold = 300; 93 // The threshold is a virtual horizontal line on the screen where it 94 // considers the "current" header to be above the line. The threshold is 95 // modified dynamically to handle headers that are near the bottom of the 96 // screen, and to slightly offset the behavior when scrolling up vs down. 97 let threshold = defaultDownThreshold; 98 // This is used to disable updates while scrolling. This is needed when 99 // clicking the header in the sidebar, which triggers a scroll event. It 100 // is somewhat finicky to detect when the scroll has finished, so this 101 // uses a relatively dumb system of disabling scroll updates for a short 102 // time after the click. 103 let disableScroll = false; 104 // Array of header elements on the page. 105 let headers; 106 // Array of li elements that are initially collapsed headers in the sidebar. 107 // I'm not sure why eslint seems to have a false positive here. 108 // eslint-disable-next-line prefer-const 109 let headerToggles = []; 110 // This is a debugging tool for the threshold which you can enable in the console. 111 let thresholdDebug = false; 112 113 // Updates the threshold based on the scroll position. 114 function updateThreshold() { 115 const scrollTop = window.pageYOffset || document.documentElement.scrollTop; 116 const windowHeight = window.innerHeight; 117 const documentHeight = document.documentElement.scrollHeight; 118 119 // The number of pixels below the viewport, at most documentHeight. 120 // This is used to push the threshold down to the bottom of the page 121 // as the user scrolls towards the bottom. 122 const pixelsBelow = Math.max(0, documentHeight - (scrollTop + windowHeight)); 123 // The number of pixels above the viewport, at least defaultDownThreshold. 124 // Similar to pixelsBelow, this is used to push the threshold back towards 125 // the top when reaching the top of the page. 126 const pixelsAbove = Math.max(0, defaultDownThreshold - scrollTop); 127 // How much the threshold should be offset once it gets close to the 128 // bottom of the page. 129 const bottomAdd = Math.max(0, windowHeight - pixelsBelow - defaultDownThreshold); 130 let adjustedBottomAdd = bottomAdd; 131 132 // Adjusts bottomAdd for a small document. The calculation above 133 // assumes the document is at least twice the windowheight in size. If 134 // it is less than that, then bottomAdd needs to be shrunk 135 // proportional to the difference in size. 136 if (documentHeight < windowHeight * 2) { 137 const maxPixelsBelow = documentHeight - windowHeight; 138 const t = 1 - pixelsBelow / Math.max(1, maxPixelsBelow); 139 const clamp = Math.max(0, Math.min(1, t)); 140 adjustedBottomAdd *= clamp; 141 } 142 143 let scrollingDown = true; 144 if (scrollTop < lastKnownScrollPosition) { 145 scrollingDown = false; 146 } 147 148 if (scrollingDown) { 149 // When scrolling down, move the threshold up towards the default 150 // downwards threshold position. If near the bottom of the page, 151 // adjustedBottomAdd will offset the threshold towards the bottom 152 // of the page. 153 const amountScrolledDown = scrollTop - lastKnownScrollPosition; 154 const adjustedDefault = defaultDownThreshold + adjustedBottomAdd; 155 threshold = Math.max(adjustedDefault, threshold - amountScrolledDown); 156 } else { 157 // When scrolling up, move the threshold down towards the default 158 // upwards threshold position. If near the bottom of the page, 159 // quickly transition the threshold back up where it normally 160 // belongs. 161 const amountScrolledUp = lastKnownScrollPosition - scrollTop; 162 const adjustedDefault = defaultUpThreshold - pixelsAbove 163 + Math.max(0, adjustedBottomAdd - defaultDownThreshold); 164 threshold = Math.min(adjustedDefault, threshold + amountScrolledUp); 165 } 166 167 if (documentHeight <= windowHeight) { 168 threshold = 0; 169 } 170 171 if (thresholdDebug) { 172 const id = 'mdbook-threshold-debug-data'; 173 let data = document.getElementById(id); 174 if (data === null) { 175 data = document.createElement('div'); 176 data.id = id; 177 data.style.cssText = `
178 position: fixed; 179 top: 50px; 180 right: 10px; 181 background-color: 0xeeeeee; 182 z-index: 9999; 183 pointer-events: none; 184 `; 185 document.body.appendChild(data); 186 } 187 data.innerHTML = ` 188 <table> 189 <tr><td>documentHeight</td><td>${documentHeight.toFixed(1)}</td></tr> 190 <tr><td>windowHeight</td><td>${windowHeight.toFixed(1)}</td></tr> 191 <tr><td>scrollTop</td><td>${scrollTop.toFixed(1)}</td></tr> 192 <tr><td>pixelsAbove</td><td>${pixelsAbove.toFixed(1)}</td></tr> 193 <tr><td>pixelsBelow</td><td>${pixelsBelow.toFixed(1)}</td></tr> 194 <tr><td>bottomAdd</td><td>${bottomAdd.toFixed(1)}</td></tr> 195 <tr><td>adjustedBottomAdd</td><td>${adjustedBottomAdd.toFixed(1)}</td></tr> 196 <tr><td>scrollingDown</td><td>${scrollingDown}</td></tr> 197 <tr><td>threshold</td><td>${threshold.toFixed(1)}</td></tr> 198 </table> 199 `; 200 drawDebugLine(); 201 } 202 203 lastKnownScrollPosition = scrollTop; 204 } 205 206 function drawDebugLine() { 207 if (!document.body) { 208 return; 209 } 210 const id = 'mdbook-threshold-debug-line'; 211 const existingLine = document.getElementById(id); 212 if (existingLine) { 213 existingLine.remove(); 214 } 215 const line = document.createElement('div'); 216 line.id = id; 217 line.style.cssText = ` 218 position: fixed; 219 top: ${threshold}px; 220 left: 0; 221 width: 100vw; 222 height: 2px; 223 background-color: red; 224 z-index: 9999; 225 pointer-events: none; 226 `; 227 document.body.appendChild(line); 228 } 229 230 function mdbookEnableThresholdDebug() { 231 thresholdDebug = true; 232 updateThreshold(); 233 drawDebugLine(); 234 } 235 236 window.mdbookEnableThresholdDebug = mdbookEnableThresholdDebug; 237 238 // Updates which headers in the sidebar should be expanded. If the current 239 // header is inside a collapsed group, then it, and all its parents should 240 // be expanded. 241 function updateHeaderExpanded(currentA) { 242 // Add expanded to all header-item li ancestors. 243 let current = currentA.parentElement; 244 while (current) { 245 if (current.tagName === 'LI' && current.classList.contains('header-item')) { 246 current.classList.add('expanded'); 247 } 248 current = current.parentElement; 249 } 250 } 251 252 // Updates which header is marked as the "current" header in the sidebar. 253 // This is done with a virtual Y threshold, where headers at or below 254 // that line will be considered the current one. 255 function updateCurrentHeader() { 256 if (!headers || !headers.length) { 257 return; 258 } 259 260 // Reset the classes, which will be rebuilt below. 261 const els = document.getElementsByClassName('current-header'); 262 for (const el of els) { 263 el.classList.remove('current-header'); 264 } 265 for (const toggle of headerToggles) { 266 toggle.classList.remove('expanded'); 267 } 268 269 // Find the last header that is above the threshold. 270 let lastHeader = null; 271 for (const header of headers) { 272 const rect = header.getBoundingClientRect(); 273 if (rect.top <= threshold) { 274 lastHeader = header; 275 } else { 276 break; 277 } 278 } 279 if (lastHeader === null) { 280 lastHeader = headers[0]; 281 const rect = lastHeader.getBoundingClientRect(); 282 const windowHeight = window.innerHeight; 283 if (rect.top >= windowHeight) { 284 return; 285 } 286 } 287 288 // Get the anchor in the summary. 289 const href = '#' + lastHeader.id; 290 const a = [...document.querySelectorAll('.header-in-summary')] 291 .find(element => element.getAttribute('href') === href); 292 if (!a) { 293 return; 294 } 295 296 a.classList.add('current-header'); 297 298 updateHeaderExpanded(a); 299 } 300 301 // Updates which header is "current" based on the threshold line. 302 function reloadCurrentHeader() { 303 if (disableScroll) { 304 return; 305 } 306 updateThreshold(); 307 updateCurrentHeader(); 308 } 309 310 311 // When clicking on a header in the sidebar, this adjusts the threshold so 312 // that it is located next to the header. This is so that header becomes 313 // "current". 314 function headerThresholdClick(event) { 315 // See disableScroll description why this is done. 316 disableScroll = true; 317 setTimeout(() => { 318 disableScroll = false;
319 }, 100); 320 // requestAnimationFrame is used to delay the update of the "current" 321 // header until after the scroll is done, and the header is in the new 322 // position. 323 requestAnimationFrame(() => { 324 requestAnimationFrame(() => { 325 // Closest is needed because if it has child elements like <code>. 326 const a = event.target.closest('a'); 327 const href = a.getAttribute('href'); 328 const targetId = href.substring(1); 329 const targetElement = document.getElementById(targetId); 330 if (targetElement) { 331 threshold = targetElement.getBoundingClientRect().bottom; 332 updateCurrentHeader(); 333 } 334 }); 335 }); 336 } 337 338 // Takes the nodes from the given head and copies them over to the 339 // destination, along with some filtering. 340 function filterHeader(source, dest) { 341 const clone = source.cloneNode(true); 342 clone.querySelectorAll('mark').forEach(mark => { 343 mark.replaceWith(...mark.childNodes); 344 }); 345 dest.append(...clone.childNodes); 346 } 347 348 // Scans page for headers and adds them to the sidebar. 349 document.addEventListener('DOMContentLoaded', function() { 350 const activeSection = document.querySelector('#mdbook-sidebar .active'); 351 if (activeSection === null) { 352 return; 353 } 354 355 const main = document.getElementsByTagName('main')[0]; 356 headers = Array.from(main.querySelectorAll('h2, h3, h4, h5, h6')) 357 .filter(h => h.id !== '' && h.children.length && h.children[0].tagName === 'A'); 358 359 if (headers.length === 0) { 360 return; 361 } 362 363 // Build a tree of headers in the sidebar. 364 365 const stack = []; 366 367 const firstLevel = parseInt(headers[0].tagName.charAt(1)); 368 for (let i = 1; i < firstLevel; i++) { 369 const ol = document.createElement('ol'); 370 ol.classList.add('section'); 371 if (stack.length > 0) { 372 stack[stack.length - 1].ol.appendChild(ol); 373 } 374 stack.push({level: i + 1, ol: ol}); 375 } 376 377 // The level where it will start folding deeply nested headers. 378 const foldLevel = 3; 379 380 for (let i = 0; i < headers.length; i++) { 381 const header = headers[i]; 382 const level = parseInt(header.tagName.charAt(1)); 383 384 const currentLevel = stack[stack.length - 1].level; 385 if (level > currentLevel) { 386 // Begin nesting to this level. 387 for (let nextLevel = currentLevel + 1; nextLevel <= level; nextLevel++) { 388 const ol = document.createElement('ol'); 389 ol.classList.add('section'); 390 const last = stack[stack.length - 1]; 391 const lastChild = last.ol.lastChild; 392 // Handle the case where jumping more than one nesting 393 // level, which doesn't have a list item to place this new 394 // list inside of. 395 if (lastChild) { 396 lastChild.appendChild(ol); 397 } else { 398 last.ol.appendChild(ol); 399 } 400 stack.push({level: nextLevel, ol: ol}); 401 } 402 } else if (level < currentLevel) { 403 while (stack.length > 1 && stack[stack.length - 1].level > level) { 404 stack.pop(); 405 } 406 } 407 408 const li = document.createElement('li'); 409 li.classList.add('header-item'); 410 li.classList.add('expanded'); 411 if (level < foldLevel) { 412 li.classList.add('expanded'); 413 } 414 const span = document.createElement('span'); 415 span.classList.add('chapter-link-wrapper'); 416 const a = document.createElement('a'); 417 span.appendChild(a); 418 a.href = '#' + header.id; 419 a.classList.add('header-in-summary'); 420 filterHeader(header.children[0], a); 421 a.addEventListener('click', headerThresholdClick); 422 const nextHeader = headers[i + 1]; 423 if (nextHeader !== undefined) { 424 const nextLevel = parseInt(nextHeader.tagName.charAt(1)); 425 if (nextLevel > level && level >= foldLevel) { 426 const toggle = document.createElement('a'); 427 toggle.classList.add('chapter-fold-toggle'); 428 toggle.classList.add('header-toggle'); 429 toggle.addEventListener('click', () => { 430 li.classList.toggle('expanded'); 431 }); 432 const toggleDiv = document.createElement('div'); 433 toggleDiv.textContent = 'â±'; 434 toggle.appendChild(toggleDiv); 435 span.appendChild(toggle); 436 headerToggles.push(li); 437 } 438 } 439 li.appendChild(span); 440 441 const currentParent = stack[stack.length - 1]; 442 currentParent.ol.appendChild(li); 443 } 444 445 const onThisPage = document.createElement('div'); 446 onThisPage.classList.add('on-this-page'); 447 onThisPage.append(stack[0].ol); 448 const activeItemSpan = activeSection.parentElement; 449 activeItemSpan.after(onThisPage); 450 }); 451 452 document.addEventListener('DOMContentLoaded', reloadCurrentHeader); 453 document.addEventListener('scroll', reloadCurrentHeader, { passive: true }); 454})(); 455
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.