From 3727064af1689c72d7a2a64812c2b92484fe30f9 Mon Sep 17 00:00:00 2001 From: "tangly1024.com" Date: Thu, 27 Jun 2024 09:57:48 +0800 Subject: [PATCH] =?UTF-8?q?example=E4=B8=BB=E9=A2=98=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- themes/example/components/Catalog.js | 94 ++++++++++++++++++++++++++++ themes/example/components/SideBar.js | 11 ++++ 2 files changed, 105 insertions(+) create mode 100644 themes/example/components/Catalog.js 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 && ( + + )} + {/* 分类 */}