|
import { useEffect, useRef, useState } from 'react' |
|
import throttle from 'lodash.throttle' |
|
import { uuidToId } from 'notion-utils' |
|
import { useGlobal } from '@/lib/global' |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const Catalog = ({ toc }) => { |
|
const { locale } = useGlobal() |
|
|
|
|
|
const [activeSection, setActiveSection] = useState(null) |
|
|
|
|
|
useEffect(() => { |
|
const throttleMs = 200 |
|
const actionSectionScrollSpy = 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) |
|
|
|
if (bbox.top - offset < 0) { |
|
currentSectionId = section.getAttribute('data-id') |
|
prevBBox = bbox |
|
continue |
|
} |
|
|
|
break |
|
} |
|
setActiveSection(currentSectionId) |
|
const index = toc?.findIndex(obj => uuidToId(obj.id) === currentSectionId) |
|
tRef?.current?.scrollTo({ top: 28 * index, behavior: 'smooth' }) |
|
}, throttleMs) |
|
|
|
actionSectionScrollSpy() |
|
window.addEventListener('scroll', actionSectionScrollSpy) |
|
return () => { |
|
window.removeEventListener('scroll', actionSectionScrollSpy) |
|
} |
|
}, [toc]) |
|
|
|
|
|
const tRef = useRef(null) |
|
|
|
|
|
if (!toc || toc?.length < 1) { |
|
return <></> |
|
} |
|
|
|
return <div id='catalog'> |
|
<div className='w-full dark:text-gray-300 mb-2'><i className='mr-1 fas fa-stream' />{locale.COMMON.TABLE_OF_CONTENTS}</div> |
|
<div className='h-96'> |
|
<nav ref={tRef} className='h-full overflow-y-auto overscroll-none scroll-hidden font-sans text-black'> |
|
{toc.map((tocItem) => { |
|
const id = uuidToId(tocItem.id) |
|
return ( |
|
<a |
|
key={id} |
|
href={`#${id}`} |
|
className={`notion-table-of-contents-item duration-300 transform font-light dark:text-gray-300 |
|
notion-table-of-contents-item-indent-level-${tocItem.indentLevel} `} |
|
> |
|
<span style={{ display: 'inline-block', marginLeft: tocItem.indentLevel * 16 }} |
|
className={`${activeSection === id && ' font-bold text-red-400 underline'}`} |
|
> |
|
{tocItem.text} |
|
</span> |
|
</a> |
|
) |
|
})} |
|
</nav> |
|
</div> |
|
</div> |
|
} |
|
|
|
export default Catalog |
|
|