Add support of popover footnotes
This commit is contained in:
@@ -12,6 +12,7 @@ import Ribbon from './Ribbon';
|
||||
import SettingsDialog from './settings/SettingsDialog';
|
||||
import Annotator from './annotator/Annotator';
|
||||
import { useBookDataStore } from '@/store/bookDataStore';
|
||||
import FootnotePopup from './FootnotePopup';
|
||||
|
||||
interface BooksGridProps {
|
||||
bookKeys: string[];
|
||||
@@ -60,6 +61,7 @@ const BooksGrid: React.FC<BooksGridProps> = ({ bookKeys, onCloseBook }) => {
|
||||
onSetSettingsDialogOpen={setFontLayoutSettingsDialogOpen}
|
||||
/>
|
||||
<FoliateViewer bookKey={bookKey} bookDoc={bookDoc} config={config} />
|
||||
<FootnotePopup bookKey={bookKey} bookDoc={bookDoc} />
|
||||
{viewSettings.scrolled ? null : (
|
||||
<>
|
||||
<SectionInfo section={sectionLabel} gapLeft={marginGap} />
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
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 './FoliateViewer';
|
||||
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<FootnotePopupProps> = ({ bookKey, bookDoc }) => {
|
||||
const footnoteRef = useRef<HTMLDivElement>(null);
|
||||
const footnoteViewRef = useRef<FoliateView | null>(null);
|
||||
const [trianglePosition, setTrianglePosition] = useState<Position | null>();
|
||||
const [popupPosition, setPopupPosition] = useState<Position | null>();
|
||||
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)!;
|
||||
renderer.setStyles?.(getStyles(viewSettings, themeCode));
|
||||
};
|
||||
|
||||
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]);
|
||||
|
||||
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 triangPos = getPosition(detail.a, rect);
|
||||
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 (
|
||||
<div>
|
||||
{showPopup && (
|
||||
<div
|
||||
className='fixed inset-0'
|
||||
onClick={handleDismissPopup}
|
||||
onContextMenu={handleDismissPopup}
|
||||
/>
|
||||
)}
|
||||
<Popup
|
||||
width={popupWidth}
|
||||
height={popupHeight}
|
||||
position={showPopup ? popupPosition! : undefined}
|
||||
trianglePosition={showPopup ? trianglePosition! : undefined}
|
||||
className='select-text overflow-y-auto'
|
||||
>
|
||||
<div
|
||||
className=''
|
||||
ref={footnoteRef}
|
||||
style={{
|
||||
width: `${popupWidth}px`,
|
||||
height: `${popupHeight}px`,
|
||||
}}
|
||||
></div>
|
||||
</Popup>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FootnotePopup;
|
||||
@@ -120,8 +120,7 @@ const DeepLPopup: React.FC<DeepLPopupProps> = ({
|
||||
width={popupWidth}
|
||||
height={popupHeight}
|
||||
position={position}
|
||||
className='bg-neutral select-text'
|
||||
triangleClassName='text-neutral'
|
||||
className='select-text'
|
||||
>
|
||||
<div className='text-neutral-content relative h-[50%] overflow-y-auto border-b border-neutral-400/75 p-4 font-sans'>
|
||||
<div className='mb-2 flex items-center justify-between'>
|
||||
|
||||
@@ -107,8 +107,7 @@ const WikipediaPopup: React.FC<WikipediaPopupProps> = ({
|
||||
height={popupHeight}
|
||||
position={position}
|
||||
trianglePosition={trianglePosition}
|
||||
className='bg-neutral select-text overflow-y-auto'
|
||||
triangleClassName='text-neutral'
|
||||
className='select-text overflow-y-auto'
|
||||
>
|
||||
<main className='p-2 font-sans'></main>
|
||||
<footer className='hidden data-[state=loaded]:block data-[state=error]:hidden data-[state=loading]:hidden'>
|
||||
|
||||
@@ -163,8 +163,7 @@ const WiktionaryPopup: React.FC<WiktionaryPopupProps> = ({
|
||||
width={popupWidth}
|
||||
height={popupHeight}
|
||||
position={position}
|
||||
className='bg-neutral select-text overflow-y-auto'
|
||||
triangleClassName='text-neutral'
|
||||
className='select-text overflow-y-auto'
|
||||
>
|
||||
<main className='p-4 font-sans' />
|
||||
<footer className='hidden data-[state=loaded]:block data-[state=error]:hidden data-[state=loading]:hidden'>
|
||||
|
||||
Reference in New Issue
Block a user