From 8fcff8c6457f16cb0a2760f0b6a846e42d08f0e1 Mon Sep 17 00:00:00 2001 From: Urgau Date: Mon, 21 Apr 2025 14:38:24 +0200 Subject: [PATCH 1/3] Add per page TOC in the rustc book --- src/doc/rustc/book.toml | 2 + src/doc/rustc/theme/pagetoc.css | 104 ++++++++++++++++++++++++++++ src/doc/rustc/theme/pagetoc.js | 118 ++++++++++++++++++++++++++++++++ 3 files changed, 224 insertions(+) create mode 100644 src/doc/rustc/theme/pagetoc.css create mode 100644 src/doc/rustc/theme/pagetoc.js diff --git a/src/doc/rustc/book.toml b/src/doc/rustc/book.toml index 167aece0ed6a3..01f127ad39054 100644 --- a/src/doc/rustc/book.toml +++ b/src/doc/rustc/book.toml @@ -6,6 +6,8 @@ title = "The rustc book" [output.html] git-repository-url = "https://github.com/rust-lang/rust/tree/master/src/doc/rustc" edit-url-template = "https://github.com/rust-lang/rust/edit/master/src/doc/rustc/{path}" +additional-css = ["theme/pagetoc.css"] +additional-js = ["theme/pagetoc.js"] [output.html.search] use-boolean-and = true diff --git a/src/doc/rustc/theme/pagetoc.css b/src/doc/rustc/theme/pagetoc.css new file mode 100644 index 0000000000000..f8f45960d9403 --- /dev/null +++ b/src/doc/rustc/theme/pagetoc.css @@ -0,0 +1,104 @@ +/* Inspired by https://github.com/JorelAli/mdBook-pagetoc/tree/98ee241 (under WTFPL) */ + +: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: -webkit-box; + display: -ms-flexbox; + 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 { + width: 100%; + } + + #sidetoc { + margin-top: 20px; + margin-left: 20px; + 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(--sidebar-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-active) !important; + } + #pagetoc .active { + background: var(--sidebar-bg); + color: var(--sidebar-active); + } + #pagetoc .pagetoc-H2 { + padding-left: 20px; + } + #pagetoc .pagetoc-H3 { + padding-left: 40px; + } + #pagetoc .pagetoc-H4 { + padding-left: 60px; + } +} + +@media print { + #sidetoc { + display: none; + } +} diff --git a/src/doc/rustc/theme/pagetoc.js b/src/doc/rustc/theme/pagetoc.js new file mode 100644 index 0000000000000..29c09903a3756 --- /dev/null +++ b/src/doc/rustc/theme/pagetoc.js @@ -0,0 +1,118 @@ +// 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) { + // Element doesn't exist yet, let's create it + const main = document.querySelector('main'); + const wrapper = document.createElement('div'); + wrapper.className = "content-wrap"; + + // Move all children into the wrapper + while (main.firstChild) { + wrapper.appendChild(main.firstChild); + } + + // Append the wrapper back to main + main.appendChild(wrapper); + + // Create the empty sidetoc and pagetoc elements + const sidetoc = document.createElement("div"); + const pagetoc = document.createElement("div"); + sidetoc.id = "sidetoc"; + pagetoc.id = "pagetoc"; + + // And append them to the current DOM + sidetoc.appendChild(pagetoc); + main.appendChild(sidetoc); +} + +if (document.getElementsByClassName("header").length <= 1) { + // There's one or less headings, we don't need a page table of contents + document.getElementById("sidetoc").remove(); +} else { + // 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()); +} From 72f915aaca3fa3018269de4e57cd1bddf4a17a86 Mon Sep 17 00:00:00 2001 From: Urgau Date: Mon, 21 Apr 2025 17:15:33 +0200 Subject: [PATCH 2/3] Fix flicker when page loads --- src/doc/rustc/theme/pagetoc.css | 14 +------------- src/doc/rustc/theme/pagetoc.js | 20 +++++--------------- 2 files changed, 6 insertions(+), 28 deletions(-) diff --git a/src/doc/rustc/theme/pagetoc.css b/src/doc/rustc/theme/pagetoc.css index f8f45960d9403..26c7ae78e8ac2 100644 --- a/src/doc/rustc/theme/pagetoc.css +++ b/src/doc/rustc/theme/pagetoc.css @@ -19,12 +19,6 @@ } @media only screen { - main { - display: -webkit-box; - display: -ms-flexbox; - display: flex; - } - @media (max-width: 1179px) { .sidebar-hidden #sidetoc { display: none; @@ -51,14 +45,8 @@ } } - .content-wrap { - width: 100%; - } - #sidetoc { - margin-top: 20px; - margin-left: 20px; - margin-right: auto; + margin-left: calc(100% + 20px); } #pagetoc { position: fixed; diff --git a/src/doc/rustc/theme/pagetoc.js b/src/doc/rustc/theme/pagetoc.js index 29c09903a3756..f3549a6de5b02 100644 --- a/src/doc/rustc/theme/pagetoc.js +++ b/src/doc/rustc/theme/pagetoc.js @@ -72,28 +72,18 @@ function updatePageToc(elem = undefined) { } if (document.getElementById("sidetoc") === null) { - // Element doesn't exist yet, let's create it - const main = document.querySelector('main'); - const wrapper = document.createElement('div'); - wrapper.className = "content-wrap"; - - // Move all children into the wrapper - while (main.firstChild) { - wrapper.appendChild(main.firstChild); - } - - // Append the wrapper back to main - main.appendChild(wrapper); + // 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"; - - // And append them to the current DOM sidetoc.appendChild(pagetoc); - main.appendChild(sidetoc); + + // And append them to the current DOM + const main = document.querySelector('main'); + main.insertBefore(sidetoc, main.firstChild); } if (document.getElementsByClassName("header").length <= 1) { From 4cbcb44d702e54b29e4f9df509f0b463e9142e5c Mon Sep 17 00:00:00 2001 From: Urgau Date: Mon, 21 Apr 2025 17:18:22 +0200 Subject: [PATCH 3/3] Cleanup the Javascript and CSS of our custom TOC --- src/doc/rustc/theme/pagetoc.css | 8 -------- src/doc/rustc/theme/pagetoc.js | 10 +++------- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/src/doc/rustc/theme/pagetoc.css b/src/doc/rustc/theme/pagetoc.css index 26c7ae78e8ac2..58ca1f8b26f81 100644 --- a/src/doc/rustc/theme/pagetoc.css +++ b/src/doc/rustc/theme/pagetoc.css @@ -10,14 +10,6 @@ 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 { @media (max-width: 1179px) { .sidebar-hidden #sidetoc { diff --git a/src/doc/rustc/theme/pagetoc.js b/src/doc/rustc/theme/pagetoc.js index f3549a6de5b02..927a5b10749b5 100644 --- a/src/doc/rustc/theme/pagetoc.js +++ b/src/doc/rustc/theme/pagetoc.js @@ -71,7 +71,8 @@ function updatePageToc(elem = undefined) { } } -if (document.getElementById("sidetoc") === null) { +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 @@ -80,16 +81,11 @@ if (document.getElementById("sidetoc") === null) { 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); -} -if (document.getElementsByClassName("header").length <= 1) { - // There's one or less headings, we don't need a page table of contents - document.getElementById("sidetoc").remove(); -} else { // Populate sidebar on load window.addEventListener("load", () => { for (const header of document.getElementsByClassName("header")) {