From d09d10f7742703800c0d0751bc9fb9c56b35d5ab Mon Sep 17 00:00:00 2001 From: Jens Reimann Date: Sun, 21 Apr 2024 13:13:52 +0200 Subject: [PATCH] docs: add a pagetoc to the theme --- guide/book.toml | 4 +- guide/theme/pagetoc.css | 105 ++++++++++++++++++++++++++++++ guide/theme/pagetoc.js | 138 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 guide/theme/pagetoc.css create mode 100644 guide/theme/pagetoc.js diff --git a/guide/book.toml b/guide/book.toml index 80c22008..1f39cc63 100644 --- a/guide/book.toml +++ b/guide/book.toml @@ -1,5 +1,5 @@ [book] -title = "The `trunk` Guide" +title = "The Trunk Guide" description = "Documention of Trunk, a web application bundler for Rust" src = "src" @@ -21,7 +21,9 @@ assets_version = "3.0.2" # do not edit: managed by `mdbook-admonish install` [output.html] additional-css = [ "./mdbook-admonish.css", + "theme/pagetoc.css" ] additional-js = [ + "theme/pagetoc.js" ] diff --git a/guide/theme/pagetoc.css b/guide/theme/pagetoc.css new file mode 100644 index 00000000..1cc776b5 --- /dev/null +++ b/guide/theme/pagetoc.css @@ -0,0 +1,105 @@ +:root { + --toc-width: 270px; + --center-content-toc-shift: calc(-1 * var(--toc-width) / 2); +} + +.nav-chapters { + /* adjust width of buttons that bring to the previous or the next page */ + min-width: 50px; +} + +.previous { + /* + adjust the space between the left sidebar or the left side of the screen + and the button that leads to the previous page + */ + margin-left: var(--page-padding); +} + +@media only screen { + main { + display: flex; + } + + @media (max-width: 1179px) { + .sidebar-hidden .sidetoc { + display: none; + } + } + + @media (max-width: 1439px) { + .sidebar-visible .sidetoc { + display: none; + } + } + + @media (1180px <= width <= 1439px) { + .sidebar-hidden main { + position: relative; + left: var(--center-content-toc-shift); + } + } + + @media (1440px <= width <= 1700px) { + .sidebar-visible main { + position: relative; + left: var(--center-content-toc-shift); + } + } + + .content-wrap { + overflow-y: auto; + width: 100%; + } + + .sidetoc { + margin-top: 20px; + margin-left: 10px; + margin-right: auto; + } + .pagetoc { + position: fixed; + /* adjust TOC width */ + width: var(--toc-width); + height: calc(100vh - var(--menu-bar-height) - 0.67em * 4); + overflow: auto; + } + .pagetoc a { + border-left: 1px solid var(--sidebar-bg); + color: var(--fg) !important; + display: block; + padding-bottom: 5px; + padding-top: 5px; + padding-left: 10px; + text-align: left; + text-decoration: none; + } + .pagetoc a:hover, + .pagetoc a.active { + background: var(--sidebar-bg); + color: var(--sidebar-fg) !important; + } + .pagetoc .active { + background: var(--sidebar-bg); + color: var(--sidebar-fg); + font-weight: bold; + } + .pagetoc .pagetoc-H2 { + padding-left: 20px; + font-size: 90%; + } + .pagetoc .pagetoc-H3 { + padding-left: 40px; + font-size: 90%; + } + .pagetoc .pagetoc-H4 { + padding-left: 60px; + font-size: 90%; + } +} + +@media print { + .sidetoc { + display: none; + } +} diff --git a/guide/theme/pagetoc.js b/guide/theme/pagetoc.js new file mode 100644 index 00000000..2e09e770 --- /dev/null +++ b/guide/theme/pagetoc.js @@ -0,0 +1,138 @@ +function forEach(elems, fun) { + Array.prototype.forEach.call(elems, fun); +} + +function getPagetoc(){ + const pagetoc = document.getElementsByClassName("pagetoc")[0]; + + if (pagetoc) { + return pagetoc; + } + + return autoCreatePagetoc(); +} + +function autoCreatePagetoc() { + const main = document.querySelector("#content > main"); + + const content = document.createElement("div"); + content.classList.add("content-wrap"); + content.append(...main.childNodes); + + main.appendChild(content); + + main.insertAdjacentHTML("beforeend", ` +
+ +
+ `); + + return document.getElementsByClassName("pagetoc")[0] +} + +function getPagetocElems() { + return getPagetoc().children; +} + +function getHeaders(){ + return document.getElementsByClassName("header") +} + +// Un-active everything when you click it +function forPagetocElem(fun) { + forEach(getPagetocElems(), fun); +} + +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; +} + +var activeHref = location.href; + +var updateFunction = function (elem = undefined) { + var id = elem; + + if (!id && location.href != activeHref) { + activeHref = location.href; + forPagetocElem(function (el) { + if (el.href === activeHref) { + id = el; + } + }); + } + + if (!id) { + var elements = getHeaders(); + let margin = window.innerHeight / 3; + + forEach(elements, function (el, i, arr) { + if (!id && getRect(el).top >= 0) { + if (getRect(el).top < margin) { + id = el; + } else { + id = arr[Math.max(0, i - 1)]; + } + } + // a very long last section + // its heading is over the screen + if (!id && i == arr.length - 1) { + id = el + } + }); + } + + forPagetocElem(function (el) { + el.classList.remove("active"); + }); + + if (!id) return; + + forPagetocElem(function (el) { + if (id.href.localeCompare(el.href) == 0) { + el.classList.add("active"); + let pagetoc = getPagetoc(); + if (overflowTop(pagetoc, el) > 0) { + pagetoc.scrollTop = el.offsetTop; + } + if (overflowBottom(pagetoc, el) < 0) { + pagetoc.scrollTop -= overflowBottom(pagetoc, el); + } + } + }); +}; + +let elements = getHeaders(); + +if (elements.length > 1) { + // Populate sidebar on load + window.addEventListener("load", function () { + var pagetoc = getPagetoc(); + var elements = getHeaders(); + forEach(elements, function (el) { + var link = document.createElement("a"); + link.appendChild(document.createTextNode(el.text)); + link.href = el.hash; + link.classList.add("pagetoc-" + el.parentElement.tagName); + pagetoc.appendChild(link); + link.onclick = function () { + updateFunction(link); + }; + }); + updateFunction(); + }); + + // Handle active elements on scroll + window.addEventListener("scroll", function () { + updateFunction(); + }); +} else { + document.getElementsByClassName("sidetoc")[0].remove(); +}