import React, { useMemo, useContext, useEffect, useState } from 'react' import { Anchor } from 'antd' import type { AnchorProps } from 'antd' import { useThemeCtx } from '..' import s from './index.module.less' import { Anchor_Scroll_Offset, isSSR } from '../utils' import { LayoutContext } from './ctx' interface Props {} const OutLine: React.FC = (props) => { const { loadedData, loadState } = useThemeCtx() const pageData = loadedData[loadState.routePath] const [outline, setOutline] = useState( () => pageData?.outlineInfo?.outline ) useEffect(() => { const outlineData = pageData?.outlineInfo?.outline setOutline(outlineData) if (import.meta.hot) { // subscribe to outline info hmr update const unsub = pageData?.outlineInfo?.onUpdate?.((newMod: any) => { const cur = newMod?.outline setOutline(cur) }) return () => { unsub?.() } } }, [pageData]) const data = useMemo(() => { // should not render OutLine if there is only one heading if (!Array.isArray(outline) || outline.length < 2) return null return buildTree(outline) }, [outline]) const layoutCtxVal = useContext(LayoutContext) const isSmallScreen = !layoutCtxVal.screenWidth?.md if (!data) return null if (isSSR || isSmallScreen) { // keep width place holder // don't render Anchor during ssr because it is not supported well return
} const onClickAnchor: AnchorProps['onClick'] = (e, { title, href }) => { // preventDefault to prevent browser scroll to the heading // let antd Anchor handle the scrolling e.preventDefault() if (__HASH_ROUTER__) { history.pushState(null, '', `#${loadState.routePath}${href}`) } else { history.pushState(null, '', href) } } return (
) } function renderAnchorLinks(data: OutlineTreeItem[]): AnchorProps['items'] { return data.map(({ id, text, children }) => { return { key: id, href: `#${id}`, title: text || id, children: renderAnchorLinks(children), } }) } export default OutLine declare global { const __HASH_ROUTER__: boolean } function buildTree(data: OutlineItem[]) { let nextDataIndex = 0 const rootResult: OutlineTreeItem[] = [] // ensure all items in data is put in rootResult while (nextDataIndex < data.length) { put(rootResult) } return rootResult function put(built: OutlineTreeItem[]) { while (true) { const nextData = data[nextDataIndex] if (!nextData) return const lastBuilt = built[built.length - 1] if (!lastBuilt || nextData.depth === lastBuilt.depth) { built.push({ ...nextData, children: [] }) nextDataIndex++ } else if (nextData.depth > lastBuilt.depth) { lastBuilt.children.push({ ...nextData, children: [] }) nextDataIndex++ // recursive put(lastBuilt.children) } else if (built === rootResult) { // nextData.depth < lastBuilt.depth // but this is already the outest recursive level built.push({ ...nextData, children: [] }) nextDataIndex++ } else { // nextData.depth < lastBuilt.depth // should return to the outer recursive level return } } } } interface OutlineItem { id: string depth: string text: string } type OutlineTreeItem = OutlineItem & { children: OutlineTreeItem[] }