From f54cadfb482e68bf66688d7eb056d6a7a19be94c Mon Sep 17 00:00:00 2001 From: ZeroZ_JQ Date: Thu, 17 Apr 2025 20:19:33 +0800 Subject: [PATCH] refactor: optimize TOC and scroll position handling --- web/app/(commonLayout)/datasets/Doc.tsx | 31 ++++++++++++++++++------- web/app/components/develop/doc.tsx | 26 +++++++++++++++------ 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/web/app/(commonLayout)/datasets/Doc.tsx b/web/app/(commonLayout)/datasets/Doc.tsx index d49e50c800..34d1419686 100644 --- a/web/app/(commonLayout)/datasets/Doc.tsx +++ b/web/app/(commonLayout)/datasets/Doc.tsx @@ -1,6 +1,6 @@ 'use client' -import { useEffect, useMemo, useState } from 'react' +import { useEffect, useMemo, useRef, useState } from 'react' import { useContext } from 'use-context-selector' import { useTranslation } from 'react-i18next' import { RiListUnordered } from '@remixicon/react' @@ -46,7 +46,7 @@ const useToc = (apiBaseUrl: string, locale: string) => { } } - const timeoutId = setTimeout(extractTOC, 500) + const timeoutId = setTimeout(extractTOC, 0) return () => clearTimeout(timeoutId) }, [locale, apiBaseUrl]) @@ -55,6 +55,7 @@ const useToc = (apiBaseUrl: string, locale: string) => { const useScrollPosition = (headingElements: HTMLElement[]) => { const [activeIndex, setActiveIndex] = useState(null) + const activeIndexRef = useRef(null) useEffect(() => { const scrollContainer = document.querySelector('.scroll-container') @@ -86,8 +87,10 @@ const useScrollPosition = (headingElements: HTMLElement[]) => { currentActiveIndex = 0 } - if (currentActiveIndex !== activeIndex) + if (currentActiveIndex !== activeIndexRef.current) { + activeIndexRef.current = currentActiveIndex setActiveIndex(currentActiveIndex) + } } const throttledScrollHandler = throttle(handleScroll, 100) @@ -98,27 +101,36 @@ const useScrollPosition = (headingElements: HTMLElement[]) => { scrollContainer.removeEventListener('scroll', throttledScrollHandler) throttledScrollHandler.cancel() } - }, [headingElements, activeIndex]) + }, [headingElements]) return activeIndex } const useResponsiveToc = () => { const [isTocExpanded, setIsTocExpanded] = useState(false) + const userToggled = useRef(false) useEffect(() => { const mediaQuery = window.matchMedia('(min-width: 1280px)') - setIsTocExpanded(mediaQuery.matches) + + if (!userToggled.current) + setIsTocExpanded(mediaQuery.matches) const handleChange = (e: MediaQueryListEvent) => { - setIsTocExpanded(e.matches) + if (!userToggled.current) + setIsTocExpanded(e.matches) } mediaQuery.addEventListener('change', handleChange) return () => mediaQuery.removeEventListener('change', handleChange) }, []) - return { isTocExpanded, setIsTocExpanded } + const setTocExpandedWithTracking = (expanded: boolean) => { + userToggled.current = true + setIsTocExpanded(expanded) + } + + return { isTocExpanded, setIsTocExpanded: setTocExpandedWithTracking } } const Doc = ({ apiBaseUrl }: DocProps) => { @@ -129,7 +141,6 @@ const Doc = ({ apiBaseUrl }: DocProps) => { const activeIndex = useScrollPosition(headingElements) const { theme } = useTheme() - // Handle TOC item click const handleTocClick = (e: React.MouseEvent, item: TocItem) => { e.preventDefault() @@ -203,7 +214,9 @@ const Doc = ({ apiBaseUrl }: DocProps) => { )} -
+
{Template}
diff --git a/web/app/components/develop/doc.tsx b/web/app/components/develop/doc.tsx index 29f26e2256..c1c2cc5951 100644 --- a/web/app/components/develop/doc.tsx +++ b/web/app/components/develop/doc.tsx @@ -1,5 +1,5 @@ 'use client' -import { useCallback, useEffect, useMemo, useState } from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useContext } from 'use-context-selector' import { useTranslation } from 'react-i18next' import { RiListUnordered } from '@remixicon/react' @@ -54,7 +54,7 @@ const useToc = (appDetail: any, locale: string) => { } } - const timeoutId = setTimeout(extractTOC, 500) + const timeoutId = setTimeout(extractTOC, 0) return () => clearTimeout(timeoutId) }, [appDetail, locale]) @@ -63,6 +63,7 @@ const useToc = (appDetail: any, locale: string) => { const useScrollPosition = (headingElements: HTMLElement[]) => { const [activeIndex, setActiveIndex] = useState(null) + const activeIndexRef = useRef(null) useEffect(() => { const scrollContainer = document.querySelector('.overflow-auto') @@ -97,8 +98,10 @@ const useScrollPosition = (headingElements: HTMLElement[]) => { currentActiveIndex = 0 } - if (currentActiveIndex !== activeIndex) + if (currentActiveIndex !== activeIndexRef.current) { + activeIndexRef.current = currentActiveIndex setActiveIndex(currentActiveIndex) + } } const throttledScrollHandler = throttle(handleScroll, 100) @@ -109,27 +112,36 @@ const useScrollPosition = (headingElements: HTMLElement[]) => { scrollContainer.removeEventListener('scroll', throttledScrollHandler) throttledScrollHandler.cancel() } - }, [headingElements, activeIndex]) + }, [headingElements]) return activeIndex } const useResponsiveToc = () => { const [isTocExpanded, setIsTocExpanded] = useState(false) + const userToggled = useRef(false) useEffect(() => { const mediaQuery = window.matchMedia('(min-width: 1280px)') - setIsTocExpanded(mediaQuery.matches) + + if (!userToggled.current) + setIsTocExpanded(mediaQuery.matches) const handleChange = (e: MediaQueryListEvent) => { - setIsTocExpanded(e.matches) + if (!userToggled.current) + setIsTocExpanded(e.matches) } mediaQuery.addEventListener('change', handleChange) return () => mediaQuery.removeEventListener('change', handleChange) }, []) - return { isTocExpanded, setIsTocExpanded } + const setTocExpandedWithTracking = (expanded: boolean) => { + userToggled.current = true + setIsTocExpanded(expanded) + } + + return { isTocExpanded, setIsTocExpanded: setTocExpandedWithTracking } } const Doc = ({ appDetail }: IDocProps) => {