diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index baf9dd736..bd641ae10 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -13,7 +13,6 @@ const config = { baseUrl: "/", organizationName: "nervosnetwork", projectName: "docs-new", - scripts: ["/js/extra.js", "/js/scrollSidebar.js"], stylesheets: [ { href: "https://cdn.jsdelivr.net/npm/katex@0.13.24/dist/katex.min.css", diff --git a/website/src/theme/Navbar/auto-scroll.ts b/website/src/theme/Navbar/auto-scroll.ts new file mode 100644 index 000000000..bf245749e --- /dev/null +++ b/website/src/theme/Navbar/auto-scroll.ts @@ -0,0 +1,106 @@ +const SIDEBAR_SCROLL_TOP_KEY = "sidebarScrollTop"; + +export function ensureActiveTabInView() { + const sidebar = document.querySelector("nav[aria-label='Docs sidebar']"); + + if (sidebar) { + // Hide the scrollbar for better UX + // @ts-ignore + sidebar.style = ` + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ + &::-webkit-scrollbar { + display: none; /* Chrome, Safari and Opera */ + } + `; + const lastScrollTop = sessionStorage.getItem(SIDEBAR_SCROLL_TOP_KEY); + if (lastScrollTop !== null) { + sidebar.scrollTop = parseInt(lastScrollTop, 10); + } + } + + const activeItems = document.querySelectorAll(".menu__link--active"); + + if (!activeItems || activeItems.length === 0) { + if (sidebar) { + sessionStorage.setItem( + SIDEBAR_SCROLL_TOP_KEY, + sidebar.scrollTop.toString() + ); + } + return; + } + const item = activeItems[activeItems.length - 1]; + + let isItemVisibleAfterRestore = false; + if (sidebar) { + const itemRect = item.getBoundingClientRect(); + const sidebarRect = sidebar.getBoundingClientRect(); + // Check if item is visible within the sidebar's viewport + isItemVisibleAfterRestore = + itemRect.top >= sidebarRect.top && itemRect.bottom <= sidebarRect.bottom; + } else { + const bounding = item.getBoundingClientRect(); + isItemVisibleAfterRestore = + bounding.top >= 0 && + bounding.bottom <= + (window.innerHeight || document.documentElement.clientHeight); + } + + // Not visible after restoring scroll, so scroll into view. + if (!isItemVisibleAfterRestore) { + item.scrollIntoView({ block: "nearest", inline: "nearest" }); + document.body.scrollTop = document.documentElement.scrollTop = 0; + } + + if (sidebar) { + sessionStorage.setItem( + SIDEBAR_SCROLL_TOP_KEY, + sidebar.scrollTop.toString() + ); + // Restore the scrollbar style + // @ts-ignore + sidebar.style = undefined; + } +} + +export function observeDocumentChanges() { + const observer = new MutationObserver((mutationsList) => { + for (const mutation of mutationsList) { + if ( + mutation.type === "childList" || + (mutation.type === "attributes" && mutation.attributeName === "class") + ) { + ensureActiveTabInView(); + } + } + }); + + const targetNode = document.getElementById("__docusaurus"); + if (targetNode) { + const config = { attributes: true, childList: true, subtree: true }; + observer.observe(targetNode, config); + } + return () => { + observer.disconnect(); + }; +} + +export function subscribeSidebarScroll() { + const sidebar = document.querySelector("nav[aria-label='Docs sidebar']"); + if (!sidebar) { + return; + } + + const handleScroll = () => { + sessionStorage.setItem( + SIDEBAR_SCROLL_TOP_KEY, + sidebar.scrollTop.toString() + ); + }; + + sidebar.addEventListener("scroll", handleScroll); + return () => { + sidebar.removeEventListener("scroll", handleScroll); + }; +} diff --git a/website/src/theme/Navbar/index.tsx b/website/src/theme/Navbar/index.tsx index abb62510b..7de419ffb 100644 --- a/website/src/theme/Navbar/index.tsx +++ b/website/src/theme/Navbar/index.tsx @@ -1,8 +1,30 @@ -import React from "react"; +import { useEffect, useRef } from "react"; import NavbarLayout from "@theme/Navbar/Layout"; import NavbarContent from "@theme/Navbar/Content"; +import { useLocation } from "@docusaurus/router"; +import { + ensureActiveTabInView, + observeDocumentChanges, + subscribeSidebarScroll, +} from "./auto-scroll"; export default function Navbar(): JSX.Element { + const isMounted = useRef(false); + const location = useLocation(); + + useEffect(() => { + ensureActiveTabInView(); + + const unsubscribeSidebarScroll = subscribeSidebarScroll(); + const unsubscribeDocumentChanges = observeDocumentChanges(); + + isMounted.current = true; + return () => { + unsubscribeSidebarScroll?.(); + unsubscribeDocumentChanges?.(); + }; + }, [location.pathname]); + return ( diff --git a/website/static/js/extra.js b/website/static/js/extra.js deleted file mode 100644 index c77901aff..000000000 --- a/website/static/js/extra.js +++ /dev/null @@ -1,23 +0,0 @@ -window.addEventListener("DOMContentLoaded", function () { - const oldSiteLink = document.createElement("div"); - oldSiteLink.innerHTML = ``; - document.querySelector("body").prepend(oldSiteLink); - - // Keeps track of search term in GA - document.addEventListener("keydown", function (event) { - const target = event.target; - const isSearchInput = - target.classList.contains("aa-Input") && target.type === "search"; - if (isSearchInput && event.key === "Enter") { - window.gtag && - window.gtag("event", "search", { - event_category: "Site Search", - event_label: target.value, - }); - } - }); -}); diff --git a/website/static/js/scrollSidebar.js b/website/static/js/scrollSidebar.js deleted file mode 100644 index 0af3e9c65..000000000 --- a/website/static/js/scrollSidebar.js +++ /dev/null @@ -1,42 +0,0 @@ -function ensureActiveTabInView() { - // Parent category is considered active too - const activeItems = document.querySelectorAll(".menu__link--active"); - if (!activeItems || activeItems.length < 2) return; - // Pick the actual active tab - const item = activeItems[1]; - const bounding = item.getBoundingClientRect(); - - if ( - bounding.top >= 0 && - bounding.bottom <= - (window.innerHeight || document.documentElement.clientHeight) - ) { - // Already visible. - } else { - // Not visible. - item.scrollIntoView({ block: "center", inline: "nearest" }); - document.body.scrollTop = document.documentElement.scrollTop = 0; - } -} - -// Watch for changes in the entire document -function observeDocumentChanges() { - const observer = new MutationObserver((mutationsList) => { - for (const mutation of mutationsList) { - if (mutation.type === "childList" || mutation.type === "attributes") { - ensureActiveTabInView(); - } - } - }); - - const targetNode = document.getElementById("__docusaurus"); - if (targetNode) { - const config = { attributes: true, childList: true, subtree: true }; - observer.observe(targetNode, config); - } -} - -document.addEventListener("DOMContentLoaded", () => { - ensureActiveTabInView(); - observeDocumentChanges(); -});