|  | 
|  | 1 | +// Inspired by https://github.com/JorelAli/mdBook-pagetoc/tree/98ee241 (under WTFPL) | 
|  | 2 | + | 
|  | 3 | +let activeHref = location.href; | 
|  | 4 | +function updatePageToc(elem = undefined) { | 
|  | 5 | +    let selectedPageTocElem = elem; | 
|  | 6 | +    const pagetoc = document.getElementById("pagetoc"); | 
|  | 7 | + | 
|  | 8 | +    function getRect(element) { | 
|  | 9 | +        return element.getBoundingClientRect(); | 
|  | 10 | +    } | 
|  | 11 | + | 
|  | 12 | +    function overflowTop(container, element) { | 
|  | 13 | +        return getRect(container).top - getRect(element).top; | 
|  | 14 | +    } | 
|  | 15 | + | 
|  | 16 | +    function overflowBottom(container, element) { | 
|  | 17 | +        return getRect(container).bottom - getRect(element).bottom; | 
|  | 18 | +    } | 
|  | 19 | + | 
|  | 20 | +    // We've not selected a heading to highlight, and the URL needs updating | 
|  | 21 | +    // so we need to find a heading based on the URL | 
|  | 22 | +    if (selectedPageTocElem === undefined && location.href !== activeHref) { | 
|  | 23 | +        activeHref = location.href; | 
|  | 24 | +        for (const pageTocElement of pagetoc.children) { | 
|  | 25 | +            if (pageTocElement.href === activeHref) { | 
|  | 26 | +                selectedPageTocElem = pageTocElement; | 
|  | 27 | +            } | 
|  | 28 | +        } | 
|  | 29 | +    } | 
|  | 30 | + | 
|  | 31 | +    // We still don't have a selected heading, let's try and find the most | 
|  | 32 | +    // suitable heading based on the scroll position | 
|  | 33 | +    if (selectedPageTocElem === undefined) { | 
|  | 34 | +        const margin = window.innerHeight / 3; | 
|  | 35 | + | 
|  | 36 | +        const headers = document.getElementsByClassName("header"); | 
|  | 37 | +        for (let i = 0; i < headers.length; i++) { | 
|  | 38 | +            const header = headers[i]; | 
|  | 39 | +            if (selectedPageTocElem === undefined && getRect(header).top >= 0) { | 
|  | 40 | +                if (getRect(header).top < margin) { | 
|  | 41 | +                    selectedPageTocElem = header; | 
|  | 42 | +                } else { | 
|  | 43 | +                    selectedPageTocElem = headers[Math.max(0, i - 1)]; | 
|  | 44 | +                } | 
|  | 45 | +            } | 
|  | 46 | +            // a very long last section's heading is over the screen | 
|  | 47 | +            if (selectedPageTocElem === undefined && i === headers.length - 1) { | 
|  | 48 | +                selectedPageTocElem = header; | 
|  | 49 | +            } | 
|  | 50 | +        } | 
|  | 51 | +    } | 
|  | 52 | + | 
|  | 53 | +    // Remove the active flag from all pagetoc elements | 
|  | 54 | +    for (const pageTocElement of pagetoc.children) { | 
|  | 55 | +        pageTocElement.classList.remove("active"); | 
|  | 56 | +    } | 
|  | 57 | + | 
|  | 58 | +    // If we have a selected heading, set it to active and scroll to it | 
|  | 59 | +    if (selectedPageTocElem !== undefined) { | 
|  | 60 | +        for (const pageTocElement of pagetoc.children) { | 
|  | 61 | +            if (selectedPageTocElem.href.localeCompare(pageTocElement.href) === 0) { | 
|  | 62 | +                pageTocElement.classList.add("active"); | 
|  | 63 | +                if (overflowTop(pagetoc, pageTocElement) > 0) { | 
|  | 64 | +                    pagetoc.scrollTop = pageTocElement.offsetTop; | 
|  | 65 | +                } | 
|  | 66 | +                if (overflowBottom(pagetoc, pageTocElement) < 0) { | 
|  | 67 | +                    pagetoc.scrollTop -= overflowBottom(pagetoc, pageTocElement); | 
|  | 68 | +                } | 
|  | 69 | +            } | 
|  | 70 | +        } | 
|  | 71 | +    } | 
|  | 72 | +} | 
|  | 73 | + | 
|  | 74 | +if (document.getElementById("sidetoc") === null && | 
|  | 75 | +    document.getElementsByClassName("header").length > 0) { | 
|  | 76 | +    // The sidetoc element doesn't exist yet, let's create it | 
|  | 77 | + | 
|  | 78 | +    // Create the empty sidetoc and pagetoc elements | 
|  | 79 | +    const sidetoc = document.createElement("div"); | 
|  | 80 | +    const pagetoc = document.createElement("div"); | 
|  | 81 | +    sidetoc.id = "sidetoc"; | 
|  | 82 | +    pagetoc.id = "pagetoc"; | 
|  | 83 | +    sidetoc.appendChild(pagetoc); | 
|  | 84 | + | 
|  | 85 | +    // And append them to the current DOM | 
|  | 86 | +    const main = document.querySelector('main'); | 
|  | 87 | +    main.insertBefore(sidetoc, main.firstChild); | 
|  | 88 | + | 
|  | 89 | +    // Populate sidebar on load | 
|  | 90 | +    window.addEventListener("load", () => { | 
|  | 91 | +        for (const header of document.getElementsByClassName("header")) { | 
|  | 92 | +            const link = document.createElement("a"); | 
|  | 93 | +            link.innerHTML = header.innerHTML; | 
|  | 94 | +            link.href = header.hash; | 
|  | 95 | +            link.classList.add("pagetoc-" + header.parentElement.tagName); | 
|  | 96 | +            document.getElementById("pagetoc").appendChild(link); | 
|  | 97 | +            link.onclick = () => updatePageToc(link); | 
|  | 98 | +        } | 
|  | 99 | +        updatePageToc(); | 
|  | 100 | +    }); | 
|  | 101 | + | 
|  | 102 | +    // Update page table of contents selected heading on scroll | 
|  | 103 | +    window.addEventListener("scroll", () => updatePageToc()); | 
|  | 104 | +} | 
0 commit comments