import React, { useEffect, useRef, useState } from 'react'; import { md5 } from 'js-md5'; import { TOCItem } from '@/libs/document'; import { useReaderStore } from '@/store/readerStore'; import { useFoliateEvents } from '../hooks/useFoliateEvents'; const findParentPath = (toc: TOCItem[], href: string): TOCItem[] => { for (const item of toc) { if (item.href === href) { return [item]; } if (item.subitems) { const path = findParentPath(item.subitems, href); if (path.length) { return [item, ...path]; } } } return []; }; const getHrefMd5 = (href: string) => md5(JSON.stringify(href)); const createExpanderIcon = (isExpanded: boolean) => { return ( ); }; const TOCItemView: React.FC<{ bookKey: string; item: TOCItem; depth: number; setCurrentHref: (href: string) => void; currentHref: string | null; expandedItems: string[]; }> = ({ bookKey, item, depth, setCurrentHref, currentHref, expandedItems }) => { const [isExpanded, setIsExpanded] = useState(expandedItems.includes(item.href || '')); const { getFoliateView } = useReaderStore(); const handleToggleExpand = (event: React.MouseEvent) => { event.preventDefault(); event.stopPropagation(); setIsExpanded((prev) => !prev); }; const handleClickItem = (event: React.MouseEvent) => { event.preventDefault(); if (item.href) { getFoliateView(bookKey)?.goTo(item.href); setCurrentHref(item.href); } }; const isActive = currentHref === item.href; useEffect(() => { setIsExpanded(expandedItems.includes(item.href || '')); }, [expandedItems, item.href]); return (
  • {item.subitems && ( {createExpanderIcon(isExpanded)} )} {item.label} {item.subitems && isExpanded && (
      {item.subitems.map((subitem) => ( ))}
    )}
  • ); }; const TOCView: React.FC<{ bookKey: string; toc: TOCItem[]; currentHref: string | null; }> = ({ bookKey, toc, currentHref: href }) => { const [currentHref, setCurrentHref] = useState(href); const [expandedItems, setExpandedItems] = useState([]); const { getFoliateView } = useReaderStore(); const tocRef = useRef(null); const tocRelocateHandler = (event: Event) => { const detail = (event as CustomEvent).detail; const { tocItem } = detail; if (tocItem?.href) { setCurrentHref(tocItem.href); } }; const foliateView = getFoliateView(bookKey); useFoliateEvents(bookKey, foliateView, { onRelocate: tocRelocateHandler }); useEffect(() => { setCurrentHref(href); }, [href]); const expandParents = (toc: TOCItem[], href: string) => { const parentPath = findParentPath(toc, href).map((item) => item.href); setExpandedItems(parentPath.filter(Boolean) as string[]); }; useEffect(() => { const hrefMd5 = currentHref ? getHrefMd5(currentHref) : ''; const currentItem = tocRef.current?.querySelector(`[data-href="${hrefMd5}"]`); if (currentItem) { const rect = currentItem.getBoundingClientRect(); const isVisible = rect.top >= 0 && rect.bottom <= window.innerHeight; if (!isVisible) { (currentItem as HTMLElement).scrollIntoView({ behavior: 'smooth', block: 'center' }); } (currentItem as HTMLElement).setAttribute('aria-current', 'page'); } if (currentHref) { expandParents(toc, currentHref); } }, [toc, currentHref]); return (
      {toc && toc.map((item) => ( ))}
    ); }; export default TOCView;