123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902 |
- 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);
- }
|