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 // Categories search with listing only use path without query 229 const currentPagePath = offsetAbsoluteUrl( 230 window.location.origin + window.location.pathname 231 ); 232 const response = await fetch(offsetRelativeUrl("listings.json")); 233 if (response.status == 200) { 234 return response.json().then(function (listingPaths) { 235 const listingHrefs = []; 236 for (const listingPath of listingPaths) { 237 const pathWithoutLeadingSlash = listingPath.listing.substring(1); 238 for (const item of listingPath.items) { 239 if ( 240 item === currentPagePath || 241 item === currentPagePath + "index.html" 242 ) { 243 // Resolve this path against the offset to be sure 244 // we already are using the correct path to the listing 245 // (this adjusts the listing urls to be rooted against 246 // whatever root the page is actually running against) 247 const relative = offsetRelativeUrl(pathWithoutLeadingSlash); 248 const baseUrl = window.location; 249 const resolvedPath = new URL(relative, baseUrl); 250 listingHrefs.push(resolvedPath.pathname); 251 break; 252 } 253 } 254 } 255 256 // Look up the tree for a nearby linting and use that if we find one 257 const nearestListing = findNearestParentListing( 258 offsetAbsoluteUrl(window.location.pathname), 259 listingHrefs 260 ); 261 if (nearestListing) { 262 activateCategories(nearestListing); 263 } else { 264 // See if the referrer is a listing page for this item 265 const referredRelativePath = offsetAbsoluteUrl(document.referrer); 266 const referrerListing = listingHrefs.find((listingHref) => { 267 const isListingReferrer = 268 listingHref === referredRelativePath || 269 listingHref === referredRelativePath + "index.html"; 270 return isListingReferrer; 271 }); 272 273 if (referrerListing) { 274 // Try to use the referrer if possible 275 activateCategories(referrerListing); 276 } else if (listingHrefs.length > 0) { 277 // Otherwise, just fall back to the first listing 278 activateCategories(listingHrefs[0]); 279 } 280 } 281 }); 282 } 283 } 284 if (hasTitleCategories()) { 285 findAndActivateCategories(); 286 } 287 288 const findNearestParentListing = (href, listingHrefs) => { 289 if (!href || !listingHrefs) { 290 return undefined; 291 } 292 // Look up the tree for a nearby linting and use that if we find one 293 const relativeParts = href.substring(1).split("/"); 294 while (relativeParts.length > 0) { 295 const path = relativeParts.join("/"); 296 for (const listingHref of listingHrefs) { 297 if (listingHref.startsWith(path)) { 298 return listingHref; 299 } 300 } 301 relativeParts.pop(); 302 } 303 304 return undefined; 305 }; 306 307 const manageSidebarVisiblity = (el, placeholderDescriptor) => { 308 let isVisible = true; 309 let elRect; 310 311 return (hiddenRegions) => { 312 if (el === null) { 313 return; 314 } 315 316 // Find the last element of the TOC 317 const lastChildEl = el.lastElementChild; 318 319 if (lastChildEl) { 320 // Converts the sidebar to a menu 321 const convertToMenu = () => { 322 for (const child of el.children) { 323 child.style.opacity = 0; 324 child.style.overflow = "hidden"; 325 child.style.pointerEvents = "none"; 326 } 327 328 nexttick(() => { 329 const toggleContainer = window.document.createElement("div"); 330 toggleContainer.style.width = "100%"; 331 toggleContainer.classList.add("zindex-over-content"); 332 toggleContainer.classList.add("quarto-sidebar-toggle"); 333 toggleContainer.classList.add("headroom-target"); // Marks this to be managed by headeroom 334 toggleContainer.id = placeholderDescriptor.id; 335 toggleContainer.style.position = "fixed"; 336 337 const toggleIcon = window.document.createElement("i"); 338 toggleIcon.classList.add("quarto-sidebar-toggle-icon"); 339 toggleIcon.classList.add("bi"); 340 toggleIcon.classList.add("bi-caret-down-fill"); 341 342 const toggleTitle = window.document.createElement("div"); 343 const titleEl = window.document.body.querySelector( 344 placeholderDescriptor.titleSelector 345 ); 346 if (titleEl) { 347 toggleTitle.append( 348 titleEl.textContent || titleEl.innerText, 349 toggleIcon 350 ); 351 } 352 toggleTitle.classList.add("zindex-over-content"); 353 toggleTitle.classList.add("quarto-sidebar-toggle-title"); 354 toggleContainer.append(toggleTitle); 355 356 const toggleContents = window.document.createElement("div"); 357 toggleContents.classList = el.classList; 358 toggleContents.classList.add("zindex-over-content"); 359 toggleContents.classList.add("quarto-sidebar-toggle-contents"); 360 for (const child of el.children) { 361 if (child.id === "toc-title") { 362 continue; 363 } 364 365 const clone = child.cloneNode(true); 366 clone.style.opacity = 1; 367 clone.style.pointerEvents = null; 368 clone.style.display = null; 369 toggleContents.append(clone); 370 } 371 toggleContents.style.height = "0px"; 372 const positionToggle = () => { 373 // position the element (top left of parent, same width as parent) 374 if (!elRect) { 375 elRect = el.getBoundingClientRect(); 376 } 377 toggleContainer.style.left = `${elRect.left}px`; 378 toggleContainer.style.top = `${elRect.top}px`; 379 toggleContainer.style.width = `${elRect.width}px`; 380 };
381 positionToggle(); 382 383 toggleContainer.append(toggleContents); 384 el.parentElement.prepend(toggleContainer); 385 386 // Process clicks 387 let tocShowing = false; 388 // Allow the caller to control whether this is dismissed 389 // when it is clicked (e.g. sidebar navigation supports 390 // opening and closing the nav tree, so don't dismiss on click) 391 const clickEl = placeholderDescriptor.dismissOnClick 392 ? toggleContainer 393 : toggleTitle; 394 395 const closeToggle = () => { 396 if (tocShowing) { 397 toggleContainer.classList.remove("expanded"); 398 toggleContents.style.height = "0px"; 399 tocShowing = false; 400 } 401 }; 402 403 // Get rid of any expanded toggle if the user scrolls 404 window.document.addEventListener( 405 "scroll", 406 throttle(() => { 407 closeToggle(); 408 }, 50) 409 ); 410 411 // Handle positioning of the toggle 412 window.addEventListener( 413 "resize", 414 throttle(() => { 415 elRect = undefined; 416 positionToggle(); 417 }, 50) 418 ); 419 420 window.addEventListener("quarto-hrChanged", () => { 421 elRect = undefined; 422 }); 423 424 // Process the click 425 clickEl.onclick = () => { 426 if (!tocShowing) { 427 toggleContainer.classList.add("expanded"); 428 toggleContents.style.height = null; 429 tocShowing = true; 430 } else { 431 closeToggle(); 432 } 433 }; 434 }); 435 }; 436 437 // Converts a sidebar from a menu back to a sidebar 438 const convertToSidebar = () => { 439 for (const child of el.children) { 440 child.style.opacity = 1; 441 child.style.overflow = null; 442 child.style.pointerEvents = null; 443 } 444 445 const placeholderEl = window.document.getElementById( 446 placeholderDescriptor.id 447 ); 448 if (placeholderEl) { 449 placeholderEl.remove(); 450 } 451 452 el.classList.remove("rollup"); 453 }; 454 455 if (isReaderMode()) { 456 convertToMenu(); 457 isVisible = false; 458 } else { 459 // Find the top and bottom o the element that is being managed 460 const elTop = el.offsetTop; 461 const elBottom = 462 elTop + lastChildEl.offsetTop + lastChildEl.offsetHeight; 463 464 if (!isVisible) { 465 // If the element is current not visible reveal if there are 466 // no conflicts with overlay regions 467 if (!inHiddenRegion(elTop, elBottom, hiddenRegions)) { 468 convertToSidebar(); 469 isVisible = true; 470 } 471 } else { 472 // If the element is visible, hide it if it conflicts with overlay regions 473 // and insert a placeholder toggle (or if we're in reader mode) 474 if (inHiddenRegion(elTop, elBottom, hiddenRegions)) { 475 convertToMenu(); 476 isVisible = false; 477 } 478 } 479 } 480 } 481 }; 482 }; 483 484 const tabEls = document.querySelectorAll('a[data-bs-toggle="tab"]'); 485 for (const tabEl of tabEls) { 486 const id = tabEl.getAttribute("data-bs-target"); 487 if (id) { 488 const columnEl = document.querySelector( 489 `${id} .column-margin, .tabset-margin-content` 490 ); 491 if (columnEl) 492 tabEl.addEventListener("shown.bs.tab", function (event) { 493 const el = event.srcElement; 494 if (el) { 495 const visibleCls = `${el.id}-margin-content`; 496 // walk up until we find a parent tabset 497 let panelTabsetEl = el.parentElement; 498 while (panelTabsetEl) { 499 if (panelTabsetEl.classList.contains("panel-tabset")) { 500 break; 501 } 502 panelTabsetEl = panelTabsetEl.parentElement; 503 } 504 505 if (panelTabsetEl) { 506 const prevSib = panelTabsetEl.previousElementSibling; 507 if ( 508 prevSib && 509 prevSib.classList.contains("tabset-margin-container") 510 ) { 511 const childNodes = prevSib.querySelectorAll( 512 ".tabset-margin-content" 513 ); 514 for (const childEl of childNodes) { 515 if (childEl.classList.contains(visibleCls)) { 516 childEl.classList.remove("collapse"); 517 } else { 518 childEl.classList.add("collapse"); 519 } 520 } 521 } 522 } 523 } 524 525 layoutMarginEls(); 526 }); 527 } 528 } 529 530 // Manage the visibility of the toc and the sidebar 531 const marginScrollVisibility = manageSidebarVisiblity(marginSidebarEl, { 532 id: "quarto-toc-toggle", 533 titleSelector: "#toc-title", 534 dismissOnClick: true, 535 }); 536 const sidebarScrollVisiblity = manageSidebarVisiblity(sidebarEl, { 537 id: "quarto-sidebarnav-toggle", 538 titleSelector: ".title", 539 dismissOnClick: false, 540 }); 541 let tocLeftScrollVisibility; 542 if (leftTocEl) { 543 tocLeftScrollVisibility = manageSidebarVisiblity(leftTocEl, { 544 id: "quarto-lefttoc-toggle", 545 titleSelector: "#toc-title",
546 dismissOnClick: true, 547 }); 548 } 549 550 // Find the first element that uses formatting in special columns 551 const conflictingEls = window.document.body.querySelectorAll( 552 '[class^="column-"], [class*=" column-"], aside, [class*="margin-caption"], [class*=" margin-caption"], [class*="margin-ref"], [class*=" margin-ref"]' 553 ); 554 555 // Filter all the possibly conflicting elements into ones 556 // the do conflict on the left or ride side 557 const arrConflictingEls = Array.from(conflictingEls); 558 const leftSideConflictEls = arrConflictingEls.filter((el) => { 559 if (el.tagName === "ASIDE") { 560 return false; 561 } 562 return Array.from(el.classList).find((className) => { 563 return ( 564 className !== "column-body" && 565 className.startsWith("column-") && 566 !className.endsWith("right") && 567 !className.endsWith("container") && 568 className !== "column-margin" 569 ); 570 }); 571 }); 572 const rightSideConflictEls = arrConflictingEls.filter((el) => { 573 if (el.tagName === "ASIDE") { 574 return true; 575 } 576 577 const hasMarginCaption = Array.from(el.classList).find((className) => { 578 return className == "margin-caption"; 579 }); 580 if (hasMarginCaption) { 581 return true; 582 } 583 584 return Array.from(el.classList).find((className) => { 585 return ( 586 className !== "column-body" && 587 !className.endsWith("container") && 588 className.startsWith("column-") && 589 !className.endsWith("left") 590 ); 591 }); 592 }); 593 594 const kOverlapPaddingSize = 10; 595 function toRegions(els) { 596 return els.map((el) => { 597 const boundRect = el.getBoundingClientRect(); 598 const top = 599 boundRect.top + 600 document.documentElement.scrollTop - 601 kOverlapPaddingSize; 602 return { 603 top, 604 bottom: top + el.scrollHeight + 2 * kOverlapPaddingSize, 605 }; 606 }); 607 } 608 609 let hasObserved = false; 610 const visibleItemObserver = (els) => { 611 let visibleElements = [...els]; 612 const intersectionObserver = new IntersectionObserver( 613 (entries, _observer) => { 614 entries.forEach((entry) => { 615 if (entry.isIntersecting) { 616 if (visibleElements.indexOf(entry.target) === -1) { 617 visibleElements.push(entry.target); 618 } 619 } else { 620 visibleElements = visibleElements.filter((visibleEntry) => { 621 return visibleEntry !== entry; 622 }); 623 } 624 }); 625 626 if (!hasObserved) { 627 hideOverlappedSidebars(); 628 } 629 hasObserved = true; 630 }, 631 {} 632 ); 633 els.forEach((el) => { 634 intersectionObserver.observe(el); 635 }); 636 637 return { 638 getVisibleEntries: () => { 639 return visibleElements; 640 }, 641 }; 642 }; 643 644 const rightElementObserver = visibleItemObserver(rightSideConflictEls); 645 const leftElementObserver = visibleItemObserver(leftSideConflictEls); 646 647 const hideOverlappedSidebars = () => { 648 marginScrollVisibility(toRegions(rightElementObserver.getVisibleEntries())); 649 sidebarScrollVisiblity(toRegions(leftElementObserver.getVisibleEntries())); 650 if (tocLeftScrollVisibility) { 651 tocLeftScrollVisibility( 652 toRegions(leftElementObserver.getVisibleEntries()) 653 ); 654 } 655 }; 656 657 window.quartoToggleReader = () => { 658 // Applies a slow class (or removes it) 659 // to update the transition speed 660 const slowTransition = (slow) => { 661 const manageTransition = (id, slow) => { 662 const el = document.getElementById(id); 663 if (el) { 664 if (slow) { 665 el.classList.add("slow"); 666 } else { 667 el.classList.remove("slow"); 668 } 669 } 670 }; 671 672 manageTransition("TOC", slow); 673 manageTransition("quarto-sidebar", slow); 674 }; 675 const readerMode = !isReaderMode(); 676 setReaderModeValue(readerMode); 677 678 // If we're entering reader mode, slow the transition 679 if (readerMode) { 680 slowTransition(readerMode); 681 } 682 highlightReaderToggle(readerMode); 683 hideOverlappedSidebars(); 684 685 // If we're exiting reader mode, restore the non-slow transition 686 if (!readerMode) { 687 slowTransition(!readerMode); 688 } 689 }; 690 691 const highlightReaderToggle = (readerMode) => { 692 const els = document.querySelectorAll(".quarto-reader-toggle"); 693 if (els) {
694 els.forEach((el) => { 695 if (readerMode) { 696 el.classList.add("reader"); 697 } else { 698 el.classList.remove("reader"); 699 } 700 }); 701 } 702 }; 703 704 const setReaderModeValue = (val) => { 705 if (window.location.protocol !== "file:") { 706 window.localStorage.setItem("quarto-reader-mode", val); 707 } else { 708 localReaderMode = val; 709 } 710 }; 711 712 const isReaderMode = () => { 713 if (window.location.protocol !== "file:") { 714 return window.localStorage.getItem("quarto-reader-mode") === "true"; 715 } else { 716 return localReaderMode; 717 } 718 }; 719 let localReaderMode = null; 720 721 const tocOpenDepthStr = tocEl?.getAttribute("data-toc-expanded"); 722 const tocOpenDepth = tocOpenDepthStr ? Number(tocOpenDepthStr) : 1; 723 724 // Walk the TOC and collapse/expand nodes 725 // Nodes are expanded if: 726 // - they are top level 727 // - they have children that are 'active' links 728 // - they are directly below an link that is 'active' 729 const walk = (el, depth) => { 730 // Tick depth when we enter a UL 731 if (el.tagName === "UL") { 732 depth = depth + 1; 733 } 734 735 // It this is active link 736 let isActiveNode = false; 737 if (el.tagName === "A" && el.classList.contains("active")) { 738 isActiveNode = true; 739 } 740 741 // See if there is an active child to this element 742 let hasActiveChild = false; 743 for (child of el.children) { 744 hasActiveChild = walk(child, depth) || hasActiveChild; 745 } 746 747 // Process the collapse state if this is an UL 748 if (el.tagName === "UL") { 749 if (tocOpenDepth === -1 && depth > 1) { 750 // toc-expand: false 751 el.classList.add("collapse"); 752 } else if ( 753 depth <= tocOpenDepth || 754 hasActiveChild || 755 prevSiblingIsActiveLink(el) 756 ) { 757 el.classList.remove("collapse"); 758 } else { 759 el.classList.add("collapse"); 760 } 761 762 // untick depth when we leave a UL 763 depth = depth - 1; 764 } 765 return hasActiveChild || isActiveNode; 766 }; 767 768 // walk the TOC and expand / collapse any items that should be shown 769 if (tocEl) { 770 updateActiveLink(); 771 walk(tocEl, 0); 772 } 773 774 // Throttle the scroll event and walk peridiocally 775 window.document.addEventListener( 776 "scroll", 777 throttle(() => { 778 if (tocEl) { 779 updateActiveLink(); 780 walk(tocEl, 0); 781 } 782 if (!isReaderMode()) { 783 hideOverlappedSidebars(); 784 } 785 }, 5) 786 ); 787 window.addEventListener( 788 "resize", 789 throttle(() => { 790 if (tocEl) { 791 updateActiveLink(); 792 walk(tocEl, 0); 793 } 794 if (!isReaderMode()) { 795 hideOverlappedSidebars(); 796 } 797 }, 10) 798 ); 799 hideOverlappedSidebars(); 800 highlightReaderToggle(isReaderMode()); 801}); 802 803// grouped tabsets 804window.addEventListener("pageshow", (_event) => { 805 function getTabSettings() { 806 const data = localStorage.getItem("quarto-persistent-tabsets-data"); 807 if (!data) { 808 localStorage.setItem("quarto-persistent-tabsets-data", "{}"); 809 return {}; 810 } 811 if (data) { 812 return JSON.parse(data); 813 } 814 } 815 816 function setTabSettings(data) { 817 localStorage.setItem( 818 "quarto-persistent-tabsets-data", 819 JSON.stringify(data) 820 ); 821 } 822 823 function setTabState(groupName, groupValue) { 824 const data = getTabSettings(); 825 data[groupName] = groupValue; 826 setTabSettings(data); 827 } 828 829 function toggleTab(tab, active) { 830 const tabPanelId = tab.getAttribute("aria-controls"); 831 const tabPanel = document.getElementById(tabPanelId); 832 if (active) { 833 tab.classList.add("active"); 834 tabPanel.classList.add("active"); 835 } else { 836 tab.classList.remove("active"); 837 tabPanel.classList.remove("active"); 838 } 839 } 840 841 function toggleAll(selectedGroup, selectorsToSync) { 842 for (const [thisGroup, tabs] of Object.entries(selectorsToSync)) { 843 const active = selectedGroup === thisGroup; 844 for (const tab of tabs) { 845 toggleTab(tab, active); 846 } 847 } 848 } 849 850 function findSelectorsToSyncByLanguage() { 851 const result = {}; 852 const tabs = Array.from( 853 document.querySelectorAll(`div[data-group] a[id^='tabset-']`) 854 ); 855 for (const item of tabs) { 856 const div = item.parentElement.parentElement.parentElement; 857 const group = div.getAttribute("data-group"); 858 if (!result[group]) { 859 result[group] = {}; 860 } 861 const selectorsToSync = result[group]; 862 const value = item.innerHTML; 863 if (!selectorsToSync[value]) { 864 selectorsToSync[value] = []; 865 } 866 selectorsToSync[value].push(item); 867 } 868 return result; 869 } 870 871 function setupSelectorSync() { 872 const selectorsToSync = findSelectorsToSyncByLanguage();
873 Object.entries(selectorsToSync).forEach(([group, tabSetsByValue]) => { 874 Object.entries(tabSetsByValue).forEach(([value, items]) => { 875 items.forEach((item) => { 876 item.addEventListener("click", (_event) => { 877 setTabState(group, value); 878 toggleAll(value, selectorsToSync[group]); 879 }); 880 }); 881 }); 882 }); 883 return selectorsToSync; 884 } 885 886 const selectorsToSync = setupSelectorSync(); 887 for (const [group, selectedName] of Object.entries(getTabSettings())) { 888 const selectors = selectorsToSync[group]; 889 // it's possible that stale state gives us empty selections, so we explicitly check here. 890 if (selectors) { 891 toggleAll(selectedName, selectors); 892 } 893 } 894}); 895 896function throttle(func, wait) { 897 let waiting = false; 898 return function () { 899 if (!waiting) { 900 func.apply(this, arguments); 901 waiting = true; 902 setTimeout(function () { 903 waiting = false; 904 }, wait); 905 } 906 }; 907} 908 909function nexttick(func) { 910 return setTimeout(func, 0); 911}
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.