From f822c079c1dfb867934a0f550cafedb2cb489a4a Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 18 Aug 2026 09:45:00 +0700 Subject: [PATCH 1/2] Headers & sub-headers are inconsistently anchored and bolded --- docs/_sass/_main.scss | 16 +++++-- docs/assets/js/main.js | 98 ++++++++++++++++++++++++------------------ 2 files changed, 68 insertions(+), 46 deletions(-) diff --git a/docs/_sass/_main.scss b/docs/_sass/_main.scss index 3b451f020c46..27bb9ed7edf0 100644 --- a/docs/_sass/_main.scss +++ b/docs/_sass/_main.scss @@ -15,10 +15,6 @@ html { line-height: 1; - - @include maxBreakpoint($breakpoint-tablet) { - scroll-padding-top: 80px; - } } table { @@ -67,6 +63,18 @@ body { overflow-x: hidden; } +/* `overflow-x: hidden` above makes `overflow-y` compute to `auto`, so `body` is the element that +scrolls the page and therefore the one whose scroll padding applies to a heading scrolled to by an +anchor. Below the tablet breakpoint the LHN is a fixed 80px header the heading has to clear; above it +the LHN is a left sidebar and the padding is only breathing room. */ +body { + scroll-padding-top: 80px; + + @include breakpoint($breakpoint-tablet) { + scroll-padding-top: 24px; + } +} + hr { background: var(--color-borders); border: none; diff --git a/docs/assets/js/main.js b/docs/assets/js/main.js index c7263e06cb20..63aac16e6464 100644 --- a/docs/assets/js/main.js +++ b/docs/assets/js/main.js @@ -314,11 +314,42 @@ function initSearchPage() { document.getElementById('search-page-clear').addEventListener('click', clearSearchInput); } -const FIXED_HEADER_HEIGHT = 80; +const ARTICLE_TOC_SELECTOR = '.article-toc'; +const TOC_LINK_CLASS = 'link'; +const ACTIVE_TOC_LINK_CLASS = 'selected-article'; + +function getArticleTocLinks() { + return document.querySelectorAll(`${ARTICLE_TOC_SELECTOR} .${TOC_LINK_CLASS}`); +} + +function getHeadingForTocLink(link) { + const headingID = decodeURIComponent(link.href.split('#').pop()); + return headingID ? document.getElementById(headingID) : null; +} + +function setActiveTocLink(activeLink) { + getArticleTocLinks().forEach((link) => { + link.classList.toggle(ACTIVE_TOC_LINK_CLASS, link === activeLink); + }); +} + +// Highlights the entry matching the URL hash, so that landing on a deep link, or coming back to one +// through the browser's history, bolds the same section a click on that entry would have. +function highlightTocLinkForHash() { + if (!window.location.hash) { + return; + } + const headingID = decodeURIComponent(window.location.hash.slice(1)).toLowerCase(); + const link = Array.from(getArticleTocLinks()).find((tocLink) => getHeadingForTocLink(tocLink)?.id.toLowerCase() === headingID); + if (!link) { + return; + } + setActiveTocLink(link); +} const tocbotOptions = { // Where to render the table of contents. - tocSelector: '.article-toc', + tocSelector: ARTICLE_TOC_SELECTOR, // Where to grab the headings to build the table of contents. contentSelector: '', @@ -332,58 +363,38 @@ const tocbotOptions = { listClass: 'lhn-items', // Main class to add to links. - linkClass: 'link', + linkClass: TOC_LINK_CLASS, - // Class to add to active links, - // the link corresponding to the top most heading on the page. - activeLinkClass: 'selected-article', + // The highlight follows what the reader asked for, so it is applied in `onClick` and by + // `highlightTocLinkForHash`. tocbot's own scrollspy would fight that by reassigning the class + // from the scroll position, so its active class is pointed at an unstyled name to disable it. + activeLinkClass: 'tocbot-active-link', - // Headings offset between the headings and the top of the document (requires scrollSmooth enabled) - headingsOffset: FIXED_HEADER_HEIGHT, - scrollSmoothOffset: -FIXED_HEADER_HEIGHT, - scrollSmooth: true, - - // If there is a fixed article scroll container, set to calculate titles' offset - scrollContainer: 'content-area', + // `overflow-x: hidden` on `html, body` in _main.scss makes `overflow-y` compute to `auto`, so + // `body` owns the page's scrollport. tocbot scrolls with `window.scrollTo`, which does nothing + // on a page whose scroller is not the documentElement, so `onClick` uses `scrollIntoView` + // instead: it scrolls whichever ancestor is scrollable and honours its `scroll-padding-top`. + scrollSmooth: false, onClick: (e) => { e.preventDefault(); + + setActiveTocLink(e.target); + + const heading = getHeadingForTocLink(e.target); + if (!heading) { + return; + } + const hashText = e.target.href.split('#').pop(); // Append hashText to the current URL without saving to history const newUrl = `${window.location.pathname}#${hashText}`; history.replaceState(null, '', newUrl); + + heading.scrollIntoView({behavior: 'smooth', block: 'start'}); }, }; -// Define the media query string for the mobile breakpoint -const mobileBreakpoint = window.matchMedia('(max-width: 799px)'); - -// Function to update tocbot options and refresh -function updateTocbotOptions(headingsOffset, scrollSmoothOffset) { - tocbotOptions.headingsOffset = headingsOffset; - tocbotOptions.scrollSmoothOffset = scrollSmoothOffset; - window.tocbot.refresh({ - ...tocbotOptions, - }); -} - -function handleBreakpointChange() { - const isMobile = mobileBreakpoint.matches; - const headingsOffset = isMobile ? FIXED_HEADER_HEIGHT : 0; - const scrollSmoothOffset = isMobile ? -FIXED_HEADER_HEIGHT : 0; - - // Update tocbot options only if there is a change in offsets - if (tocbotOptions.headingsOffset !== headingsOffset || tocbotOptions.scrollSmoothOffset !== scrollSmoothOffset) { - updateTocbotOptions(headingsOffset, scrollSmoothOffset); - } -} - -// Add listener for changes to the media query status using addEventListener -mobileBreakpoint.addEventListener('change', handleBreakpointChange); - -// Initial check -handleBreakpointChange(); - window.addEventListener('DOMContentLoaded', () => { injectFooterCopyright(); @@ -394,6 +405,8 @@ window.addEventListener('DOMContentLoaded', () => { }); } + highlightTocLinkForHash(); + initSearchPage(); document.getElementById('header-button').addEventListener('click', toggleHeaderMenu); @@ -453,4 +466,5 @@ window.addEventListener('hashchange', () => { document.getElementById(lowerCaseHash.slice(1))?.scrollIntoView({ behavior: 'smooth', }); + highlightTocLinkForHash(); }); From 19f04edb04e3ac2eaaf57c2f9e82966f98d12746 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 18 Aug 2026 09:59:08 +0700 Subject: [PATCH 2/2] fix spell check --- docs/assets/js/main.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/assets/js/main.js b/docs/assets/js/main.js index 63aac16e6464..2db62a149f43 100644 --- a/docs/assets/js/main.js +++ b/docs/assets/js/main.js @@ -371,9 +371,9 @@ const tocbotOptions = { activeLinkClass: 'tocbot-active-link', // `overflow-x: hidden` on `html, body` in _main.scss makes `overflow-y` compute to `auto`, so - // `body` owns the page's scrollport. tocbot scrolls with `window.scrollTo`, which does nothing - // on a page whose scroller is not the documentElement, so `onClick` uses `scrollIntoView` - // instead: it scrolls whichever ancestor is scrollable and honours its `scroll-padding-top`. + // `body` is the element that scrolls the page. tocbot scrolls with `window.scrollTo`, which does + // nothing when the documentElement is not the scrolling element, so `onClick` uses + // `scrollIntoView`: it scrolls whichever ancestor scrolls and honours its `scroll-padding-top`. scrollSmooth: false, onClick: (e) => {