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