diff --git a/themes/example/components/Catalog.js b/themes/example/components/Catalog.js new file mode 100644 index 00000000..161a6cae --- /dev/null +++ b/themes/example/components/Catalog.js @@ -0,0 +1,94 @@ +import throttle from 'lodash.throttle' +import { uuidToId } from 'notion-utils' +import { useCallback, useEffect, useRef, useState } from 'react' + +/** + * 目录导航组件 + * @param toc + * @returns {JSX.Element} + * @constructor + */ +const Catalog = ({ toc }) => { + // 监听滚动事件 + useEffect(() => { + window.addEventListener('scroll', actionSectionScrollSpy) + actionSectionScrollSpy() + return () => { + window.removeEventListener('scroll', actionSectionScrollSpy) + } + }, []) + + // 目录自动滚动 + const tRef = useRef(null) + const tocIds = [] + + // 同步选中目录事件 + const [activeSection, setActiveSection] = useState(null) + const throttleMs = 200 + const actionSectionScrollSpy = useCallback( + throttle(() => { + const sections = document.getElementsByClassName('notion-h') + let prevBBox = null + let currentSectionId = activeSection + for (let i = 0; i < sections.length; ++i) { + const section = sections[i] + if (!section || !(section instanceof Element)) continue + if (!currentSectionId) { + currentSectionId = section.getAttribute('data-id') + } + const bbox = section.getBoundingClientRect() + const prevHeight = prevBBox ? bbox.top - prevBBox.bottom : 0 + const offset = Math.max(150, prevHeight / 4) + // GetBoundingClientRect returns values relative to viewport + if (bbox.top - offset < 0) { + currentSectionId = section.getAttribute('data-id') + prevBBox = bbox + continue + } + // No need to continue loop, if last element has been detected + break + } + setActiveSection(currentSectionId) + const index = tocIds.indexOf(currentSectionId) || 0 + tRef?.current?.scrollTo({ top: 28 * index, behavior: 'smooth' }) + }, throttleMs) + ) + + // 无目录就直接返回空 + if (!toc || toc.length < 1) { + return <> + } + + return ( +
+
+ +
+
+ ) +} + +export default Catalog diff --git a/themes/example/components/SideBar.js b/themes/example/components/SideBar.js index 68fc5738..35c95b1e 100644 --- a/themes/example/components/SideBar.js +++ b/themes/example/components/SideBar.js @@ -5,6 +5,7 @@ import dynamic from 'next/dynamic' import Link from 'next/link' import CONFIG from '../config' import Announcement from './Announcement' +import Catalog from './Catalog' const ExampleRecentComments = dynamic( () => import('./RecentCommentListForExample') ) @@ -32,6 +33,16 @@ export const SideBar = props => { return ( <> + {/* 目录 */} + {post?.toc && post?.toc.length > 2 && ( + + )} + {/* 分类 */}