diff --git a/package-lock.json b/package-lock.json index ff6ce5c59..de09d596e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50,6 +50,7 @@ "unist-util-visit-children": "^3.0.0" }, "devDependencies": { + "@types/html-escaper": "^3.0.0", "@types/mdast": "^4.0.0", "@types/node": "^20.5.8", "@types/react": "^18.2.21", @@ -1903,6 +1904,12 @@ "hoist-non-react-statics": "^3.3.0" } }, + "node_modules/@types/html-escaper": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/html-escaper/-/html-escaper-3.0.0.tgz", + "integrity": "sha512-OcJcvP3Yk8mjYwf/IdXZtTE1tb/u0WF0qa29ER07ZHCYUBZXSN29Z1mBS+/96+kNMGTFUAbSz9X+pHmHpZrTCw==", + "dev": true + }, "node_modules/@types/json5": { "version": "0.0.30", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.30.tgz", diff --git a/package.json b/package.json index c8eecc092..105859e92 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "nanostores": "^0.9.3", "node-html-parser": "^6.1.6", "parse-numeric-range": "^1.3.0", + "playwright": "^1.37.1", "preact": "^10.17.1", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -49,10 +50,10 @@ "unified": "~10.1.2", "unist-util-remove": "^4.0.0", "unist-util-visit": "^5.0.0", - "unist-util-visit-children": "^3.0.0", - "playwright": "^1.37.1" + "unist-util-visit-children": "^3.0.0" }, "devDependencies": { + "@types/html-escaper": "^3.0.0", "@types/mdast": "^4.0.0", "@types/node": "^20.5.8", "@types/react": "^18.2.21", diff --git a/src/components/RightSidebar/TableOfContents.tsx b/src/components/RightSidebar/TableOfContents.tsx index 962d64ec2..94916dd8c 100644 --- a/src/components/RightSidebar/TableOfContents.tsx +++ b/src/components/RightSidebar/TableOfContents.tsx @@ -1,93 +1,98 @@ -import type { MarkdownHeading } from 'astro'; -import type { FunctionalComponent } from 'preact'; -import { unescape } from 'html-escaper'; -import { useState, useEffect, useRef } from 'preact/hooks'; +import type { MarkdownHeading } from "astro"; +import type { FunctionalComponent } from "preact"; +import { unescape } from "html-escaper"; +import { useState, useEffect, useRef } from "preact/hooks"; type ItemOffsets = { - id: string; - topOffset: number; + id: string; + topOffset: number; }; const TableOfContents: FunctionalComponent<{ headings: MarkdownHeading[] }> = ({ - headings = [], + headings = [], }) => { - const toc = useRef(); - const onThisPageID = 'on-this-page-heading'; - const itemOffsets = useRef([]); - const [currentID, setCurrentID] = useState('overview'); - useEffect(() => { - const getItemOffsets = () => { - const titles = document.querySelectorAll('article :is(h1, h2, h3, h4)'); - itemOffsets.current = Array.from(titles).map((title) => ({ - id: title.id, - topOffset: title.getBoundingClientRect().top + window.scrollY, - })); - }; + const toc = useRef(null); + const onThisPageID = "on-this-page-heading"; + const itemOffsets = useRef([]); + const [currentID, setCurrentID] = useState("overview"); + useEffect(() => { + const getItemOffsets = () => { + const titles = document.querySelectorAll("article :is(h1, h2, h3, h4)"); + itemOffsets.current = Array.from(titles).map((title) => ({ + id: title.id, + topOffset: title.getBoundingClientRect().top + window.scrollY, + })); + }; - getItemOffsets(); - window.addEventListener('resize', getItemOffsets); + getItemOffsets(); + window.addEventListener("resize", getItemOffsets); - return () => { - window.removeEventListener('resize', getItemOffsets); - }; - }, []); + return () => { + window.removeEventListener("resize", getItemOffsets); + }; + }, []); - useEffect(() => { - if (!toc.current) return; + useEffect(() => { + if (!toc.current) return; - const setCurrent: IntersectionObserverCallback = (entries) => { - for (const entry of entries) { - if (entry.isIntersecting) { - const { id } = entry.target; - if (id === onThisPageID) continue; - setCurrentID(entry.target.id); - break; - } - } - }; + const setCurrent: IntersectionObserverCallback = (entries) => { + for (const entry of entries) { + if (entry.isIntersecting) { + const { id } = entry.target; + if (id === onThisPageID) continue; + setCurrentID(entry.target.id); + break; + } + } + }; - const observerOptions: IntersectionObserverInit = { - // Negative top margin accounts for `scroll-margin`. - // Negative bottom margin means heading needs to be towards top of viewport to trigger intersection. - rootMargin: '-100px 0% -66%', - threshold: 1, - }; + const observerOptions: IntersectionObserverInit = { + // Negative top margin accounts for `scroll-margin`. + // Negative bottom margin means heading needs to be towards top of viewport to trigger intersection. + rootMargin: "-100px 0% -66%", + threshold: 1, + }; - const headingsObserver = new IntersectionObserver(setCurrent, observerOptions); + const headingsObserver = new IntersectionObserver( + setCurrent, + observerOptions + ); - // Observe all the headings in the main page content. - document.querySelectorAll('article :is(h1,h2,h3)').forEach((h) => headingsObserver.observe(h)); + // Observe all the headings in the main page content. + document + .querySelectorAll("article :is(h1,h2,h3)") + .forEach((h) => headingsObserver.observe(h)); - // Stop observing when the component is unmounted. - return () => headingsObserver.disconnect(); - }, [toc.current]); + // Stop observing when the component is unmounted. + return () => headingsObserver.disconnect(); + }, [toc.current]); - const onLinkClick = (e) => { - setCurrentID(e.target.getAttribute('href').replace('#', '')); - }; + const onLinkClick = (e) => { + setCurrentID(e.target.getAttribute("href").replace("#", "")); + }; - return ( - <> -

- On this page -

- - - ); + return ( + <> +

+ On this page +

+ + + ); }; export default TableOfContents;