diff --git a/README.md b/README.md index 36352841..bedd8d86 100644 --- a/README.md +++ b/README.md @@ -78,12 +78,12 @@ Stay tuned for continuous improvements and updates! Contributions and suggestion ![Annotations](./data/screenshots/annotations.png) -![Wikipedia](./data/screenshots/wikipedia.png) - ![DeepL](./data/screenshots/deepl.png) ![Footnote](./data/screenshots/footnote_popover.png) +![Wikipedia](./data/screenshots/wikipedia_vertical.png) + ![Dark Mode](./data/screenshots/dark_mode.png) --- diff --git a/apps/readest-app/src/app/reader/components/FoliateViewer.tsx b/apps/readest-app/src/app/reader/components/FoliateViewer.tsx index 1c54856c..22fb53e3 100644 --- a/apps/readest-app/src/app/reader/components/FoliateViewer.tsx +++ b/apps/readest-app/src/app/reader/components/FoliateViewer.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useRef } from 'react'; -import { BookDoc } from '@/libs/document'; +import { BookDoc, getDirection } from '@/libs/document'; import { BookConfig, BookNote, BookSearchConfig, BookSearchResult } from '@/types/book'; import { useReaderStore } from '@/store/readerStore'; import { useParallelViewStore } from '@/store/parallelViewStore'; @@ -76,7 +76,7 @@ const FoliateViewer: React.FC<{ const viewRef = useRef(null); const isViewCreated = useRef(false); const { getView, setView: setFoliateView, setProgress, getViewSettings } = useReaderStore(); - const { hoveredBookKey, setHoveredBookKey } = useReaderStore(); + const { hoveredBookKey, setHoveredBookKey, setViewSettings } = useReaderStore(); const { getParallels } = useParallelViewStore(); const { themeCode } = useTheme(); @@ -120,7 +120,10 @@ const FoliateViewer: React.FC<{ const detail = (event as CustomEvent).detail; console.log('doc loaded:', detail); if (detail.doc) { + const writingDir = viewRef.current?.renderer.setStyles && getDirection(detail.doc); const viewSettings = getViewSettings(bookKey)!; + viewSettings.vertical = writingDir?.vertical || false; + setViewSettings(bookKey, viewSettings); if (viewSettings.scrolled && shouldAutoHideScrollbar) { handleScrollbarAutoHide(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 103d8700..8915440e 100644 --- a/apps/readest-app/src/app/reader/components/FootnotePopup.tsx +++ b/apps/readest-app/src/app/reader/components/FootnotePopup.tsx @@ -65,7 +65,8 @@ const FootnotePopup: React.FC = ({ bookKey, bookDoc }) => { const gridFrame = document.querySelector(`#gridcell-${bookKey}`); if (!gridFrame) return; const rect = gridFrame.getBoundingClientRect(); - const triangPos = getPosition(detail.a, rect); + const viewSettings = getViewSettings(bookKey)!; + const triangPos = getPosition(detail.a, rect, viewSettings.vertical); const popupPos = getPopupPosition(triangPos, rect, popupWidth, popupHeight, popupPadding); setTrianglePosition(triangPos); setPopupPosition(popupPos); diff --git a/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx b/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx index ebe1547b..2b4033e2 100644 --- a/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx +++ b/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx @@ -30,13 +30,14 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => { const { envConfig } = useEnv(); const { settings } = useSettingsStore(); const { getConfig, saveConfig, getBookData, updateBooknotes } = useBookDataStore(); - const { getProgress, getView, getViewsById } = useReaderStore(); + const { getProgress, getView, getViewsById, getViewSettings } = useReaderStore(); const { isNotebookPinned, isNotebookVisible } = useNotebookStore(); const { setNotebookVisible, setNotebookNewAnnotation } = useNotebookStore(); const config = getConfig(bookKey)!; const progress = getProgress(bookKey)!; const bookData = getBookData(bookKey)!; const view = getView(bookKey); + const viewSettings = getViewSettings(bookKey)!; const isShowingPopup = useRef(false); const isTextSelected = useRef(false); @@ -48,6 +49,7 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => { const [trianglePosition, setTrianglePosition] = useState(); const [annotPopupPosition, setAnnotPopupPosition] = useState(); const [dictPopupPosition, setDictPopupPosition] = useState(); + const [translatorPopupPosition, setTranslatorPopupPosition] = useState(); const [toastMessage, setToastMessage] = useState(''); const [highlightOptionsVisible, setHighlightOptionsVisible] = useState(false); @@ -59,7 +61,9 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => { ); const dictPopupWidth = 480; - const dictPopupHeight = 360; + const dictPopupHeight = 300; + const transPopupWidth = 480; + const transPopupHeight = 360; const annotPopupWidth = 280; const annotPopupHeight = 44; const popupPadding = 10; @@ -148,7 +152,7 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => { const gridFrame = document.querySelector(`#gridcell-${bookKey}`); if (!gridFrame) return; const rect = gridFrame.getBoundingClientRect(); - const triangPos = getPosition(selection.range, rect); + const triangPos = getPosition(selection.range, rect, viewSettings.vertical); const annotPopupPos = getPopupPosition( triangPos, rect, @@ -163,13 +167,22 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => { dictPopupHeight, popupPadding, ); + const transPopupPos = getPopupPosition( + triangPos, + rect, + transPopupWidth, + transPopupHeight, + popupPadding, + ); if (triangPos.point.x == 0 || triangPos.point.y == 0) return; setShowAnnotPopup(true); setAnnotPopupPosition(annotPopupPos); setDictPopupPosition(dictPopupPos); + setTranslatorPopupPosition(transPopupPos); setTrianglePosition(triangPos); isShowingPopup.current = true; } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [selection, bookKey]); useEffect(() => { @@ -357,13 +370,13 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => { popupHeight={dictPopupHeight} /> )} - {showDeepLPopup && trianglePosition && dictPopupPosition && ( + {showDeepLPopup && trianglePosition && translatorPopupPosition && ( )} {showAnnotPopup && trianglePosition && annotPopupPosition && ( diff --git a/apps/readest-app/src/app/reader/components/annotator/WikipediaPopup.tsx b/apps/readest-app/src/app/reader/components/annotator/WikipediaPopup.tsx index acce0475..b5cedae7 100644 --- a/apps/readest-app/src/app/reader/components/annotator/WikipediaPopup.tsx +++ b/apps/readest-app/src/app/reader/components/annotator/WikipediaPopup.tsx @@ -96,7 +96,8 @@ const WikipediaPopup: React.FC = ({ } }; - const langCode = typeof lang === 'string' ? lang : lang?.[0]; + const bookLang = typeof lang === 'string' ? lang : lang?.[0]; + const langCode = bookLang ? bookLang.split('-')[0]! : 'en'; fetchSummary(text, langCode); }, [text, lang]); diff --git a/apps/readest-app/src/components/Popup.tsx b/apps/readest-app/src/components/Popup.tsx index ae986bc4..dfac13d9 100644 --- a/apps/readest-app/src/components/Popup.tsx +++ b/apps/readest-app/src/components/Popup.tsx @@ -23,13 +23,46 @@ const Popup = ({
{ + const { defaultView } = doc; + const { writingMode, direction } = defaultView!.getComputedStyle(doc.body); + console.log('direction', writingMode, direction); + const vertical = writingMode === 'vertical-rl' || writingMode === 'vertical-lr'; + const rtl = doc.body.dir === 'rtl' || direction === 'rtl' || doc.documentElement.dir === 'rtl'; + return { vertical, rtl }; +}; diff --git a/apps/readest-app/src/services/constants.ts b/apps/readest-app/src/services/constants.ts index 1d84ffca..f5daf6f2 100644 --- a/apps/readest-app/src/services/constants.ts +++ b/apps/readest-app/src/services/constants.ts @@ -42,6 +42,7 @@ export const DEFAULT_BOOK_LAYOUT: BookLayout = { maxInlineSize: 720, maxBlockSize: 1440, animated: false, + vertical: false, }; export const DEFAULT_BOOK_STYLE: BookStyle = { diff --git a/apps/readest-app/src/types/book.ts b/apps/readest-app/src/types/book.ts index 0683fcfe..0dc4eb7f 100644 --- a/apps/readest-app/src/types/book.ts +++ b/apps/readest-app/src/types/book.ts @@ -42,6 +42,7 @@ export interface BookLayout { maxInlineSize: number; maxBlockSize: number; animated: boolean; + vertical: boolean; } export interface BookStyle { diff --git a/apps/readest-app/src/utils/sel.ts b/apps/readest-app/src/utils/sel.ts index ca690724..dec29c16 100644 --- a/apps/readest-app/src/utils/sel.ts +++ b/apps/readest-app/src/utils/sel.ts @@ -15,7 +15,7 @@ export interface Point { y: number; } -export type PositionDir = 'up' | 'down'; +export type PositionDir = 'up' | 'down' | 'left' | 'right'; export interface Position { point: Point; @@ -64,8 +64,7 @@ const getIframeElement = (nodeElement: Range | Element): HTMLIFrameElement | nul return null; }; -export const getPosition = (target: Range | Element, rect: Rect) => { - // TODO: vertical text +export const getPosition = (target: Range | Element, rect: Rect, isVertical: boolean = false) => { const frameElement = getIframeElement(target); const transform = frameElement ? getComputedStyle(frameElement).transform : ''; const match = transform.match(/matrix\((.+)\)/); @@ -75,6 +74,22 @@ export const getPosition = (target: Range | Element, rect: Rect) => { const rects = Array.from(target.getClientRects()); const first = frameRect(frame, rects[0] as Rect, sx, sy); const last = frameRect(frame, rects.at(-1) as Rect, sx, sy); + + if (isVertical) { + const leftSpace = first.left - rect.left; + const rightSpace = rect.right - first.right; + const dir = leftSpace > rightSpace ? 'left' : 'right'; + const position = { + point: { + x: dir === 'left' ? first.left - rect.left - 6 : first.right - rect.left + 6, + y: (first.top + first.bottom) / 2 - rect.top, + }, + dir, + } as Position; + const inView = pointIsInView(position.point); + return inView ? position : ({ point: { x: 0, y: 0 }, dir } as Position); + } + const start = { point: { x: (first.left + first.right) / 2 - rect.left, y: first.top - rect.top - 12 }, dir: 'up', @@ -98,10 +113,20 @@ export const getPopupPosition = ( popupHeightPx: number, popupPaddingPx: number, ) => { - const popupPoint = { - x: position.point.x - popupWidthPx / 2, - y: position.dir === 'up' ? position.point.y - popupHeightPx : position.point.y + 6, - }; + const popupPoint = { x: 0, y: 0 }; + if (position.dir === 'up') { + popupPoint.x = position.point.x - popupWidthPx / 2; + popupPoint.y = position.point.y - popupHeightPx; + } else if (position.dir === 'down') { + popupPoint.x = position.point.x - popupWidthPx / 2; + popupPoint.y = position.point.y + 6; + } else if (position.dir === 'left') { + popupPoint.x = position.point.x - popupWidthPx; + popupPoint.y = position.point.y - popupHeightPx / 2; + } else if (position.dir === 'right') { + popupPoint.x = position.point.x + 6; + popupPoint.y = position.point.y - popupHeightPx / 2; + } if (popupPoint.x < popupPaddingPx) { popupPoint.x = popupPaddingPx; diff --git a/data/screenshots/wikipedia.png b/data/screenshots/wikipedia.png deleted file mode 100644 index 9e82eaab..00000000 Binary files a/data/screenshots/wikipedia.png and /dev/null differ diff --git a/data/screenshots/wikipedia_vertical.png b/data/screenshots/wikipedia_vertical.png new file mode 100644 index 00000000..0c54e396 Binary files /dev/null and b/data/screenshots/wikipedia_vertical.png differ