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