diff --git a/docs/README.md b/docs/README.md index b9a953ea89..194b973ff2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -20,3 +20,9 @@ Putting binary assets such as images in the Git repository will bloat the reposi - We have a Cloudflare router called `docs-proxy` that intercepts requests to `zed.dev/docs` and forwards them to the "docs" Cloudflare Pages project. - CI uploads a new version to the Pages project from `.github/workflows/deploy_docs.yml` on every push to `main`. + +### Table of Contents + +The table of contents files (`theme/page-toc.js` and `theme/page-doc.css`) were initially generated by [`mdbook-pagetoc`](https://crates.io/crates/mdbook-pagetoc). + +Since all these preprocessor does is generate the static assets, we don't need to keep it around once they have been generated. diff --git a/docs/book.toml b/docs/book.toml index 445dc0bcb6..1f439be205 100644 --- a/docs/book.toml +++ b/docs/book.toml @@ -9,6 +9,8 @@ site-url = "/docs/" [output.html] no-section-label = true preferred-dark-theme = "light" +additional-css = ["theme/page-toc.css"] +additional-js = ["theme/page-toc.js"] [output.html.print] enable = false diff --git a/docs/theme/css/chrome.css b/docs/theme/css/chrome.css index 71775cfc50..9097c2bb50 100644 --- a/docs/theme/css/chrome.css +++ b/docs/theme/css/chrome.css @@ -560,7 +560,7 @@ ul#searchresults span.teaser em { .chapter li a.active { color: var(--sidebar-active); - background-color: rgba(8, 76, 207, 0.1); + background-color: var(--sidebar-active-bg); } .chapter li > a.toggle { diff --git a/docs/theme/css/general.css b/docs/theme/css/general.css index fc4e28b5c0..b08175e8d5 100644 --- a/docs/theme/css/general.css +++ b/docs/theme/css/general.css @@ -113,7 +113,7 @@ h6:target::before { */ :target { /* Safari does not support logical properties */ - scroll-margin-top: calc(var(--menu-bar-height) + 0.5em); + scroll-margin-top: calc(var(--menu-bar-height) + 2rem); } .page { @@ -141,7 +141,7 @@ h6:target::before { .content { overflow-y: auto; - padding: 24px 4px 48px 4px; + padding: 48px 4px; } .content main { margin-inline-start: auto; diff --git a/docs/theme/css/variables.css b/docs/theme/css/variables.css index 3342ce59ef..15dc370e71 100644 --- a/docs/theme/css/variables.css +++ b/docs/theme/css/variables.css @@ -6,7 +6,7 @@ --sidebar-resize-indicator-space: 2px; --page-padding: 15px; --content-max-width: 750px; - --menu-bar-height: 50px; + --menu-bar-height: 64px; --font: "IA Writer Quattro S", sans-serif; --title-font: "Agrandir", "Helvetica Neue", Helvetica, Arial, sans-serif; --mono-font: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, @@ -20,7 +20,8 @@ --sidebar-fg: hsl(0, 0%, 0%); --sidebar-non-existant: #aaaaaa; - --sidebar-active: rgb(8, 76, 207); + --sidebar-active: hsl(219, 93%, 42%); + --sidebar-active-bg: hsl(219, 93%, 42%, 0.1); --sidebar-spacer: #f4f4f4; --scrollbar: #8f8f8f; diff --git a/docs/theme/index.hbs b/docs/theme/index.hbs index a1cdde6bac..8976b54bd9 100644 --- a/docs/theme/index.hbs +++ b/docs/theme/index.hbs @@ -119,7 +119,6 @@
-
{{> header}} @@ -192,7 +191,12 @@
- {{{ content }}} +
+ +
+ {{{ content }}}
diff --git a/docs/theme/page-toc.css b/docs/theme/page-toc.css new file mode 100644 index 0000000000..dacd61a09b --- /dev/null +++ b/docs/theme/page-toc.css @@ -0,0 +1,79 @@ +@media only screen and (max-width: 1674px) { + .sidetoc { + display: none; + } +} + +@media only screen and (min-width: 1675px) { + main { + position: relative; + } + .sidetoc { + margin-left: auto; + margin-right: auto; + left: calc(100% + (var(--content-max-width)) / 3 - 160px); + position: absolute; + } + .pagetoc { + position: fixed; + top: 64px; + width: 220px; + height: calc(100vh - var(--menu-bar-height) - 0.67em * 4); + padding-top: 80px; + margin-right: 16px; + padding-bottom: 40px; + overflow: auto; + } + .pagetoc > :last-child { + margin-bottom: 64px; + } + .pagetoc a { + width: fit-content; + font-size: 1.4rem; + border-left: 1px solid var(--sidebar-bg); + color: var(--fg) !important; + display: block; + padding: 2px; + margin: 8px 0 8px 12px; + text-align: left; + text-decoration: underline; + text-decoration-color: hsl(0, 0%, 0%, 0.1); + } + .pagetoc a:hover { + text-decoration-color: hsl(0, 0%, 0%, 0.5); + } + .pagetoc a.active { + background-color: var(--sidebar-active-bg); + color: var(--sidebar-active) !important; + text-decoration-color: hsl(219, 93%, 42%, 0.1); + } + .pagetoc a.active:hover { + text-decoration-color: hsl(219, 93%, 42%, 0.8); + } + .pagetoc .active { + background: var(--sidebar-bg); + color: var(--sidebar-fg); + } + .pagetoc .pagetoc-H1 { + display: none; + } + .pagetoc .pagetoc-H3 { + margin-left: 24px; + } + .pagetoc .pagetoc-H4 { + margin-left: 42px; + } + .pagetoc .pagetoc-H5 { + display: none; + } + .pagetoc .pagetoc-H6 { + display: none; + } + .toc-title { + margin: 0; + margin-bottom: 12px; + padding-left: 12px; + font-size: 1.4rem; + color: #000; + } +} diff --git a/docs/theme/page-toc.js b/docs/theme/page-toc.js new file mode 100644 index 0000000000..62af2e7dc4 --- /dev/null +++ b/docs/theme/page-toc.js @@ -0,0 +1,73 @@ +let scrollTimeout; + +const listenActive = () => { + const elems = document.querySelector(".pagetoc").children; + [...elems].forEach((el) => { + el.addEventListener("click", (event) => { + clearTimeout(scrollTimeout); + [...elems].forEach((el) => el.classList.remove("active")); + el.classList.add("active"); + // Prevent scroll updates for a short period + scrollTimeout = setTimeout(() => { + scrollTimeout = null; + }, 100); // Adjust timing as needed + }); + }); +}; + +const getPagetoc = () => + document.querySelector(".pagetoc") || autoCreatePagetoc(); + +const autoCreatePagetoc = () => { + const main = document.querySelector("#content > main"); + const content = Object.assign(document.createElement("div"), { + className: "content-wrap", + }); + content.append(...main.childNodes); + main.prepend(content); + main.insertAdjacentHTML( + "afterbegin", + '
', + ); + return document.querySelector(".pagetoc"); +}; +const updateFunction = () => { + if (scrollTimeout) return; // Skip updates if within the cooldown period from a click + const headers = [...document.getElementsByClassName("header")]; + const scrolledY = window.scrollY; + let lastHeader = null; + + // Find the last header that is above the current scroll position + for (let i = headers.length - 1; i >= 0; i--) { + if (scrolledY >= headers[i].offsetTop) { + lastHeader = headers[i]; + break; + } + } + + const pagetocLinks = [...document.querySelector(".pagetoc").children]; + pagetocLinks.forEach((link) => link.classList.remove("active")); + + if (lastHeader) { + const activeLink = pagetocLinks.find( + (link) => lastHeader.href === link.href, + ); + if (activeLink) activeLink.classList.add("active"); + } +}; + +window.addEventListener("load", () => { + const pagetoc = getPagetoc(); + const headers = [...document.getElementsByClassName("header")]; + headers.forEach((header) => { + const link = Object.assign(document.createElement("a"), { + textContent: header.text, + href: header.href, + className: `pagetoc-${header.parentElement.tagName}`, + }); + pagetoc.appendChild(link); + }); + updateFunction(); + listenActive(); + window.addEventListener("scroll", updateFunction); +});