forked from akai/readest
Support left / right popover on vertical writing documents
This commit is contained in:
@@ -78,12 +78,12 @@ Stay tuned for continuous improvements and updates! Contributions and suggestion
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
@@ -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<FoliateView | null>(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);
|
||||
}
|
||||
|
||||
@@ -65,7 +65,8 @@ const FootnotePopup: React.FC<FootnotePopupProps> = ({ 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);
|
||||
|
||||
@@ -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<Position>();
|
||||
const [annotPopupPosition, setAnnotPopupPosition] = useState<Position>();
|
||||
const [dictPopupPosition, setDictPopupPosition] = useState<Position>();
|
||||
const [translatorPopupPosition, setTranslatorPopupPosition] = useState<Position>();
|
||||
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 && (
|
||||
<DeepLPopup
|
||||
text={selection?.text as string}
|
||||
position={dictPopupPosition}
|
||||
position={translatorPopupPosition}
|
||||
trianglePosition={trianglePosition}
|
||||
popupWidth={dictPopupWidth}
|
||||
popupHeight={dictPopupHeight}
|
||||
popupWidth={transPopupWidth}
|
||||
popupHeight={transPopupHeight}
|
||||
/>
|
||||
)}
|
||||
{showAnnotPopup && trianglePosition && annotPopupPosition && (
|
||||
|
||||
@@ -96,7 +96,8 @@ const WikipediaPopup: React.FC<WikipediaPopupProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
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]);
|
||||
|
||||
|
||||
@@ -23,13 +23,46 @@ const Popup = ({
|
||||
<div
|
||||
className={`triangle text-base-200 absolute z-10 ${triangleClassName}`}
|
||||
style={{
|
||||
left: `${trianglePosition ? trianglePosition.point.x : -999}px`,
|
||||
top: `${trianglePosition ? trianglePosition.point.y : -999}px`,
|
||||
borderLeft: '6px solid transparent',
|
||||
borderRight: '6px solid transparent',
|
||||
borderBottom: trianglePosition && trianglePosition.dir === 'up' ? 'none' : `6px solid`,
|
||||
borderTop: trianglePosition && trianglePosition.dir === 'up' ? `6px solid` : 'none',
|
||||
transform: 'translateX(-50%)',
|
||||
left:
|
||||
trianglePosition?.dir === 'left'
|
||||
? `${trianglePosition.point.x}px`
|
||||
: trianglePosition?.dir === 'right'
|
||||
? `${trianglePosition.point.x}px`
|
||||
: `${trianglePosition ? trianglePosition.point.x : -999}px`,
|
||||
top:
|
||||
trianglePosition?.dir === 'up'
|
||||
? `${trianglePosition.point.y}px`
|
||||
: trianglePosition?.dir === 'down'
|
||||
? `${trianglePosition.point.y}px`
|
||||
: `${trianglePosition ? trianglePosition.point.y : -999}px`,
|
||||
borderLeft:
|
||||
trianglePosition?.dir === 'right'
|
||||
? 'none'
|
||||
: trianglePosition?.dir === 'left'
|
||||
? `6px solid`
|
||||
: '6px solid transparent',
|
||||
borderRight:
|
||||
trianglePosition?.dir === 'left'
|
||||
? 'none'
|
||||
: trianglePosition?.dir === 'right'
|
||||
? `6px solid`
|
||||
: '6px solid transparent',
|
||||
borderTop:
|
||||
trianglePosition?.dir === 'down'
|
||||
? 'none'
|
||||
: trianglePosition?.dir === 'up'
|
||||
? `6px solid`
|
||||
: '6px solid transparent',
|
||||
borderBottom:
|
||||
trianglePosition?.dir === 'up'
|
||||
? 'none'
|
||||
: trianglePosition?.dir === 'down'
|
||||
? `6px solid`
|
||||
: '6px solid transparent',
|
||||
transform:
|
||||
trianglePosition?.dir === 'left' || trianglePosition?.dir === 'right'
|
||||
? 'translateY(-50%)'
|
||||
: 'translateX(-50%)',
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
|
||||
@@ -166,3 +166,12 @@ export class DocumentLoader {
|
||||
return { book, format } as { book: BookDoc; format: BookFormat };
|
||||
}
|
||||
}
|
||||
|
||||
export const getDirection = (doc: Document) => {
|
||||
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 };
|
||||
};
|
||||
|
||||
@@ -42,6 +42,7 @@ export const DEFAULT_BOOK_LAYOUT: BookLayout = {
|
||||
maxInlineSize: 720,
|
||||
maxBlockSize: 1440,
|
||||
animated: false,
|
||||
vertical: false,
|
||||
};
|
||||
|
||||
export const DEFAULT_BOOK_STYLE: BookStyle = {
|
||||
|
||||
@@ -42,6 +42,7 @@ export interface BookLayout {
|
||||
maxInlineSize: number;
|
||||
maxBlockSize: number;
|
||||
animated: boolean;
|
||||
vertical: boolean;
|
||||
}
|
||||
|
||||
export interface BookStyle {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.7 MiB |
Reference in New Issue
Block a user