1const sectionChanged = new CustomEvent("quarto-sectionChanged", { 2 detail: {}, 3 bubbles: true, 4 cancelable: false, 5 composed: false, 6}); 7 8const layoutMarginEls = () => { 9 // Find any conflicting margin elements and add margins to the 10 // top to prevent overlap 11 const marginChildren = window.document.querySelectorAll( 12 ".column-margin.column-container > *, .margin-caption, .aside" 13 ); 14 15 let lastBottom = 0; 16 for (const marginChild of marginChildren) { 17 if (marginChild.offsetParent !== null) { 18 // clear the top margin so we recompute it 19 marginChild.style.marginTop = null; 20 const top = marginChild.getBoundingClientRect().top + window.scrollY; 21 if (top < lastBottom) { 22 const marginChildStyle = window.getComputedStyle(marginChild); 23 const marginBottom = parseFloat(marginChildStyle["marginBottom"]); 24 const margin = lastBottom - top + marginBottom; 25 marginChild.style.marginTop = `${margin}px`; 26 } 27 const styles = window.getComputedStyle(marginChild); 28 const marginTop = parseFloat(styles["marginTop"]); 29 lastBottom = top + marginChild.getBoundingClientRect().height + marginTop; 30 } 31 } 32}; 33 34window.document.addEventListener("DOMContentLoaded", function (_event) { 35 // Recompute the position of margin elements anytime the body size changes 36 if (window.ResizeObserver) { 37 const resizeObserver = new window.ResizeObserver( 38 throttle(() => { 39 layoutMarginEls(); 40 if ( 41 window.document.body.getBoundingClientRect().width < 990 && 42 isReaderMode() 43 ) { 44 quartoToggleReader(); 45 } 46 }, 50) 47 ); 48 resizeObserver.observe(window.document.body); 49 } 50 51 const tocEl = window.document.querySelector('nav.toc-active[role="doc-toc"]'); 52 const sidebarEl = window.document.getElementById("quarto-sidebar"); 53 const leftTocEl = window.document.getElementById("quarto-sidebar-toc-left"); 54 const marginSidebarEl = window.document.getElementById( 55 "quarto-margin-sidebar" 56 ); 57 // function to determine whether the element has a previous sibling that is active 58 const prevSiblingIsActiveLink = (el) => { 59 const sibling = el.previousElementSibling; 60 if (sibling && sibling.tagName === "A") { 61 return sibling.classList.contains("active"); 62 } else { 63 return false; 64 } 65 }; 66 67 // fire slideEnter for bootstrap tab activations (for htmlwidget resize behavior) 68 function fireSlideEnter(e) { 69 const event = window.document.createEvent("Event"); 70 event.initEvent("slideenter", true, true); 71 window.document.dispatchEvent(event); 72 } 73 const tabs = window.document.querySelectorAll('a[data-bs-toggle="tab"]');
74 tabs.forEach((tab) => { 75 tab.addEventListener("shown.bs.tab", fireSlideEnter); 76 }); 77 78 // fire slideEnter for tabby tab activations (for htmlwidget resize behavior) 79 document.addEventListener("tabby", fireSlideEnter, false); 80 81 // Track scrolling and mark TOC links as active 82 // get table of contents and sidebar (bail if we don't have at least one) 83 const tocLinks = tocEl 84 ? [...tocEl.querySelectorAll("a[data-scroll-target]")] 85 : []; 86 const makeActive = (link) => tocLinks[link].classList.add("active"); 87 const removeActive = (link) => tocLinks[link].classList.remove("active"); 88 const removeAllActive = () => 89 [...Array(tocLinks.length).keys()].forEach((link) => removeActive(link)); 90 91 // activate the anchor for a section associated with this TOC entry 92 tocLinks.forEach((link) => { 93 link.addEventListener("click", () => { 94 if (link.href.indexOf("#") !== -1) { 95 const anchor = link.href.split("#")[1]; 96 const heading = window.document.querySelector( 97 `[data-anchor-id="${anchor}"]` 98 ); 99 if (heading) { 100 // Add the class 101 heading.classList.add("reveal-anchorjs-link"); 102 103 // function to show the anchor 104 const handleMouseout = () => { 105 heading.classList.remove("reveal-anchorjs-link"); 106 heading.removeEventListener("mouseout", handleMouseout); 107 }; 108 109 // add a function to clear the anchor when the user mouses out of it 110 heading.addEventListener("mouseout", handleMouseout); 111 } 112 } 113 }); 114 }); 115 116 const sections = tocLinks.map((link) => { 117 const target = link.getAttribute("data-scroll-target"); 118 if (target.startsWith("#")) { 119 return window.document.getElementById(decodeURI(`${target.slice(1)}`)); 120 } else { 121 return window.document.querySelector(decodeURI(`${target}`)); 122 } 123 }); 124 125 const sectionMargin = 200; 126 let currentActive = 0; 127 // track whether we've initialized state the first time 128 let init = false; 129 130 const updateActiveLink = () => { 131 // The index from bottom to top (e.g. reversed list) 132 let sectionIndex = -1; 133 if ( 134 window.innerHeight + window.pageYOffset >= 135 window.document.body.offsetHeight 136 ) { 137 // This is the no-scroll case where last section should be the active one 138 sectionIndex = 0; 139 } else { 140 // This finds the last section visible on screen that should be made active 141 sectionIndex = [...sections].reverse().findIndex((section) => { 142 if (section) { 143 return window.pageYOffset >= section.offsetTop - sectionMargin; 144 } else { 145 return false; 146 } 147 }); 148 } 149 if (sectionIndex > -1) { 150 const current = sections.length - sectionIndex - 1; 151 if (current !== currentActive) { 152 removeAllActive(); 153 currentActive = current; 154 makeActive(current); 155 if (init) { 156 window.dispatchEvent(sectionChanged); 157 } 158 init = true; 159 } 160 } 161 }; 162 163 const inHiddenRegion = (top, bottom, hiddenRegions) => { 164 for (const region of hiddenRegions) { 165 if (top <= region.bottom && bottom >= region.top) { 166 return true; 167 } 168 } 169 return false; 170 }; 171 172 const categorySelector = "header.quarto-title-block .quarto-category"; 173 const activateCategories = (href) => { 174 // Find any categories 175 // Surround them with a link pointing back to: 176 // #category=Authoring 177 try { 178 const categoryEls = window.document.querySelectorAll(categorySelector); 179 for (const categoryEl of categoryEls) { 180 const categoryText = categoryEl.textContent; 181 if (categoryText) { 182 const link = `${href}#category=${encodeURIComponent(categoryText)}`; 183 const linkEl = window.document.createElement("a"); 184 linkEl.setAttribute("href", link); 185 for (const child of categoryEl.childNodes) { 186 linkEl.append(child); 187 } 188 categoryEl.appendChild(linkEl); 189 } 190 } 191 } catch { 192 // Ignore errors 193 } 194 }; 195 function hasTitleCategories() { 196 return window.document.querySelector(categorySelector) !== null; 197 } 198 199 function offsetRelativeUrl(url) { 200 const offset = getMeta("quarto:offset"); 201 return offset ? offset + url : url; 202 } 203 204 function offsetAbsoluteUrl(url) { 205 const offset = getMeta("quarto:offset"); 206 const baseUrl = new URL(offset, window.location); 207 208 const projRelativeUrl = url.replace(baseUrl, ""); 209 if (projRelativeUrl.startsWith("/")) { 210 return projRelativeUrl; 211 } else { 212 return "/" + projRelativeUrl; 213 } 214 } 215 216 // read a meta tag value 217 function getMeta(metaName) { 218 const metas = window.document.getElementsByTagName("meta"); 219 for (let i = 0; i < metas.length; i++) { 220 if (metas[i].getAttribute("name") === metaName) { 221 return metas[i].getAttribute("content"); 222 } 223 } 224 return ""; 225 } 226 227 async function findAndActivateCategories() { 228 const currentPagePath = offsetAbsoluteUrl(window.location.href); 229 const response = await fetch(offsetRelativeUrl("listings.json")); 230 if (response.status == 200) { 231 return response.json().then(function (listingPaths) { 232 const listingHrefs = []; 233 for (const listingPath of listingPaths) { 234 const pathWithoutLeadingSlash = listingPath.listing.substring(1); 235 for (const item of listingPath.items) { 236 if ( 237 item === currentPagePath || 238 item === currentPagePath + "index.html" 239 ) { 240 // Resolve this path against the offset to be sure 241 // we already are using the correct path to the listing 242 // (this adjusts the listing urls to be rooted against 243 // whatever root the page is actually running against) 244 const relative = offsetRelativeUrl(pathWithoutLeadingSlash); 245 const baseUrl = window.location; 246 const resolvedPath = new URL(relative, baseUrl); 247 listingHrefs.push(resolvedPath.pathname); 248 break; 249 } 250 } 251 } 252 253 // Look up the tree for a nearby linting and use that if we find one 254 const nearestListing = findNearestParentListing( 255 offsetAbsoluteUrl(window.location.pathname), 256 listingHrefs 257 ); 258 if (nearestListing) { 259 activateCategories(nearestListing); 260 } else { 261 // See if the referrer is a listing page for this item 262 const referredRelativePath = offsetAbsoluteUrl(document.referrer); 263 const referrerListing = listingHrefs.find((listingHref) => { 264 const isListingReferrer = 265 listingHref === referredRelativePath || 266 listingHref === referredRelativePath + "index.html"; 267 return isListingReferrer; 268 }); 269 270 if (referrerListing) { 271 // Try to use the referrer if possible 272 activateCategories(referrerListing); 273 } else if (listingHrefs.length > 0) { 274 // Otherwise, just fall back to the first listing 275 activateCategories(listingHrefs[0]); 276 } 277 } 278 }); 279 } 280 } 281 if (hasTitleCategories()) { 282 findAndActivateCategories(); 283 } 284 285 const findNearestParentListing = (href, listingHrefs) => { 286 if (!href || !listingHrefs) { 287 return undefined; 288 } 289 // Look up the tree for a nearby linting and use that if we find one 290 const relativeParts = href.substring(1).split("/"); 291 while (relativeParts.length > 0) { 292 const path = relativeParts.join("/"); 293 for (const listingHref of listingHrefs) { 294 if (listingHref.startsWith(path)) { 295 return listingHref; 296 } 297 } 298 relativeParts.pop(); 299 } 300 301 return undefined; 302 }; 303 304 const manageSidebarVisiblity = (el, placeholderDescriptor) => { 305 let isVisible = true; 306 let elRect; 307 308 return (hiddenRegions) => { 309 if (el === null) { 310 return; 311 } 312 313 // Find the last element of the TOC 314 const lastChildEl = el.lastElementChild; 315 316 if (lastChildEl) { 317 // Converts the sidebar to a menu 318 const convertToMenu = () => { 319 for (const child of el.children) { 320 child.style.opacity = 0; 321 child.style.overflow = "hidden"; 322 child.style.pointerEvents = "none"; 323 } 324 325 nexttick(() => { 326 const toggleContainer = window.document.createElement("div"); 327 toggleContainer.style.width = "100%"; 328 toggleContainer.classList.add("zindex-over-content"); 329 toggleContainer.classList.add("quarto-sidebar-toggle"); 330 toggleContainer.classList.add("headroom-target"); // Marks this to be managed by headeroom 331 toggleContainer.id = placeholderDescriptor.id; 332 toggleContainer.style.position = "fixed"; 333 334 const toggleIcon = window.document.createElement("i"); 335 toggleIcon.classList.add("quarto-sidebar-toggle-icon"); 336 toggleIcon.classList.add("bi"); 337 toggleIcon.classList.add("bi-caret-down-fill"); 338 339 const toggleTitle = window.document.createElement("div"); 340 const titleEl = window.document.body.querySelector( 341 placeholderDescriptor.titleSelector 342 ); 343 if (titleEl) { 344 toggleTitle.append( 345 titleEl.textContent || titleEl.innerText, 346 toggleIcon 347 ); 348 } 349 toggleTitle.classList.add("zindex-over-content"); 350 toggleTitle.classList.add("quarto-sidebar-toggle-title"); 351 toggleContainer.append(toggleTitle); 352 353 const toggleContents = window.document.createElement("div"); 354 toggleContents.classList = el.classList; 355 toggleContents.classList.add("zindex-over-content"); 356 toggleContents.classList.add("quarto-sidebar-toggle-contents"); 357 for (const child of el.children) { 358 if (child.id === "toc-title") { 359 continue; 360 } 361 362 const clone = child.cloneNode(true); 363 clone.style.opacity = 1; 364 clone.style.pointerEvents = null; 365 clone.style.display = null; 366 toggleContents.append(clone); 367 } 368 toggleContents.style.height = "0px"; 369 const positionToggle = () => { 370 // position the element (top left of parent, same width as parent) 371 if (!elRect) { 372 elRect = el.getBoundingClientRect(); 373 } 374 toggleContainer.style.left = `${elRect.left}px`; 375 toggleContainer.style.top = `${elRect.top}px`; 376 toggleContainer.style.width = `${elRect.width}px`; 377 };
378 positionToggle(); 379 380 toggleContainer.append(toggleContents); 381 el.parentElement.prepend(toggleContainer); 382 383 // Process clicks 384 let tocShowing = false; 385 // Allow the caller to control whether this is dismissed 386 // when it is clicked (e.g. sidebar navigation supports 387 // opening and closing the nav tree, so don't dismiss on click) 388 const clickEl = placeholderDescriptor.dismissOnClick 389 ? toggleContainer 390 : toggleTitle; 391 392 const closeToggle = () => { 393 if (tocShowing) { 394 toggleContainer.classList.remove("expanded"); 395 toggleContents.style.height = "0px"; 396 tocShowing = false; 397 } 398 }; 399 400 // Get rid of any expanded toggle if the user scrolls 401 window.document.addEventListener( 402 "scroll", 403 throttle(() => { 404 closeToggle(); 405 }, 50) 406 ); 407 408 // Handle positioning of the toggle 409 window.addEventListener( 410 "resize", 411 throttle(() => { 412 elRect = undefined; 413 positionToggle(); 414 }, 50) 415 ); 416 417 window.addEventListener("quarto-hrChanged", () => { 418 elRect = undefined; 419 }); 420 421 // Process the click 422 clickEl.onclick = () => { 423 if (!tocShowing) { 424 toggleContainer.classList.add("expanded"); 425 toggleContents.style.height = null; 426 tocShowing = true; 427 } else { 428 closeToggle(); 429 } 430 }; 431 }); 432 }; 433 434 // Converts a sidebar from a menu back to a sidebar 435 const convertToSidebar = () => { 436 for (const child of el.children) { 437 child.style.opacity = 1; 438 child.style.overflow = null; 439 child.style.pointerEvents = null; 440 } 441 442 const placeholderEl = window.document.getElementById( 443 placeholderDescriptor.id 444 ); 445 if (placeholderEl) { 446 placeholderEl.remove(); 447 } 448 449 el.classList.remove("rollup"); 450 }; 451 452 if (isReaderMode()) { 453 convertToMenu(); 454 isVisible = false; 455 } else { 456 // Find the top and bottom o the element that is being managed 457 const elTop = el.offsetTop; 458 const elBottom = 459 elTop + lastChildEl.offsetTop + lastChildEl.offsetHeight; 460 461 if (!isVisible) { 462 // If the element is current not visible reveal if there are 463 // no conflicts with overlay regions 464 if (!inHiddenRegion(elTop, elBottom, hiddenRegions)) { 465 convertToSidebar(); 466 isVisible = true; 467 } 468 } else { 469 // If the element is visible, hide it if it conflicts with overlay regions 470 // and insert a placeholder toggle (or if we're in reader mode) 471 if (inHiddenRegion(elTop, elBottom, hiddenRegions)) { 472 convertToMenu(); 473 isVisible = false; 474 } 475 } 476 } 477 } 478 }; 479 }; 480 481 const tabEls = document.querySelectorAll('a[data-bs-toggle="tab"]'); 482 for (const tabEl of tabEls) { 483 const id = tabEl.getAttribute("data-bs-target"); 484 if (id) { 485 const columnEl = document.querySelector( 486 `${id} .column-margin, .tabset-margin-content` 487 ); 488 if (columnEl) 489 tabEl.addEventListener("shown.bs.tab", function (event) { 490 const el = event.srcElement; 491 if (el) { 492 const visibleCls = `${el.id}-margin-content`; 493 // walk up until we find a parent tabset 494 let panelTabsetEl = el.parentElement; 495 while (panelTabsetEl) { 496 if (panelTabsetEl.classList.contains("panel-tabset")) { 497 break; 498 } 499 panelTabsetEl = panelTabsetEl.parentElement; 500 } 501 502 if (panelTabsetEl) { 503 const prevSib = panelTabsetEl.previousElementSibling; 504 if ( 505 prevSib && 506 prevSib.classList.contains("tabset-margin-container") 507 ) { 508 const childNodes = prevSib.querySelectorAll( 509 ".tabset-margin-content" 510 ); 511 for (const childEl of childNodes) { 512 if (childEl.classList.contains(visibleCls)) { 513 childEl.classList.remove("collapse"); 514 } else { 515 childEl.classList.add("collapse"); 516 } 517 } 518 } 519 } 520 } 521 522 layoutMarginEls(); 523 }); 524 } 525 } 526 527 // Manage the visibility of the toc and the sidebar 528 const marginScrollVisibility = manageSidebarVisiblity(marginSidebarEl, { 529 id: "quarto-toc-toggle", 530 titleSelector: "#toc-title", 531 dismissOnClick: true, 532 }); 533 const sidebarScrollVisiblity = manageSidebarVisiblity(sidebarEl, { 534 id: "quarto-sidebarnav-toggle", 535 titleSelector: ".title", 536 dismissOnClick: false, 537 }); 538 let tocLeftScrollVisibility; 539 if (leftTocEl) { 540 tocLeftScrollVisibility = manageSidebarVisiblity(leftTocEl, { 541 id: "quarto-lefttoc-toggle", 542 titleSelector: "#toc-title",
543 dismissOnClick: true, 544 }); 545 } 546 547 // Find the first element that uses formatting in special columns 548 const conflictingEls = window.document.body.querySelectorAll( 549 '[class^="column-"], [class*=" column-"], aside, [class*="margin-caption"], [class*=" margin-caption"], [class*="margin-ref"], [class*=" margin-ref"]' 550 ); 551 552 // Filter all the possibly conflicting elements into ones 553 // the do conflict on the left or ride side 554 const arrConflictingEls = Array.from(conflictingEls); 555 const leftSideConflictEls = arrConflictingEls.filter((el) => { 556 if (el.tagName === "ASIDE") { 557 return false; 558 } 559 return Array.from(el.classList).find((className) => { 560 return ( 561 className !== "column-body" && 562 className.startsWith("column-") && 563 !className.endsWith("right") && 564 !className.endsWith("container") && 565 className !== "column-margin" 566 ); 567 }); 568 }); 569 const rightSideConflictEls = arrConflictingEls.filter((el) => { 570 if (el.tagName === "ASIDE") { 571 return true; 572 } 573 574 const hasMarginCaption = Array.from(el.classList).find((className) => { 575 return className == "margin-caption"; 576 }); 577 if (hasMarginCaption) { 578 return true; 579 } 580 581 return Array.from(el.classList).find((className) => { 582 return ( 583 className !== "column-body" && 584 !className.endsWith("container") && 585 className.startsWith("column-") && 586 !className.endsWith("left") 587 ); 588 }); 589 }); 590 591 const kOverlapPaddingSize = 10; 592 function toRegions(els) { 593 return els.map((el) => { 594 const boundRect = el.getBoundingClientRect(); 595 const top = 596 boundRect.top + 597 document.documentElement.scrollTop - 598 kOverlapPaddingSize; 599 return { 600 top, 601 bottom: top + el.scrollHeight + 2 * kOverlapPaddingSize, 602 }; 603 }); 604 } 605 606 let hasObserved = false; 607 const visibleItemObserver = (els) => { 608 let visibleElements = [...els]; 609 const intersectionObserver = new IntersectionObserver( 610 (entries, _observer) => { 611 entries.forEach((entry) => { 612 if (entry.isIntersecting) { 613 if (visibleElements.indexOf(entry.target) === -1) { 614 visibleElements.push(entry.target); 615 } 616 } else { 617 visibleElements = visibleElements.filter((visibleEntry) => { 618 return visibleEntry !== entry; 619 }); 620 } 621 }); 622 623 if (!hasObserved) { 624 hideOverlappedSidebars(); 625 } 626 hasObserved = true; 627 }, 628 {} 629 ); 630 els.forEach((el) => { 631 intersectionObserver.observe(el); 632 }); 633 634 return { 635 getVisibleEntries: () => { 636 return visibleElements; 637 }, 638 }; 639 }; 640 641 const rightElementObserver = visibleItemObserver(rightSideConflictEls); 642 const leftElementObserver = visibleItemObserver(leftSideConflictEls); 643 644 const hideOverlappedSidebars = () => { 645 marginScrollVisibility(toRegions(rightElementObserver.getVisibleEntries())); 646 sidebarScrollVisiblity(toRegions(leftElementObserver.getVisibleEntries())); 647 if (tocLeftScrollVisibility) { 648 tocLeftScrollVisibility( 649 toRegions(leftElementObserver.getVisibleEntries()) 650 ); 651 } 652 }; 653 654 window.quartoToggleReader = () => { 655 // Applies a slow class (or removes it) 656 // to update the transition speed 657 const slowTransition = (slow) => { 658 const manageTransition = (id, slow) => { 659 const el = document.getElementById(id); 660 if (el) { 661 if (slow) { 662 el.classList.add("slow"); 663 } else { 664 el.classList.remove("slow"); 665 } 666 } 667 }; 668 669 manageTransition("TOC", slow); 670 manageTransition("quarto-sidebar", slow); 671 }; 672 const readerMode = !isReaderMode(); 673 setReaderModeValue(readerMode); 674 675 // If we're entering reader mode, slow the transition 676 if (readerMode) { 677 slowTransition(readerMode); 678 } 679 highlightReaderToggle(readerMode); 680 hideOverlappedSidebars(); 681 682 // If we're exiting reader mode, restore the non-slow transition 683 if (!readerMode) { 684 slowTransition(!readerMode); 685 } 686 }; 687 688 const highlightReaderToggle = (readerMode) => { 689 const els = document.querySelectorAll(".quarto-reader-toggle"); 690 if (els) {
691 els.forEach((el) => { 692 if (readerMode) { 693 el.classList.add("reader"); 694 } else { 695 el.classList.remove("reader"); 696 } 697 }); 698 } 699 }; 700 701 const setReaderModeValue = (val) => { 702 if (window.location.protocol !== "file:") { 703 window.localStorage.setItem("quarto-reader-mode", val); 704 } else { 705 localReaderMode = val; 706 } 707 }; 708 709 const isReaderMode = () => { 710 if (window.location.protocol !== "file:") { 711 return window.localStorage.getItem("quarto-reader-mode") === "true"; 712 } else { 713 return localReaderMode; 714 } 715 }; 716 let localReaderMode = null; 717 718 const tocOpenDepthStr = tocEl?.getAttribute("data-toc-expanded"); 719 const tocOpenDepth = tocOpenDepthStr ? Number(tocOpenDepthStr) : 1; 720 721 // Walk the TOC and collapse/expand nodes 722 // Nodes are expanded if: 723 // - they are top level 724 // - they have children that are 'active' links 725 // - they are directly below an link that is 'active' 726 const walk = (el, depth) => { 727 // Tick depth when we enter a UL 728 if (el.tagName === "UL") { 729 depth = depth + 1; 730 } 731 732 // It this is active link 733 let isActiveNode = false; 734 if (el.tagName === "A" && el.classList.contains("active")) { 735 isActiveNode = true; 736 } 737 738 // See if there is an active child to this element 739 let hasActiveChild = false; 740 for (child of el.children) { 741 hasActiveChild = walk(child, depth) || hasActiveChild; 742 } 743 744 // Process the collapse state if this is an UL 745 if (el.tagName === "UL") { 746 if (tocOpenDepth === -1 && depth > 1) { 747 // toc-expand: false 748 el.classList.add("collapse"); 749 } else if ( 750 depth <= tocOpenDepth || 751 hasActiveChild || 752 prevSiblingIsActiveLink(el) 753 ) { 754 el.classList.remove("collapse"); 755 } else { 756 el.classList.add("collapse"); 757 } 758 759 // untick depth when we leave a UL 760 depth = depth - 1; 761 } 762 return hasActiveChild || isActiveNode; 763 }; 764 765 // walk the TOC and expand / collapse any items that should be shown 766 if (tocEl) { 767 updateActiveLink(); 768 walk(tocEl, 0); 769 } 770 771 // Throttle the scroll event and walk peridiocally 772 window.document.addEventListener( 773 "scroll", 774 throttle(() => { 775 if (tocEl) { 776 updateActiveLink(); 777 walk(tocEl, 0); 778 } 779 if (!isReaderMode()) { 780 hideOverlappedSidebars(); 781 } 782 }, 5) 783 ); 784 window.addEventListener( 785 "resize", 786 throttle(() => { 787 if (tocEl) { 788 updateActiveLink(); 789 walk(tocEl, 0); 790 } 791 if (!isReaderMode()) { 792 hideOverlappedSidebars(); 793 } 794 }, 10) 795 ); 796 hideOverlappedSidebars(); 797 highlightReaderToggle(isReaderMode()); 798}); 799 800// grouped tabsets 801window.addEventListener("pageshow", (_event) => { 802 function getTabSettings() { 803 const data = localStorage.getItem("quarto-persistent-tabsets-data"); 804 if (!data) { 805 localStorage.setItem("quarto-persistent-tabsets-data", "{}"); 806 return {}; 807 } 808 if (data) { 809 return JSON.parse(data); 810 } 811 } 812 813 function setTabSettings(data) { 814 localStorage.setItem( 815 "quarto-persistent-tabsets-data", 816 JSON.stringify(data) 817 ); 818 } 819 820 function setTabState(groupName, groupValue) { 821 const data = getTabSettings(); 822 data[groupName] = groupValue; 823 setTabSettings(data); 824 } 825 826 function toggleTab(tab, active) { 827 const tabPanelId = tab.getAttribute("aria-controls"); 828 const tabPanel = document.getElementById(tabPanelId); 829 if (active) { 830 tab.classList.add("active"); 831 tabPanel.classList.add("active"); 832 } else { 833 tab.classList.remove("active"); 834 tabPanel.classList.remove("active"); 835 } 836 } 837 838 function toggleAll(selectedGroup, selectorsToSync) { 839 for (const [thisGroup, tabs] of Object.entries(selectorsToSync)) { 840 const active = selectedGroup === thisGroup; 841 for (const tab of tabs) { 842 toggleTab(tab, active); 843 } 844 } 845 } 846 847 function findSelectorsToSyncByLanguage() { 848 const result = {}; 849 const tabs = Array.from( 850 document.querySelectorAll(`div[data-group] a[id^='tabset-']`) 851 ); 852 for (const item of tabs) { 853 const div = item.parentElement.parentElement.parentElement; 854 const group = div.getAttribute("data-group"); 855 if (!result[group]) { 856 result[group] = {}; 857 } 858 const selectorsToSync = result[group]; 859 const value = item.innerHTML; 860 if (!selectorsToSync[value]) { 861 selectorsToSync[value] = []; 862 } 863 selectorsToSync[value].push(item); 864 } 865 return result; 866 } 867 868 function setupSelectorSync() { 869 const selectorsToSync = findSelectorsToSyncByLanguage();
870 Object.entries(selectorsToSync).forEach(([group, tabSetsByValue]) => { 871 Object.entries(tabSetsByValue).forEach(([value, items]) => { 872 items.forEach((item) => { 873 item.addEventListener("click", (_event) => { 874 setTabState(group, value); 875 toggleAll(value, selectorsToSync[group]); 876 }); 877 }); 878 }); 879 }); 880 return selectorsToSync; 881 } 882 883 const selectorsToSync = setupSelectorSync(); 884 for (const [group, selectedName] of Object.entries(getTabSettings())) { 885 const selectors = selectorsToSync[group]; 886 // it's possible that stale state gives us empty selections, so we explicitly check here. 887 if (selectors) { 888 toggleAll(selectedName, selectors); 889 } 890 } 891}); 892 893function throttle(func, wait) { 894 let waiting = false; 895 return function () { 896 if (!waiting) { 897 func.apply(this, arguments); 898 waiting = true; 899 setTimeout(function () { 900 waiting = false; 901 }, wait); 902 } 903 }; 904} 905 906function nexttick(func) { 907 return setTimeout(func, 0); 908}
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.