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);
}
};
useFoliateEvents(getFoliateView(bookKey), { 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;