import { clsx } from 'clsx' import { For, Show, createSignal, createEffect, on, onMount, onCleanup } from 'solid-js' import { throttle, debounce } from 'throttle-debounce' import { useLocalize } from '../../context/localize' import { DEFAULT_HEADER_OFFSET } from '../../stores/router' import { isDesktop } from '../../utils/media-query' import { Icon } from '../_shared/Icon' import styles from './TableOfContents.module.scss' interface Props { variant: 'article' | 'editor' parentSelector: string body: string } const isInViewport = (el: Element): boolean => { const rect = el.getBoundingClientRect() return rect.top <= DEFAULT_HEADER_OFFSET } const scrollToHeader = (element) => { window.scrollTo({ behavior: 'smooth', top: element.getBoundingClientRect().top - document.body.getBoundingClientRect().top - DEFAULT_HEADER_OFFSET, }) } export const TableOfContents = (props: Props) => { const { t } = useLocalize() const [headings, setHeadings] = createSignal([]) const [areHeadingsLoaded, setAreHeadingsLoaded] = createSignal(false) const [activeHeaderIndex, setActiveHeaderIndex] = createSignal(-1) const [isVisible, setIsVisible] = createSignal(props.variant === 'article') const toggleIsVisible = () => { setIsVisible((visible) => !visible) } setIsVisible(isDesktop()) const updateHeadings = () => { setHeadings( // eslint-disable-next-line unicorn/prefer-spread Array.from(document.querySelector(props.parentSelector).querySelectorAll('h2, h3, h4')), ) setAreHeadingsLoaded(true) } const debouncedUpdateHeadings = debounce(500, updateHeadings) const updateActiveHeader = throttle(50, () => { const newActiveIndex = headings().findLastIndex((heading) => isInViewport(heading)) setActiveHeaderIndex(newActiveIndex) }) createEffect( on( () => props.body, () => debouncedUpdateHeadings(), ), ) onMount(() => { window.addEventListener('scroll', updateActiveHeader) onCleanup(() => window.removeEventListener('scroll', updateActiveHeader)) }) return ( 2 : headings().length > 1) } >

{t('contents')}

    {(h, index) => (
  • )}
) }