const sectionChanged = new CustomEvent("quarto-sectionChanged", { detail: {}, bubbles: true, cancelable: false, composed: false, }); const layoutMarginEls = () => { // Find any conflicting margin elements and add margins to the // top to prevent overlap const marginChildren = window.document.querySelectorAll( ".column-margin.column-container > * " ); let lastBottom = 0; for (const marginChild of marginChildren) { if (marginChild.offsetParent !== null) { // clear the top margin so we recompute it marginChild.style.marginTop = null; const top = marginChild.getBoundingClientRect().top + window.scrollY; console.log({ childtop: marginChild.getBoundingClientRect().top, scroll: window.scrollY, top, lastBottom, }); if (top < lastBottom) { const margin = lastBottom - top; marginChild.style.marginTop = `${margin}px`; } const styles = window.getComputedStyle(marginChild); const marginTop = parseFloat(styles["marginTop"]); console.log({ top, height: marginChild.getBoundingClientRect().height, marginTop, total: top + marginChild.getBoundingClientRect().height + marginTop, }); lastBottom = top + marginChild.getBoundingClientRect().height + marginTop; } } }; window.document.addEventListener("DOMContentLoaded", function (_event) { // Recompute the position of margin elements anytime the body size changes if (window.ResizeObserver) { const resizeObserver = new window.ResizeObserver( throttle(layoutMarginEls, 50) ); resizeObserver.observe(window.document.body); } const tocEl = window.document.querySelector('nav.toc-active[role="doc-toc"]'); const sidebarEl = window.document.getElementById("quarto-sidebar"); const leftTocEl = window.document.getElementById("quarto-sidebar-toc-left"); const marginSidebarEl = window.document.getElementById( "quarto-margin-sidebar" ); // function to determine whether the element has a previous sibling that is active const prevSiblingIsActiveLink = (el) => { const sibling = el.previousElementSibling; if (sibling && sibling.tagName === "A") { return sibling.classList.contains("active"); } else { return false; } }; // fire slideEnter for bootstrap tab activations (for htmlwidget resize behavior) function fireSlideEnter(e) { const event = window.document.createEvent("Event"); event.initEvent("slideenter", true, true); window.document.dispatchEvent(event); } const tabs = window.document.querySelectorAll('a[data-bs-toggle="tab"]'); tabs.forEach((tab) => { tab.addEventListener("shown.bs.tab", fireSlideEnter); }); // fire slideEnter for tabby tab activations (for htmlwidget resize behavior) document.addEventListener("tabby", fireSlideEnter, false); // Track scrolling and mark TOC links as active // get table of contents and sidebar (bail if we don't have at least one) const tocLinks = tocEl ? [...tocEl.querySelectorAll("a[data-scroll-target]")] : []; const makeActive = (link) => tocLinks[link].classList.add("active"); const removeActive = (link) => tocLinks[link].classList.remove("active"); const removeAllActive = () => [...Array(tocLinks.length).keys()].forEach((link) => removeActive(link)); // activate the anchor for a section associated with this TOC entry tocLinks.forEach((link) => { link.addEventListener("click", () => { if (link.href.indexOf("#") !== -1) { const anchor = link.href.split("#")[1]; const heading = window.document.querySelector( `[data-anchor-id=${anchor}]` ); if (heading) { // Add the class heading.classList.add("reveal-anchorjs-link"); // function to show the anchor const handleMouseout = () => { heading.classList.remove("reveal-anchorjs-link"); heading.removeEventListener("mouseout", handleMouseout); }; // add a function to clear the anchor when the user mouses out of it heading.addEventListener("mouseout", handleMouseout); } } }); }); const sections = tocLinks.map((link) => { const target = link.getAttribute("data-scroll-target"); if (target.startsWith("#")) { return window.document.getElementById(decodeURI(`${target.slice(1)}`)); } else { return window.document.querySelector(decodeURI(`${target}`)); } }); const sectionMargin = 200; let currentActive = 0; // track whether we've initialized state the first time let init = false; const updateActiveLink = () => { // The index from bottom to top (e.g. reversed list) let sectionIndex = -1; if ( window.innerHeight + window.pageYOffset >= window.document.body.offsetHeight ) { sectionIndex = 0; } else { sectionIndex = [...sections].reverse().findIndex((section) => { if (section) { return window.pageYOffset >= section.offsetTop - sectionMargin; } else { return false; } }); } if (sectionIndex > -1) { const current = sections.length - sectionIndex - 1; if (current !== currentActive) { removeAllActive(); currentActive = current; makeActive(current); if (init) { window.dispatchEvent(sectionChanged); } init = true; } } }; const inHiddenRegion = (top, bottom, hiddenRegions) => { for (const region of hiddenRegions) { if (top <= region.bottom && bottom >= region.top) { return true; } } return false; }; const categorySelector = "header.quarto-title-block .quarto-category"; const activateCategories = (href) => { // Find any categories // Surround them with a link pointing back to: // #category=Authoring try { const categoryEls = window.document.querySelectorAll(categorySelector); for (const categoryEl of categoryEls) { const categoryText = categoryEl.textContent; if (categoryText) { const link = `${href}#category=${encodeURIComponent(categoryText)}`; const linkEl = window.document.createElement("a"); linkEl.setAttribute("href", link); for (const child of categoryEl.childNodes) { linkEl.append(child); } categoryEl.appendChild(linkEl); } } } catch { // Ignore errors } }; function hasTitleCategories() { return window.document.querySelector(categorySelector) !== null; } function offsetRelativeUrl(url) { const offset = getMeta("quarto:offset"); return offset ? offset + url : url; } function offsetAbsoluteUrl(url) { const offset = getMeta("quarto:offset"); const baseUrl = new URL(offset, window.location); const projRelativeUrl = url.replace(baseUrl, ""); if (projRelativeUrl.startsWith("/")) { return projRelativeUrl; } else { return "/" + projRelativeUrl; } } // read a meta tag value function getMeta(metaName) { const metas = window.document.getElementsByTagName("meta"); for (let i = 0; i < metas.length; i++) { if (metas[i].getAttribute("name") === metaName) { return metas[i].getAttribute("content"); } } return ""; } async function findAndActivateCategories() { const currentPagePath = offsetAbsoluteUrl(window.location.href); const response = await fetch(offsetRelativeUrl("listings.json")); if (response.status == 200) { return response.json().then(function (listingPaths) { const listingHrefs = []; for (const listingPath of listingPaths) { const pathWithoutLeadingSlash = listingPath.listing.substring(1); for (const item of listingPath.items) { if ( item === currentPagePath || item === currentPagePath + "index.html" ) { // Resolve this path against the offset to be sure // we already are using the correct path to the listing // (this adjusts the listing urls to be rooted against // whatever root the page is actually running against) const relative = offsetRelativeUrl(pathWithoutLeadingSlash); const baseUrl = window.location; const resolvedPath = new URL(relative, baseUrl); listingHrefs.push(resolvedPath.pathname); break; } } } // Look up the tree for a nearby linting and use that if we find one const nearestListing = findNearestParentListing( offsetAbsoluteUrl(window.location.pathname), listingHrefs ); if (nearestListing) { activateCategories(nearestListing); } else { // See if the referrer is a listing page for this item const referredRelativePath = offsetAbsoluteUrl(document.referrer); const referrerListing = listingHrefs.find((listingHref) => { const isListingReferrer = listingHref === referredRelativePath || listingHref === referredRelativePath + "index.html"; return isListingReferrer; }); if (referrerListing) { // Try to use the referrer if possible activateCategories(referrerListing); } else if (listingHrefs.length > 0) { // Otherwise, just fall back to the first listing activateCategories(listingHrefs[0]); } } }); } } if (hasTitleCategories()) { findAndActivateCategories(); } const findNearestParentListing = (href, listingHrefs) => { if (!href || !listingHrefs) { return undefined; } // Look up the tree for a nearby linting and use that if we find one const relativeParts = href.substring(1).split("/"); while (relativeParts.length > 0) { const path = relativeParts.join("/"); for (const listingHref of listingHrefs) { if (listingHref.startsWith(path)) { return listingHref; } } relativeParts.pop(); } return undefined; }; const manageSidebarVisiblity = (el, placeholderDescriptor) => { let isVisible = true; let elRect; return (hiddenRegions) => { if (el === null) { return; } // Find the last element of the TOC const lastChildEl = el.lastElementChild; if (lastChildEl) { // Converts the sidebar to a menu const convertToMenu = () => { for (const child of el.children) { child.style.opacity = 0; child.style.overflow = "hidden"; } nexttick(() => { const toggleContainer = window.document.createElement("div"); toggleContainer.style.width = "100%"; toggleContainer.classList.add("zindex-over-content"); toggleContainer.classList.add("quarto-sidebar-toggle"); toggleContainer.classList.add("headroom-target"); // Marks this to be managed by headeroom toggleContainer.id = placeholderDescriptor.id; toggleContainer.style.position = "fixed"; const toggleIcon = window.document.createElement("i"); toggleIcon.classList.add("quarto-sidebar-toggle-icon"); toggleIcon.classList.add("bi"); toggleIcon.classList.add("bi-caret-down-fill"); const toggleTitle = window.document.createElement("div"); const titleEl = window.document.body.querySelector( placeholderDescriptor.titleSelector ); if (titleEl) { toggleTitle.append( titleEl.textContent || titleEl.innerText, toggleIcon ); } toggleTitle.classList.add("zindex-over-content"); toggleTitle.classList.add("quarto-sidebar-toggle-title"); toggleContainer.append(toggleTitle); const toggleContents = window.document.createElement("div"); toggleContents.classList = el.classList; toggleContents.classList.add("zindex-over-content"); toggleContents.classList.add("quarto-sidebar-toggle-contents"); for (const child of el.children) { if (child.id === "toc-title") { continue; } const clone = child.cloneNode(true); clone.style.opacity = 1; clone.style.display = null; toggleContents.append(clone); } toggleContents.style.height = "0px"; const positionToggle = () => { // position the element (top left of parent, same width as parent) if (!elRect) { elRect = el.getBoundingClientRect(); } toggleContainer.style.left = `${elRect.left}px`; toggleContainer.style.top = `${elRect.top}px`; toggleContainer.style.width = `${elRect.width}px`; }; positionToggle(); toggleContainer.append(toggleContents); el.parentElement.prepend(toggleContainer); // Process clicks let tocShowing = false; // Allow the caller to control whether this is dismissed // when it is clicked (e.g. sidebar navigation supports // opening and closing the nav tree, so don't dismiss on click) const clickEl = placeholderDescriptor.dismissOnClick ? toggleContainer : toggleTitle; const closeToggle = () => { if (tocShowing) { toggleContainer.classList.remove("expanded"); toggleContents.style.height = "0px"; tocShowing = false; } }; // Get rid of any expanded toggle if the user scrolls window.document.addEventListener( "scroll", throttle(() => { closeToggle(); }, 50) ); // Handle positioning of the toggle window.addEventListener( "resize", throttle(() => { elRect = undefined; positionToggle(); }, 50) ); window.addEventListener("quarto-hrChanged", () => { elRect = undefined; }); // Process the click clickEl.onclick = () => { if (!tocShowing) { toggleContainer.classList.add("expanded"); toggleContents.style.height = null; tocShowing = true; } else { closeToggle(); } }; }); }; // Converts a sidebar from a menu back to a sidebar const convertToSidebar = () => { for (const child of el.children) { child.style.opacity = 1; child.style.overflow = null; } const placeholderEl = window.document.getElementById( placeholderDescriptor.id ); if (placeholderEl) { placeholderEl.remove(); } el.classList.remove("rollup"); }; if (isReaderMode()) { convertToMenu(); isVisible = false; } else { // Find the top and bottom o the element that is being managed const elTop = el.offsetTop; const elBottom = elTop + lastChildEl.offsetTop + lastChildEl.offsetHeight; if (!isVisible) { // If the element is current not visible reveal if there are // no conflicts with overlay regions if (!inHiddenRegion(elTop, elBottom, hiddenRegions)) { convertToSidebar(); isVisible = true; } } else { // If the element is visible, hide it if it conflicts with overlay regions // and insert a placeholder toggle (or if we're in reader mode) if (inHiddenRegion(elTop, elBottom, hiddenRegions)) { convertToMenu(); isVisible = false; } } } } }; }; const tabEls = document.querySelectorAll('a[data-bs-toggle="tab"]'); for (const tabEl of tabEls) { const id = tabEl.getAttribute("data-bs-target"); if (id) { const columnEl = document.querySelector( `${id} .column-margin, .tabset-margin-content` ); if (columnEl) tabEl.addEventListener("shown.bs.tab", function (event) { const el = event.srcElement; if (el) { const visibleCls = `${el.id}-margin-content`; // walk up until we find a parent tabset let panelTabsetEl = el.parentElement; while (panelTabsetEl) { if (panelTabsetEl.classList.contains("panel-tabset")) { break; } panelTabsetEl = panelTabsetEl.parentElement; } if (panelTabsetEl) { const prevSib = panelTabsetEl.previousElementSibling; if ( prevSib && prevSib.classList.contains("tabset-margin-container") ) { const childNodes = prevSib.querySelectorAll( ".tabset-margin-content" ); for (const childEl of childNodes) { if (childEl.classList.contains(visibleCls)) { childEl.classList.remove("collapse"); } else { childEl.classList.add("collapse"); } } } } } layoutMarginEls(); }); } } // Manage the visibility of the toc and the sidebar const marginScrollVisibility = manageSidebarVisiblity(marginSidebarEl, { id: "quarto-toc-toggle", titleSelector: "#toc-title", dismissOnClick: true, }); const sidebarScrollVisiblity = manageSidebarVisiblity(sidebarEl, { id: "quarto-sidebarnav-toggle", titleSelector: ".title", dismissOnClick: false, }); let tocLeftScrollVisibility; if (leftTocEl) { tocLeftScrollVisibility = manageSidebarVisiblity(leftTocEl, { id: "quarto-lefttoc-toggle", titleSelector: "#toc-title", dismissOnClick: true, }); } // Find the first element that uses formatting in special columns const conflictingEls = window.document.body.querySelectorAll( '[class^="column-"], [class*=" column-"], aside, [class*="margin-caption"], [class*=" margin-caption"], [class*="margin-ref"], [class*=" margin-ref"]' ); // Filter all the possibly conflicting elements into ones // the do conflict on the left or ride side const arrConflictingEls = Array.from(conflictingEls); const leftSideConflictEls = arrConflictingEls.filter((el) => { if (el.tagName === "ASIDE") { return false; } return Array.from(el.classList).find((className) => { return ( className !== "column-body" && className.startsWith("column-") && !className.endsWith("right") && !className.endsWith("container") && className !== "column-margin" ); }); }); const rightSideConflictEls = arrConflictingEls.filter((el) => { if (el.tagName === "ASIDE") { return true; } const hasMarginCaption = Array.from(el.classList).find((className) => { return className == "margin-caption"; }); if (hasMarginCaption) { return true; } return Array.from(el.classList).find((className) => { return ( className !== "column-body" && !className.endsWith("container") && className.startsWith("column-") && !className.endsWith("left") ); }); }); const kOverlapPaddingSize = 10; function toRegions(els) { return els.map((el) => { const boundRect = el.getBoundingClientRect(); const top = boundRect.top + document.documentElement.scrollTop - kOverlapPaddingSize; return { top, bottom: top + el.scrollHeight + 2 * kOverlapPaddingSize, }; }); } let hasObserved = false; const visibleItemObserver = (els) => { let visibleElements = [...els]; const intersectionObserver = new IntersectionObserver( (entries, _observer) => { entries.forEach((entry) => { if (entry.isIntersecting) { if (visibleElements.indexOf(entry.target) === -1) { visibleElements.push(entry.target); } } else { visibleElements = visibleElements.filter((visibleEntry) => { return visibleEntry !== entry; }); } }); if (!hasObserved) { hideOverlappedSidebars(); } hasObserved = true; }, {} ); els.forEach((el) => { intersectionObserver.observe(el); }); return { getVisibleEntries: () => { return visibleElements; }, }; }; const rightElementObserver = visibleItemObserver(rightSideConflictEls); const leftElementObserver = visibleItemObserver(leftSideConflictEls); const hideOverlappedSidebars = () => { marginScrollVisibility(toRegions(rightElementObserver.getVisibleEntries())); sidebarScrollVisiblity(toRegions(leftElementObserver.getVisibleEntries())); if (tocLeftScrollVisibility) { tocLeftScrollVisibility( toRegions(leftElementObserver.getVisibleEntries()) ); } }; window.quartoToggleReader = () => { // Applies a slow class (or removes it) // to update the transition speed const slowTransition = (slow) => { const manageTransition = (id, slow) => { const el = document.getElementById(id); if (el) { if (slow) { el.classList.add("slow"); } else { el.classList.remove("slow"); } } }; manageTransition("TOC", slow); manageTransition("quarto-sidebar", slow); }; const readerMode = !isReaderMode(); setReaderModeValue(readerMode); // If we're entering reader mode, slow the transition if (readerMode) { slowTransition(readerMode); } highlightReaderToggle(readerMode); hideOverlappedSidebars(); // If we're exiting reader mode, restore the non-slow transition if (!readerMode) { slowTransition(!readerMode); } }; const highlightReaderToggle = (readerMode) => { const els = document.querySelectorAll(".quarto-reader-toggle"); if (els) { els.forEach((el) => { if (readerMode) { el.classList.add("reader"); } else { el.classList.remove("reader"); } }); } }; const setReaderModeValue = (val) => { if (window.location.protocol !== "file:") { window.localStorage.setItem("quarto-reader-mode", val); } else { localReaderMode = val; } }; const isReaderMode = () => { if (window.location.protocol !== "file:") { return window.localStorage.getItem("quarto-reader-mode") === "true"; } else { return localReaderMode; } }; let localReaderMode = null; const tocOpenDepthStr = tocEl?.getAttribute("data-toc-expanded"); const tocOpenDepth = tocOpenDepthStr ? Number(tocOpenDepthStr) : 1; // Walk the TOC and collapse/expand nodes // Nodes are expanded if: // - they are top level // - they have children that are 'active' links // - they are directly below an link that is 'active' const walk = (el, depth) => { // Tick depth when we enter a UL if (el.tagName === "UL") { depth = depth + 1; } // It this is active link let isActiveNode = false; if (el.tagName === "A" && el.classList.contains("active")) { isActiveNode = true; } // See if there is an active child to this element let hasActiveChild = false; for (child of el.children) { hasActiveChild = walk(child, depth) || hasActiveChild; } // Process the collapse state if this is an UL if (el.tagName === "UL") { if (tocOpenDepth === -1 && depth > 1) { el.classList.add("collapse"); } else if ( depth <= tocOpenDepth || hasActiveChild || prevSiblingIsActiveLink(el) ) { el.classList.remove("collapse"); } else { el.classList.add("collapse"); } // untick depth when we leave a UL depth = depth - 1; } return hasActiveChild || isActiveNode; }; // walk the TOC and expand / collapse any items that should be shown if (tocEl) { walk(tocEl, 0); updateActiveLink(); } // Throttle the scroll event and walk peridiocally window.document.addEventListener( "scroll", throttle(() => { if (tocEl) { updateActiveLink(); walk(tocEl, 0); } if (!isReaderMode()) { hideOverlappedSidebars(); } }, 5) ); window.addEventListener( "resize", throttle(() => { if (!isReaderMode()) { hideOverlappedSidebars(); } }, 10) ); hideOverlappedSidebars(); highlightReaderToggle(isReaderMode()); }); // grouped tabsets window.addEventListener("pageshow", (_event) => { function getTabSettings() { const data = localStorage.getItem("quarto-persistent-tabsets-data"); if (!data) { localStorage.setItem("quarto-persistent-tabsets-data", "{}"); return {}; } if (data) { return JSON.parse(data); } } function setTabSettings(data) { localStorage.setItem( "quarto-persistent-tabsets-data", JSON.stringify(data) ); } function setTabState(groupName, groupValue) { const data = getTabSettings(); data[groupName] = groupValue; setTabSettings(data); } function toggleTab(tab, active) { const tabPanelId = tab.getAttribute("aria-controls"); const tabPanel = document.getElementById(tabPanelId); if (active) { tab.classList.add("active"); tabPanel.classList.add("active"); } else { tab.classList.remove("active"); tabPanel.classList.remove("active"); } } function toggleAll(selectedGroup, selectorsToSync) { for (const [thisGroup, tabs] of Object.entries(selectorsToSync)) { const active = selectedGroup === thisGroup; for (const tab of tabs) { toggleTab(tab, active); } } } function findSelectorsToSyncByLanguage() { const result = {}; const tabs = Array.from( document.querySelectorAll(`div[data-group] a[id^='tabset-']`) ); for (const item of tabs) { const div = item.parentElement.parentElement.parentElement; const group = div.getAttribute("data-group"); if (!result[group]) { result[group] = {}; } const selectorsToSync = result[group]; const value = item.innerHTML; if (!selectorsToSync[value]) { selectorsToSync[value] = []; } selectorsToSync[value].push(item); } return result; } function setupSelectorSync() { const selectorsToSync = findSelectorsToSyncByLanguage(); Object.entries(selectorsToSync).forEach(([group, tabSetsByValue]) => { Object.entries(tabSetsByValue).forEach(([value, items]) => { items.forEach((item) => { item.addEventListener("click", (_event) => { setTabState(group, value); toggleAll(value, selectorsToSync[group]); }); }); }); }); return selectorsToSync; } const selectorsToSync = setupSelectorSync(); for (const [group, selectedName] of Object.entries(getTabSettings())) { const selectors = selectorsToSync[group]; // it's possible that stale state gives us empty selections, so we explicitly check here. if (selectors) { toggleAll(selectedName, selectors); } } }); function throttle(func, wait) { let waiting = false; return function () { if (!waiting) { func.apply(this, arguments); waiting = true; setTimeout(function () { waiting = false; }, wait); } }; } function nexttick(func) { return setTimeout(func, 0); }