forked from akai/readest
refactor(annotatot): more cleaner text selector on Android (#2867)
This commit is contained in:
@@ -7,7 +7,7 @@ import { useEnv } from '@/context/EnvContext';
|
||||
import { BookNote, BooknoteGroup, HighlightColor, HighlightStyle } from '@/types/book';
|
||||
import { NOTE_PREFIX } from '@/types/view';
|
||||
import { NativeTouchEventType } from '@/types/system';
|
||||
import { getOSPlatform, uniqueId } from '@/utils/misc';
|
||||
import { getLocale, getOSPlatform, uniqueId } from '@/utils/misc';
|
||||
import { useBookDataStore } from '@/store/bookDataStore';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
@@ -18,13 +18,16 @@ import { useDeviceControlStore } from '@/store/deviceStore';
|
||||
import { useFoliateEvents } from '../../hooks/useFoliateEvents';
|
||||
import { useNotesSync } from '../../hooks/useNotesSync';
|
||||
import { useTextSelector } from '../../hooks/useTextSelector';
|
||||
import { getPopupPosition, getPosition, Position, TextSelection } from '@/utils/sel';
|
||||
import { Position, TextSelection } from '@/utils/sel';
|
||||
import { getPopupPosition, getPosition, getTextFromRange } from '@/utils/sel';
|
||||
import { eventDispatcher } from '@/utils/event';
|
||||
import { findTocItemBS } from '@/utils/toc';
|
||||
import { throttle } from '@/utils/throttle';
|
||||
import { runSimpleCC } from '@/utils/simplecc';
|
||||
import { getWordCount } from '@/utils/word';
|
||||
import { isCfiInLocation } from '@/utils/cfi';
|
||||
import { TransformContext } from '@/services/transformers/types';
|
||||
import { transformContent } from '@/services/transformService';
|
||||
import { getHighlightColorHex } from '../../utils/annotatorUtil';
|
||||
import { annotationToolButtons } from './AnnotationTools';
|
||||
import AnnotationPopup from './AnnotationPopup';
|
||||
@@ -51,6 +54,7 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
const bookData = getBookData(bookKey)!;
|
||||
const view = getView(bookKey);
|
||||
const viewSettings = getViewSettings(bookKey)!;
|
||||
const primaryLang = bookData.book?.primaryLanguage || 'en';
|
||||
|
||||
const containerRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -159,6 +163,20 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
[],
|
||||
);
|
||||
|
||||
const transformCtx: TransformContext = {
|
||||
bookKey,
|
||||
viewSettings: getViewSettings(bookKey)!,
|
||||
userLocale: getLocale(),
|
||||
content: '',
|
||||
transformers: ['punctuation'],
|
||||
reversePunctuationTransform: true,
|
||||
};
|
||||
|
||||
const getAnnotationText = async (range: Range) => {
|
||||
transformCtx['content'] = getTextFromRange(range, primaryLang.startsWith('ja') ? ['rt'] : []);
|
||||
return await transformContent(transformCtx);
|
||||
};
|
||||
|
||||
const {
|
||||
isTextSelected,
|
||||
handleScroll,
|
||||
@@ -170,7 +188,7 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
handleShowPopup,
|
||||
handleUpToPopup,
|
||||
handleContextmenu,
|
||||
} = useTextSelector(bookKey, setSelection, handleDismissPopup);
|
||||
} = useTextSelector(bookKey, setSelection, getAnnotationText, handleDismissPopup);
|
||||
|
||||
const handleDismissPopupAndSelection = () => {
|
||||
handleDismissPopup();
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useBookDataStore } from '@/store/bookDataStore';
|
||||
import { getLocale, getOSPlatform } from '@/utils/misc';
|
||||
import { getOSPlatform } from '@/utils/misc';
|
||||
import { eventDispatcher } from '@/utils/event';
|
||||
import { getTextFromRange, TextSelection } from '@/utils/sel';
|
||||
import { transformContent } from '@/services/transformService';
|
||||
import { TransformContext } from '@/services/transformers/types';
|
||||
import { isPointerInsideSelection, TextSelection } from '@/utils/sel';
|
||||
|
||||
export const useTextSelector = (
|
||||
bookKey: string,
|
||||
setSelection: React.Dispatch<React.SetStateAction<TextSelection | null>>,
|
||||
getAnnotationText: (range: Range) => Promise<string>,
|
||||
handleDismissPopup: () => void,
|
||||
) => {
|
||||
const { appService } = useEnv();
|
||||
const { getBookData } = useBookDataStore();
|
||||
const { getView, getViewSettings } = useReaderStore();
|
||||
const view = getView(bookKey);
|
||||
const bookData = getBookData(bookKey)!;
|
||||
const primaryLang = bookData.book?.primaryLanguage || 'en';
|
||||
const osPlatform = getOSPlatform();
|
||||
|
||||
const isPopuped = useRef(false);
|
||||
@@ -32,18 +27,6 @@ export const useTextSelector = (
|
||||
return sel && sel.toString().trim().length > 0 && sel.rangeCount > 0;
|
||||
};
|
||||
|
||||
const transformCtx: TransformContext = {
|
||||
bookKey,
|
||||
viewSettings: getViewSettings(bookKey)!,
|
||||
userLocale: getLocale(),
|
||||
content: '',
|
||||
transformers: ['punctuation'],
|
||||
reversePunctuationTransform: true,
|
||||
};
|
||||
const getAnnotationText = async (range: Range) => {
|
||||
transformCtx['content'] = getTextFromRange(range, primaryLang.startsWith('ja') ? ['rt'] : []);
|
||||
return await transformContent(transformCtx);
|
||||
};
|
||||
const makeSelection = async (sel: Selection, index: number, rebuildRange = false) => {
|
||||
isTextSelected.current = true;
|
||||
isUpToPopup.current = true;
|
||||
@@ -80,24 +63,7 @@ export const useTextSelector = (
|
||||
}, 30);
|
||||
}, 30);
|
||||
};
|
||||
const isPointerInsideSelection = (selection: Selection, ev: PointerEvent) => {
|
||||
if (selection.rangeCount === 0) return false;
|
||||
const range = selection.getRangeAt(0);
|
||||
const rects = range.getClientRects();
|
||||
const padding = 80;
|
||||
for (let i = 0; i < rects.length; i++) {
|
||||
const rect = rects[i]!;
|
||||
if (
|
||||
ev.clientX >= rect.left - padding &&
|
||||
ev.clientX <= rect.right + padding &&
|
||||
ev.clientY >= rect.top - padding &&
|
||||
ev.clientY <= rect.bottom + padding
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const handlePointerdown = (e: PointerEvent) => {
|
||||
lastPointerType.current = e.pointerType;
|
||||
};
|
||||
@@ -129,19 +95,10 @@ export const useTextSelector = (
|
||||
|
||||
const sel = doc.getSelection() as Selection;
|
||||
if (isValidSelection(sel)) {
|
||||
isTextSelected.current = true;
|
||||
isUpToPopup.current = true;
|
||||
if (!selectionPosition.current) {
|
||||
selectionPosition.current = view?.renderer?.start || null;
|
||||
}
|
||||
} else {
|
||||
if (!isUpToPopup.current) {
|
||||
handleDismissPopup();
|
||||
isTextSelected.current = false;
|
||||
}
|
||||
if (isPopuped.current) {
|
||||
isUpToPopup.current = false;
|
||||
}
|
||||
selectionPosition.current = null;
|
||||
}
|
||||
};
|
||||
@@ -161,15 +118,12 @@ export const useTextSelector = (
|
||||
};
|
||||
|
||||
const handleShowPopup = (showPopup: boolean) => {
|
||||
setTimeout(
|
||||
() => {
|
||||
if (showPopup && !isPopuped.current) {
|
||||
isUpToPopup.current = false;
|
||||
}
|
||||
isPopuped.current = showPopup;
|
||||
},
|
||||
['android', 'ios'].includes(osPlatform) || appService?.isIOSApp ? 0 : 500,
|
||||
);
|
||||
setTimeout(() => {
|
||||
if (showPopup && !isPopuped.current) {
|
||||
isUpToPopup.current = false;
|
||||
}
|
||||
isPopuped.current = showPopup;
|
||||
}, 500);
|
||||
};
|
||||
|
||||
const handleUpToPopup = () => {
|
||||
|
||||
@@ -74,6 +74,25 @@ const constrainPointWithinRect = (point: Point, rect: Rect, padding: number) =>
|
||||
};
|
||||
};
|
||||
|
||||
export const isPointerInsideSelection = (selection: Selection, ev: PointerEvent) => {
|
||||
if (selection.rangeCount === 0) return false;
|
||||
const range = selection.getRangeAt(0);
|
||||
const rects = range.getClientRects();
|
||||
const padding = 80;
|
||||
for (let i = 0; i < rects.length; i++) {
|
||||
const rect = rects[i]!;
|
||||
if (
|
||||
ev.clientX >= rect.left - padding &&
|
||||
ev.clientX <= rect.right + padding &&
|
||||
ev.clientY >= rect.top - padding &&
|
||||
ev.clientY <= rect.bottom + padding
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const getPosition = (
|
||||
targetElement: Range | Element | TextSelection,
|
||||
rect: Rect,
|
||||
|
||||
@@ -245,6 +245,8 @@ const getLayoutStyles = (
|
||||
${vertical ? 'font-feature-settings: "vrt2" 1, "vert" 1; text-orientation: upright;' : ''}
|
||||
text-align: var(--default-text-align);
|
||||
max-height: unset;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: text;
|
||||
}
|
||||
body {
|
||||
overflow: unset;
|
||||
|
||||
Reference in New Issue
Block a user