// Inspired by https://github.com/JorelAli/mdBook-pagetoc/tree/98ee241 (under WTFPL) let activeHref = location.href; function updatePageToc(elem = undefined) { let selectedPageTocElem = elem; const pagetoc = document.getElementById("pagetoc"); function getRect(element) { return element.getBoundingClientRect(); } function overflowTop(container, element) { return getRect(container).top - getRect(element).top; } function overflowBottom(container, element) { return getRect(container).bottom - getRect(element).bottom; } // We've not selected a heading to highlight, and the URL needs updating // so we need to find a heading based on the URL if (selectedPageTocElem === undefined && location.href !== activeHref) { activeHref = location.href; for (const pageTocElement of pagetoc.children) { if (pageTocElement.href === activeHref) { selectedPageTocElem = pageTocElement; } } } // We still don't have a selected heading, let's try and find the most // suitable heading based on the scroll position if (selectedPageTocElem === undefined) { const margin = window.innerHeight / 3; const headers = document.getElementsByClassName("header"); for (let i = 0; i < headers.length; i++) { const header = headers[i]; if (selectedPageTocElem === undefined && getRect(header).top >= 0) { if (getRect(header).top < margin) { selectedPageTocElem = header; } else { selectedPageTocElem = headers[Math.max(0, i - 1)]; } } // a very long last section's heading is over the screen if (selectedPageTocElem === undefined && i === headers.length - 1) { selectedPageTocElem = header; } } } // Remove the active flag from all pagetoc elements for (const pageTocElement of pagetoc.children) { pageTocElement.classList.remove("active"); } // If we have a selected heading, set it to active and scroll to it if (selectedPageTocElem !== undefined) { for (const pageTocElement of pagetoc.children) { if (selectedPageTocElem.href.localeCompare(pageTocElement.href) === 0) { pageTocElement.classList.add("active"); if (overflowTop(pagetoc, pageTocElement) > 0) { pagetoc.scrollTop = pageTocElement.offsetTop; } if (overflowBottom(pagetoc, pageTocElement) < 0) { pagetoc.scrollTop -= overflowBottom(pagetoc, pageTocElement); } } } } } if (document.getElementById("sidetoc") === null && document.getElementsByClassName("header").length > 0) { // The sidetoc element doesn't exist yet, let's create it // Create the empty sidetoc and pagetoc elements const sidetoc = document.createElement("div"); const pagetoc = document.createElement("div"); sidetoc.id = "sidetoc"; pagetoc.id = "pagetoc"; sidetoc.appendChild(pagetoc); // And append them to the current DOM const main = document.querySelector('main'); main.insertBefore(sidetoc, main.firstChild); // Populate sidebar on load window.addEventListener("load", () => { for (const header of document.getElementsByClassName("header")) { const link = document.createElement("a"); link.innerHTML = header.innerHTML; link.href = header.hash; link.classList.add("pagetoc-" + header.parentElement.tagName); document.getElementById("pagetoc").appendChild(link); link.onclick = () => updatePageToc(link); } updatePageToc(); }); // Update page table of contents selected heading on scroll window.addEventListener("scroll", () => updatePageToc()); }