Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/src/resources/formats/html/templates/quarto-html-after-body.ejs
12923 views
<% if (darkMode !== undefined || tabby || anchors || copyCode || codeTools || tippy || hoverFootnotes || hoverCitations || linkExternalIcon || linkExternalNewwindow || hoverXrefs) { %> 
  <script id = "quarto-html-after-body" type="application/javascript">
  
  
  window.document.addEventListener("DOMContentLoaded", function (event) {
  
  
    <% if (darkMode !== undefined) { %> 

    
    // Ensure there is a toggle, if there isn't float one in the top right
    if (window.document.querySelector('.quarto-color-scheme-toggle') === null) {
      const a = window.document.createElement('a');
      a.classList.add('top-right');
      a.classList.add('quarto-color-scheme-toggle');
      a.href = "";
      a.onclick = function() { try { window.quartoToggleColorScheme(); } catch {} return false; };
  
      const i = window.document.createElement("i");
      i.classList.add('bi');
      a.appendChild(i);
  
      window.document.body.appendChild(a);
    }
    setColorSchemeToggle(hasAlternateSentinel())

    <% } %>
  
    <% if (tabby) {  %>
  
    const tabsets =  window.document.querySelectorAll(".panel-tabset-tabby")
    tabsets.forEach(function(tabset) {
      const tabby = new Tabby('#' + tabset.id);
    });
  
    <% } %>
  
    <% if (anchors) { %>
  
    const icon = "<%= anchors === true ? '' : anchors %>";
    const anchorJS = new window.AnchorJS();
    anchorJS.options = {
      placement: 'right',
      icon: icon
    };
    anchorJS.add('.anchored');
    <% } %>
  
    <% if (copyCode) { %>
  
    const isCodeAnnotation = (el) => {
      for (const clz of el.classList) {
        if (clz.startsWith('code-annotation-')) {                     
          return true;
        }
      }
      return false;
    }
  
    const onCopySuccess = function(e) {
      // button target
      const button = e.trigger;
      // don't keep focus
      button.blur();
      // flash "checked"
      button.classList.add('code-copy-button-checked');
      var currentTitle = button.getAttribute("title");
      button.setAttribute("title", "<%- language['copy-button-tooltip-success'] %>");
  
  
      let tooltip;
      if (window.bootstrap) {
        button.setAttribute("data-bs-toggle", "tooltip");
        button.setAttribute("data-bs-placement", "left");
        button.setAttribute("data-bs-title", "<%- language['copy-button-tooltip-success'] %>");
  
        tooltip = new bootstrap.Tooltip(button, 
          { trigger: "manual", 
            customClass: "code-copy-button-tooltip",
            offset: [0, -8]});
        tooltip.show();    
      }
  
      setTimeout(function() {
        if (tooltip) {
          tooltip.hide();
          button.removeAttribute("data-bs-title");
          button.removeAttribute("data-bs-toggle");
          button.removeAttribute("data-bs-placement");
        }
        button.setAttribute("title", currentTitle);
        button.classList.remove('code-copy-button-checked');
      }, 1000);
      // clear code selection
      e.clearSelection();
    }
  
    const getTextToCopy = function(trigger) {
      const outerScaffold = trigger.parentElement.cloneNode(true);
      const codeEl = outerScaffold.querySelector('code');
      for (const childEl of codeEl.children) {
        if (isCodeAnnotation(childEl)) {
          childEl.remove();
        }
      }
      return codeEl.innerText;
    }
  
    const clipboard = new window.ClipboardJS('.code-copy-button:not([data-in-quarto-modal])', {
      text: getTextToCopy
    });
    clipboard.on('success', onCopySuccess);
  
    
    if (window.document.getElementById('quarto-embedded-source-code-modal')) {
      <% /* %>
      // For code content inside modals, clipBoardJS needs to be initialized with a container option
      // TODO: Check when it could be a function (https://github.com/zenorocha/clipboard.js/issues/860)
      <% */ %>
      const clipboardModal = new window.ClipboardJS('.code-copy-button[data-in-quarto-modal]', {
        text: getTextToCopy,
        container: window.document.getElementById('quarto-embedded-source-code-modal')
      });
      clipboardModal.on('success', onCopySuccess);
    }
  
    <% } %>
  
    <% if (codeTools) { %>
  
    const viewSource = window.document.getElementById('quarto-view-source') ||
                       window.document.getElementById('quarto-code-tools-source');
    if (viewSource) {
      const sourceUrl = viewSource.getAttribute("data-quarto-source-url");
      viewSource.addEventListener("click", function(e) {
        if (sourceUrl) {
          // rstudio viewer pane
          if (/\bcapabilities=\b/.test(window.location)) {
            window.open(sourceUrl);
          } else {
            window.location.href = sourceUrl;
          }
        } else {
          const modal = new bootstrap.Modal(document.getElementById('quarto-embedded-source-code-modal'));
          modal.show();
        }
        return false;
      });
    }
    function toggleCodeHandler(show) {
      return function(e) {
        const detailsSrc = window.document.querySelectorAll(".cell > details > .sourceCode");
        for (let i=0; i<detailsSrc.length; i++) {
          const details = detailsSrc[i].parentElement;
          if (show) {
            details.open = true;
          } else {
            details.removeAttribute("open");
          }
        }
        const cellCodeDivs = window.document.querySelectorAll(".cell > .sourceCode");
        const fromCls = show ? "hidden" : "unhidden";
        const toCls = show ? "unhidden" : "hidden";
        for (let i=0; i<cellCodeDivs.length; i++) {
          const codeDiv = cellCodeDivs[i];
          if (codeDiv.classList.contains(fromCls)) {
            codeDiv.classList.remove(fromCls);
            codeDiv.classList.add(toCls);
          } 
        }
        return false;
      }
    }
    const hideAllCode = window.document.getElementById("quarto-hide-all-code");
    if (hideAllCode) {
      hideAllCode.addEventListener("click", toggleCodeHandler(false));
    }
    const showAllCode = window.document.getElementById("quarto-show-all-code");
    if (showAllCode) {
      showAllCode.addEventListener("click", toggleCodeHandler(true));
    }
  
    <% } %>
  
      var localhostRegex = new RegExp(/^(?:http|https):\/\/localhost\:?[0-9]*\//);
      var mailtoRegex = new RegExp(/^mailto:/);
      <% if (linkExternalFilter && linkExternalFilter.match(/\\\\/)) { %>
        <%- `var filterRegex = new RegExp(/${linkExternalFilter}/);` %>
      <% } else if (linkExternalFilter) { %>
        <%= `var filterRegex = new RegExp("${linkExternalFilter}");` %>
      <% } else { %>
        var filterRegex = new RegExp('/' + window.location.host + '/');
      <% } %>
      var isInternal = (href) => {
          return filterRegex.test(href) || localhostRegex.test(href) || mailtoRegex.test(href);
      }
      // Inspect non-navigation links and adorn them if external
     var links = window.document.querySelectorAll('a[href]:not(.nav-link):not(.navbar-brand):not(.toc-action):not(.sidebar-link):not(.sidebar-item-toggle):not(.pagination-link):not(.no-external):not([aria-hidden]):not(.dropdown-item):not(.quarto-navigation-tool):not(.about-link)');
      for (var i=0; i<links.length; i++) {
        const link = links[i];
        if (!isInternal(link.href)) {
          // undo the damage that might have been done by quarto-nav.js in the case of
          // links that we want to consider external
          if (link.dataset.originalHref !== undefined) {
            link.href = link.dataset.originalHref;
          }
          <% if (linkExternalNewwindow) { %> 
            // target, if specified
            link.setAttribute("target", "_blank");
            if (link.getAttribute("rel") === null) {
              link.setAttribute("rel", "noopener");
            }
          <% } %>
  
          
          <% if (linkExternalIcon && typeof(linkExternalIcon) === 'string') { %> 
            // external icon, if provided
            var extIcon = window.document.createElement('i');
            extIcon.classList.add('bi-<%=linkExternalIcon%>');
            extIcon.classList.add('quarto-ext-icon');
            link.parentNode.insertBefore(extIcon, link.nextSibling)
          <% } else if (linkExternalIcon) { %>
            // default icon
            link.classList.add("external");
          <% } %>
        }
      }
  
  
  
    <% if (tippy) { %>
    function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) {
      const config = {
        allowHTML: true,
        maxWidth: 500,
        delay: 100,
        arrow: false,
        appendTo: function(el) {
          <% if (tippyOptions.parent) { %>
            return el.closest('<%= tippyOptions.parent %>') || el.parentElement;
          <% } else { %>
            return el.parentElement;
          <% } %>
        },
        interactive: true,
        interactiveBorder: 10,
        theme: '<%= tippyOptions.theme %>',
        placement: 'bottom-start',
      };
      if (contentFn) {
        config.content = contentFn;
      }
  
      if (onTriggerFn) {
        config.onTrigger = onTriggerFn;
      }
  
      if (onUntriggerFn) {
        config.onUntrigger = onUntriggerFn;
      }
  
      <% for(const key of Object.keys(tippyOptions.config)) { %>
        config['<%= key %>'] = <%= JSON.stringify(tippyOptions.config[key]) %>;
      <% } %>
      window.tippy(el, config); 
    }
    <% } %>
  
    <% if (hoverFootnotes) { %>
  
    const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]');
    for (var i=0; i<noterefs.length; i++) {
      const ref = noterefs[i];
      tippyHover(ref, function() {
        // use id or data attribute instead here
        let href = ref.getAttribute('data-footnote-href') || ref.getAttribute('href');
        try { href = new URL(href).hash; } catch {}
        const id = href.replace(/^#\/?/, "");
        const note = window.document.getElementById(id);
  
        if (note) {
          return note.innerHTML;
        } else {
          return "";
        }
      });
    }
  
    <% } %>
  
    <% if (hoverXrefs) { %>
    const xrefs = window.document.querySelectorAll('a.quarto-xref');
  
    const processXRef = (id, note) => {
  
      // Strip column container classes
      const stripColumnClz = (el) => {
        el.classList.remove("page-full", "page-columns");
        if (el.children) {
          for (const child of el.children) {
            stripColumnClz(child);
          }
        }
      }
      stripColumnClz(note)
  
      if (id === null || id.startsWith('sec-')) {
        // Special case sections, only their first couple elements
        const container = document.createElement("div");
        if (note.children && note.children.length > 2) {
  
          container.appendChild(note.children[0].cloneNode(true));
          for (let i = 1; i < note.children.length; i++) {
            const child = note.children[i];
            if (child.tagName === "P" && child.innerText === "") {
              continue;
            } else {
              container.appendChild(child.cloneNode(true));
              break;
            }
          }
  
          if (window.Quarto?.typesetMath) {
            window.Quarto.typesetMath(container);
          }
          return container.innerHTML
        } else {
          if (window.Quarto?.typesetMath) {
            window.Quarto.typesetMath(note);
          }
          return note.innerHTML;
        }
      } else {
        // Remove any anchor links if they are present
        const anchorLink = note.querySelector('a.anchorjs-link');
        if (anchorLink) {
          anchorLink.remove();
        }
        if (window.Quarto?.typesetMath) {
          window.Quarto.typesetMath(note);
        }
        <% /* %>
        // TODO in 1.5, we should make sure this works without a callout special case
        <% */ %>
        if (note.classList.contains("callout")) {
          return note.outerHTML;
        } else {
          return note.innerHTML;
        }
      }
    }
  
  
    for (var i=0; i<xrefs.length; i++) {
      const xref = xrefs[i];
      tippyHover(xref, undefined, function(instance) {
        instance.disable();
        let url = xref.getAttribute('href');
        let hash = undefined; 
        if (url.startsWith('#')) {
          hash = url;
        } else {
          try { hash = new URL(url).hash; } catch {}
        }
  
        if (hash) {
          const id = hash.replace(/^#\/?/, "");
          const note = window.document.getElementById(id);
          if (note !== null) {
            try {
              const html = processXRef(id, note.cloneNode(true));
              instance.setContent(html);
            } finally {
              instance.enable();
              instance.show();
            }
          } else {
            // See if we can fetch this
            fetch(url.split('#')[0])
            .then(res => res.text())
            .then(html => {
              const parser = new DOMParser();
              const htmlDoc = parser.parseFromString(html, "text/html");
              const note = htmlDoc.getElementById(id);
  
              if (note !== null) {
                const html = processXRef(id, note);
                
                instance.setContent(html);
              } 
            }).finally(() => {
              instance.enable();
              instance.show();
            });
          }
        } else {
          // See if we can fetch a full url (with no hash to target)
          // This is a special case and we should probably do some content thinning / targeting
          fetch(url)
          .then(res => res.text())
          .then(html => {
            const parser = new DOMParser();
            const htmlDoc = parser.parseFromString(html, "text/html");
            const note = htmlDoc.querySelector('main.content');
            if (note !== null) {
              // This should only happen for chapter cross references
              // (since there is no id in the URL)
  
              // remove the first header
              if (note.children.length > 0 && note.children[0].tagName === "HEADER") {
                note.children[0].remove();
              }
              const html = processXRef(null, note);
              instance.setContent(html);
            } 
          }).finally(() => {
            instance.enable();
            instance.show();
          });
        }
      }, function(instance) {
      });
    }
  
    <% } %>
  
    <% if (codeAnnotations) { %>
      <% if (codeAnnotations !== "none") { %>
  
        let selectedAnnoteEl;
  
        const selectorForAnnotation = ( cell, annotation) => {
          let cellAttr = 'data-code-cell="' + cell + '"';
          let lineAttr = 'data-code-annotation="' +  annotation + '"';
  
          const selector = 'span[' + cellAttr + '][' + lineAttr + ']';
          return selector;
        }
  
        
        const selectCodeLines = (annoteEl) => {
          const doc = window.document;
          const targetCell = annoteEl.getAttribute("data-target-cell");
          const targetAnnotation = annoteEl.getAttribute("data-target-annotation");
          
          const annoteSpan = window.document.querySelector(selectorForAnnotation(targetCell, targetAnnotation));
          const lines = annoteSpan.getAttribute("data-code-lines").split(",");
  
          const lineIds = lines.map((line) => {
            return targetCell + "-" + line;
          })
  
          let top = null;
          let height = null;
          let parent = null;
          if (lineIds.length > 0) {
              //compute the position of the single el (top and bottom and make a div)
              const el = window.document.getElementById(lineIds[0]);
              top = el.offsetTop;
              height = el.offsetHeight;
              parent = el.parentElement.parentElement;
  
            if (lineIds.length > 1) {
              const lastEl = window.document.getElementById(lineIds[lineIds.length - 1]);
              const bottom = lastEl.offsetTop + lastEl.offsetHeight;
              height = bottom - top;
            }
  
            if (top !== null && height !== null && parent !== null) {
              // cook up a div (if necessary) and position it 
              let div = window.document.getElementById("code-annotation-line-highlight");
              if (div === null) {
                div = window.document.createElement("div");
                div.setAttribute("id", "code-annotation-line-highlight");
                div.style.position = 'absolute';
                parent.appendChild(div);
              }
              div.style.top = top - 2 + "px";
              div.style.height = height + 4 + "px";
              div.style.left = 0;
              
              let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter");
              if (gutterDiv === null) {
                gutterDiv = window.document.createElement("div");
                gutterDiv.setAttribute("id", "code-annotation-line-highlight-gutter");
                gutterDiv.style.position = 'absolute';
                const codeCell = window.document.getElementById(targetCell);
                const gutter = codeCell.querySelector('.code-annotation-gutter');
                gutter.appendChild(gutterDiv);
              }
  
              gutterDiv.style.top = top - 2 + "px";
              gutterDiv.style.height = height + 4 + "px";
            }
            selectedAnnoteEl = annoteEl;
          }
        };
  
        const unselectCodeLines = () => {
          const elementsIds = ["code-annotation-line-highlight", "code-annotation-line-highlight-gutter"];
          elementsIds.forEach((elId) => {
            const div = window.document.getElementById(elId);
            if (div) {
              div.remove();
            }
          });
          selectedAnnoteEl = undefined;
        };
      <% } %>
  
  
  
          // Handle positioning of the toggle
      window.addEventListener(
        "resize",
        throttle(() => {
          elRect = undefined;
          if (selectedAnnoteEl) {
            selectCodeLines(selectedAnnoteEl);
          }
        }, 10)
      );
  
      function throttle(fn, ms) {
      let throttle = false;
      let timer;
        return (...args) => {
          if(!throttle) { // first call gets through
              fn.apply(this, args);
              throttle = true;
          } else { // all the others get throttled
              if(timer) clearTimeout(timer); // cancel #2
              timer = setTimeout(() => {
                fn.apply(this, args);
                timer = throttle = false;
              }, ms);
          }
        };
      }
  
      <% if (codeAnnotations === true || codeAnnotations === "below")  { %>
        
        // Attach click handler to the DT
        const annoteDls = window.document.querySelectorAll('dt[data-target-cell]');
        for (const annoteDlNode of annoteDls) {
          annoteDlNode.addEventListener('click', (event) => {
            const clickedEl = event.target;
            if (clickedEl !== selectedAnnoteEl) {
              unselectCodeLines();
              const activeEl = window.document.querySelector('dt[data-target-cell].code-annotation-active');
              if (activeEl) {
                activeEl.classList.remove('code-annotation-active');
              }
              
              selectCodeLines(clickedEl);
              clickedEl.classList.add('code-annotation-active');
            } else {
              // Unselect the line
              unselectCodeLines();
              clickedEl.classList.remove('code-annotation-active');
            }
          });
        }
  
      
  
      <% } else if (codeAnnotations === "hover" || codeAnnotations === "select") { %>
  
        const annoteTargets = window.document.querySelectorAll('.code-annotation-anchor');
        for (let i=0; i<annoteTargets.length; i++) {
          const annoteTarget = annoteTargets[i];
  
          const targetCell = annoteTarget.getAttribute("data-target-cell");
          const targetAnnotation = annoteTarget.getAttribute("data-target-annotation");
          const contentFn = () => {
            const content = window.document.querySelector(selectorForAnnotation(targetCell, targetAnnotation));
            if (content) {
              const tipContent = content.cloneNode(true);
              tipContent.classList.add("code-annotation-tip-content");
              return tipContent.outerHTML;
            }
          }
  
          const config = {
            allowHTML: true,
            content: contentFn,
            onShow: (instance) => {
              <% if (codeAnnotations === "select" || codeAnnotations === "hover") { %>selectCodeLines(instance.reference);<% } %>
              instance.reference.classList.add('code-annotation-active');
              window.tippy.hideAll();
            },
            onHide: (instance) => {
              <% if (codeAnnotations === "select" || codeAnnotations === "hover") { %>unselectCodeLines();<% } %>
              instance.reference.classList.remove('code-annotation-active');
            },
            maxWidth: 300,
            delay: [50, 0],
            duration: [200, 0],
            offset: [5, 10],
            arrow: true,
            <% if (codeAnnotations === "select") { %>trigger: 'click',<% } %>
            appendTo: function(el) {
              return el.parentElement.parentElement.parentElement;
            },
            interactive: true,
            interactiveBorder: 10,
            theme: '<%= tippyOptions.theme %>',
            placement: 'right',
            <% if (codeAnnotations === "select") { %>positionFixed: true,<% } %>
            popperOptions: {
              modifiers: [
              {
                name: 'flip',
                options: {
                  flipVariations: false, // true by default
                  allowedAutoPlacements: ['right'],
                  fallbackPlacements: ['right', 'top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end', 'left'],
                },
              },
              {
                name: 'preventOverflow',
                options: {
                  mainAxis: false,
                  altAxis: false
                }
              }
              ]        
            }      
          };
          window.tippy(annoteTarget, config); 
        }
        <% } %>
    <% } %>
  
    <% if (hoverCitations) { %>
  
  
    const findCites = (el) => {
      const parentEl = el.parentElement;
      if (parentEl) {
        const cites = parentEl.dataset.cites;
        if (cites) {
          return {
            el,
            cites: cites.split(' ')
          };
        } else {
          return findCites(el.parentElement)
        }
      } else {
        return undefined;
      }
    };
  
    var bibliorefs = window.document.querySelectorAll('a[role="doc-biblioref"]');
    for (var i=0; i<bibliorefs.length; i++) {
      const ref = bibliorefs[i];
      const citeInfo = findCites(ref);
      if (citeInfo) {
        tippyHover(citeInfo.el, function() {
          var popup = window.document.createElement('div');
          citeInfo.cites.forEach(function(cite) {
            var citeDiv = window.document.createElement('div');
            citeDiv.classList.add('hanging-indent');
            citeDiv.classList.add('csl-entry');
            var biblioDiv = window.document.getElementById('ref-' + cite);
            if (biblioDiv) {
              citeDiv.innerHTML = biblioDiv.innerHTML;
            }
            popup.appendChild(citeDiv);
          });
          return popup.innerHTML;
        });
      }
    }
    
  
    <% } %>
  
  });
  
  </script>
  <% } %>