import React, { useEffect, useRef, useState } from 'react'; import { BookDoc } from '@/libs/document'; import { useReaderStore } from '@/store/readerStore'; import { useFoliateEvents } from '../hooks/useFoliateEvents'; import { useTheme } from '@/hooks/useTheme'; import { getStyles } from '@/utils/style'; import { getPopupPosition, getPosition, Position } from '@/utils/sel'; import { FoliateView } from '@/types/view'; import { FootnoteHandler } from 'foliate-js/footnotes.js'; import Popup from '@/components/Popup'; interface FootnotePopupProps { bookKey: string; bookDoc: BookDoc; } const popupWidth = 360; const popupHeight = 88; const popupPadding = 10; const FootnotePopup: React.FC = ({ bookKey, bookDoc }) => { const footnoteRef = useRef(null); const footnoteViewRef = useRef(null); const [trianglePosition, setTrianglePosition] = useState(); const [popupPosition, setPopupPosition] = useState(); const [showPopup, setShowPopup] = useState(false); const { getView, getViewSettings } = useReaderStore(); const { themeCode } = useTheme(); const view = getView(bookKey); const footnoteHandler = new FootnoteHandler(); useEffect(() => { const handleBeforeRender = (e: Event) => { const detail = (e as CustomEvent).detail; const { view } = detail; footnoteViewRef.current = view; footnoteRef.current?.replaceChildren(view); const { renderer } = view; renderer.setAttribute('flow', 'scrolled'); renderer.setAttribute('margin', '0px'); renderer.setAttribute('gap', '5%'); const viewSettings = getViewSettings(bookKey)!; const popupTheme = { ...themeCode }; const popupContainer = document.getElementById('popup-container'); if (popupContainer) { const backgroundColor = getComputedStyle(popupContainer).backgroundColor; popupTheme.bg = backgroundColor; } renderer.setStyles?.(getStyles(viewSettings, popupTheme)); }; const handleRender = (e: Event) => { const detail = (e as CustomEvent).detail; console.log('render footnote', detail); setShowPopup(true); }; footnoteHandler.addEventListener('before-render', handleBeforeRender); footnoteHandler.addEventListener('render', handleRender); return () => { footnoteHandler.removeEventListener('before-render', handleBeforeRender); footnoteHandler.removeEventListener('render', handleRender); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [view, themeCode]); const docLinkHandler = async (event: Event) => { const detail = (event as CustomEvent).detail; console.log('doc link click', detail); const gridFrame = document.querySelector(`#gridcell-${bookKey}`); if (!gridFrame) return; const rect = gridFrame.getBoundingClientRect(); const viewSettings = getViewSettings(bookKey)!; const triangPos = getPosition(detail.a, rect, viewSettings.vertical); const popupPos = getPopupPosition(triangPos, rect, popupWidth, popupHeight, popupPadding); setTrianglePosition(triangPos); setPopupPosition(popupPos); footnoteHandler.handle(bookDoc, event)?.catch((err) => { console.warn(err); const detail = (event as CustomEvent).detail; view?.goTo(detail.href); }); }; const closePopup = () => { const view = footnoteRef.current?.querySelector('foliate-view') as FoliateView; view?.close(); view?.remove(); }; const handleDismissPopup = () => { closePopup(); setPopupPosition(null); setTrianglePosition(null); setShowPopup(false); }; useFoliateEvents(view, { onLinkClick: docLinkHandler, }); useEffect(() => { if (footnoteViewRef.current) { footnoteRef.current?.replaceChildren(footnoteViewRef.current); } }, [footnoteRef]); return (
{showPopup && (
)}
); }; export default FootnotePopup;