From 82f303fcfef38c2d1d5a5566a31bfc8e1f1e5848 Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Wed, 19 Mar 2025 00:00:07 +0800 Subject: [PATCH] feat: responsive window size for popup footnotes, closes #538 (#632) --- .../app/reader/components/FoliateViewer.tsx | 2 +- .../app/reader/components/FootnotePopup.tsx | 104 +++++++++++++----- apps/readest-app/src/utils/style.ts | 4 +- packages/foliate-js | 2 +- 4 files changed, 79 insertions(+), 33 deletions(-) diff --git a/apps/readest-app/src/app/reader/components/FoliateViewer.tsx b/apps/readest-app/src/app/reader/components/FoliateViewer.tsx index 3a5c402e..b81870ef 100644 --- a/apps/readest-app/src/app/reader/components/FoliateViewer.tsx +++ b/apps/readest-app/src/app/reader/components/FoliateViewer.tsx @@ -62,7 +62,7 @@ const FoliateViewer: React.FC<{ viewSettings.vertical = writingDir?.vertical || viewSettings.writingMode.includes('vertical') || false; viewSettings.rtl = writingDir?.rtl || viewSettings.writingMode.includes('rl') || false; - setViewSettings(bookKey, viewSettings); + setViewSettings(bookKey, { ...viewSettings }); applyColorScheme(detail.doc, getIsDarkMode()); mountAdditionalFonts(detail.doc); diff --git a/apps/readest-app/src/app/reader/components/FootnotePopup.tsx b/apps/readest-app/src/app/reader/components/FootnotePopup.tsx index 56baab25..ac02d20f 100644 --- a/apps/readest-app/src/app/reader/components/FootnotePopup.tsx +++ b/apps/readest-app/src/app/reader/components/FootnotePopup.tsx @@ -25,11 +25,17 @@ const FootnotePopup: React.FC = ({ bookKey, bookDoc }) => { const [trianglePosition, setTrianglePosition] = useState(); const [popupPosition, setPopupPosition] = useState(); const [showPopup, setShowPopup] = useState(false); - const { getView, getViewSettings } = useReaderStore(); + + const { getView, getProgress, getViewSettings } = useReaderStore(); const view = getView(bookKey); + const progress = getProgress(bookKey)!; const viewSettings = getViewSettings(bookKey)!; const footnoteHandler = new FootnoteHandler(); + const [gridRect, setGridRect] = useState(null); + const [responsiveWidth, setResponsiveWidth] = useState(popupWidth); + const [responsiveHeight, setResponsiveHeight] = useState(popupHeight); + useEffect(() => { const handleBeforeRender = (e: Event) => { const detail = (e as CustomEvent).detail; @@ -48,7 +54,7 @@ const FootnotePopup: React.FC = ({ bookKey, bookDoc }) => { const { renderer } = view; renderer.setAttribute('flow', 'scrolled'); renderer.setAttribute('margin', '0px'); - renderer.setAttribute('gap', '5%'); + renderer.setAttribute('gap', '0%'); const viewSettings = getViewSettings(bookKey)!; const themeCode = getThemeCode(); const popupTheme = { ...themeCode }; @@ -64,8 +70,17 @@ const FootnotePopup: React.FC = ({ bookKey, bookDoc }) => { const handleRender = (e: Event) => { const detail = (e as CustomEvent).detail; - console.log('render footnote', detail); - setShowPopup(true); + // console.log('render footnote', detail); + const { view } = detail; + view.addEventListener('relocate', () => { + const { renderer } = view as FoliateView; + if (viewSettings.vertical) { + setResponsiveWidth(Math.min(renderer.viewSize, window.innerWidth / 2)); + } else { + setResponsiveHeight(Math.min(renderer.viewSize, window.innerHeight / 2)); + } + setShowPopup(true); + }); }; footnoteHandler.addEventListener('before-render', handleBeforeRender); @@ -77,23 +92,44 @@ const FootnotePopup: React.FC = ({ bookKey, bookDoc }) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [view]); + useEffect(() => { + handleDismissPopup(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [progress]); + + useEffect(() => { + if (viewSettings.vertical) { + setResponsiveWidth(popupHeight); + setResponsiveHeight(Math.max(360, window.innerHeight / 4)); + } else { + setResponsiveWidth(Math.max(360, window.innerWidth / 4)); + setResponsiveHeight(popupHeight); + } + }, [viewSettings]); + + useEffect(() => { + if (trianglePosition && gridRect) { + const popupPos = getPopupPosition( + trianglePosition, + gridRect, + responsiveWidth, + responsiveHeight, + popupPadding, + ); + setPopupPosition(popupPos); + } + }, [trianglePosition, gridRect, responsiveWidth, responsiveHeight]); + const docLinkHandler = async (event: Event) => { const detail = (event as CustomEvent).detail; - console.log('doc link click', 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, popupPadding, viewSettings.vertical); - const popupPos = getPopupPosition( - triangPos, - rect, - viewSettings.vertical ? popupHeight : popupWidth, - viewSettings.vertical ? popupWidth : popupHeight, - popupPadding, - ); + setGridRect(rect); setTrianglePosition(triangPos); - setPopupPosition(popupPos); footnoteHandler.handle(bookDoc, event)?.catch((err) => { console.warn(err); @@ -110,11 +146,13 @@ const FootnotePopup: React.FC = ({ bookKey, bookDoc }) => { const handleDismissPopup = () => { closePopup(); + setGridRect(null); setPopupPosition(null); setTrianglePosition(null); setShowPopup(false); }; + // Handle custom footnote popup event from iframe event const handleFootnotePopupEvent = (event: CustomEvent) => { const { element, footnote } = event.detail; const gridFrame = document.querySelector(`#gridcell-${bookKey}`); @@ -122,21 +160,30 @@ const FootnotePopup: React.FC = ({ bookKey, bookDoc }) => { const rect = gridFrame.getBoundingClientRect(); const viewSettings = getViewSettings(bookKey)!; const triangPos = getPosition(element, rect, popupPadding, viewSettings.vertical); - const popupPos = getPopupPosition( - triangPos, - rect, - viewSettings.vertical ? popupHeight : popupWidth, - viewSettings.vertical ? popupWidth : popupHeight, - popupPadding, - ); if (footnoteRef.current) { const elem = document.createElement('p'); elem.textContent = footnote; - elem.setAttribute('style', `padding: 16px; hanging-punctuation: allow-end last;`); + elem.setAttribute('style', `padding: 1em; hanging-punctuation: allow-end last;`); + elem.style.visibility = 'hidden'; + if (viewSettings.vertical) { + elem.style.height = `${responsiveHeight}px`; + } else { + elem.style.width = `${responsiveWidth}px`; + } + document.body.appendChild(elem); + const popupSize = elem.getBoundingClientRect(); + if (viewSettings.vertical) { + setResponsiveWidth(Math.min(popupSize.width, window.innerWidth / 2)); + } else { + setResponsiveHeight(Math.min(popupSize.height, window.innerHeight / 2)); + } + document.body.removeChild(elem); + + elem.style.visibility = 'visible'; footnoteRef.current.replaceChildren(elem); - setShowPopup(true); + setGridRect(rect); setTrianglePosition(triangPos); - setPopupPosition(popupPos); + setShowPopup(true); } }; @@ -158,9 +205,6 @@ const FootnotePopup: React.FC = ({ bookKey, bookDoc }) => { } }, [footnoteRef]); - const width = viewSettings.vertical ? popupHeight : popupWidth; - const height = viewSettings.vertical ? popupWidth : popupHeight; - return (
{showPopup && ( @@ -171,8 +215,8 @@ const FootnotePopup: React.FC = ({ bookKey, bookDoc }) => { /> )} = ({ bookKey, bookDoc }) => { className='' ref={footnoteRef} style={{ - width: `${width}px`, - height: `${height}px`, + width: `${responsiveWidth}px`, + height: `${responsiveHeight}px`, }} >
diff --git a/apps/readest-app/src/utils/style.ts b/apps/readest-app/src/utils/style.ts index da61f3a0..bac59ffe 100644 --- a/apps/readest-app/src/utils/style.ts +++ b/apps/readest-app/src/utils/style.ts @@ -254,7 +254,9 @@ export const getFootnoteStyles = () => ` } p, li, blockquote, dd { - text-indent: 0; + padding: 1em !important; + margin: 0 !important; + text-indent: 0 !important; } `; diff --git a/packages/foliate-js b/packages/foliate-js index 9f611d40..71bb30a3 160000 --- a/packages/foliate-js +++ b/packages/foliate-js @@ -1 +1 @@ -Subproject commit 9f611d40845b1de42d3688dc461a2a45cc11d711 +Subproject commit 71bb30a3ebf5c2727f500b06b7d07fe4afd9e883