feat(annotator): instant highlighting with mouse, touch or pen (#2881)

This commit is contained in:
Huang Xin
2026-01-07 16:13:37 +01:00
committed by GitHub
parent 5ffaac5e67
commit 9614c62360
7 changed files with 407 additions and 31 deletions
@@ -1,7 +1,7 @@
import clsx from 'clsx';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { BookNote } from '@/types/book';
import { BookNote, HighlightColor } from '@/types/book';
import { Point, TextSelection } from '@/utils/sel';
import { useSettingsStore } from '@/store/settingsStore';
import { useAnnotationEditor } from '../../hooks/useAnnotationEditor';
@@ -105,6 +105,7 @@ interface AnnotationRangeEditorProps {
bookKey: string;
annotation: BookNote;
selection: TextSelection;
handleColor: HighlightColor;
getAnnotationText: (range: Range) => Promise<string>;
setSelection: React.Dispatch<React.SetStateAction<TextSelection | null>>;
onStartEdit: () => void;
@@ -114,6 +115,7 @@ const AnnotationRangeEditor: React.FC<AnnotationRangeEditorProps> = ({
bookKey,
annotation,
selection,
handleColor,
getAnnotationText,
setSelection,
onStartEdit,
@@ -123,7 +125,7 @@ const AnnotationRangeEditor: React.FC<AnnotationRangeEditorProps> = ({
useAnnotationEditor({ bookKey, annotation, getAnnotationText, setSelection });
const initializedRef = useRef(false);
const handleColor = getHighlightColorHex(settings, annotation.color) ?? '#FFFF00';
const handleColorHex = getHighlightColorHex(settings, handleColor) ?? '#FFFF00';
const draggingRef = useRef<'start' | 'end' | null>(null);
const startRef = useRef<Point>({ x: 0, y: 0 });
const endRef = useRef<Point>({ x: 0, y: 0 });
@@ -198,7 +200,7 @@ const AnnotationRangeEditor: React.FC<AnnotationRangeEditorProps> = ({
<Handle
position={currentStart}
type='start'
color={handleColor}
color={handleColorHex}
onDragStart={handleStartDragStart}
onDrag={handleStartDrag}
onDragEnd={handleDragEnd}
@@ -206,7 +208,7 @@ const AnnotationRangeEditor: React.FC<AnnotationRangeEditorProps> = ({
<Handle
position={currentEnd}
type='end'
color={handleColor}
color={handleColorHex}
onDragStart={handleEndDragStart}
onDrag={handleEndDrag}
onDragEnd={handleDragEnd}
@@ -198,9 +198,12 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => {
isTextSelected,
handleScroll,
handleTouchStart,
handleTouchMove,
handleTouchEnd,
handlePointerdown,
handlePointerup,
handlePointerDown,
handlePointerMove,
handlePointerCancel,
handlePointerUp,
handleSelectionchange,
handleShowPopup,
handleUpToPopup,
@@ -217,10 +220,12 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => {
const detail = (event as CustomEvent).detail;
const { doc, index } = detail;
const handleTouchmove = () => {
const handleTouchmove = (ev: TouchEvent) => {
// Available on iOS, on Android not fired
// To make the popup not follow the selection while dragging
setShowAnnotPopup(false);
setEditingAnnotation(null);
handleTouchMove(ev);
};
const handleNativeTouch = (event: CustomEvent) => {
@@ -229,7 +234,7 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => {
handleTouchStart();
} else if (ev.type === 'touchend') {
handleTouchEnd();
handlePointerup(doc, index);
handlePointerUp(doc, index);
}
};
@@ -245,13 +250,14 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => {
view?.renderer?.addEventListener('scroll', () => {
repositionPopups();
});
detail.doc?.addEventListener('touchstart', handleTouchStart);
detail.doc?.addEventListener('touchmove', handleTouchmove);
const opts = { passive: false };
detail.doc?.addEventListener('touchstart', handleTouchStart, opts);
detail.doc?.addEventListener('touchmove', handleTouchmove, opts);
detail.doc?.addEventListener('touchend', handleTouchEnd);
detail.doc?.addEventListener('pointerdown', handlePointerdown);
detail.doc?.addEventListener('pointerup', (ev: PointerEvent) =>
handlePointerup(doc, index, ev),
);
detail.doc?.addEventListener('pointerdown', handlePointerDown.bind(null, doc, index), opts);
detail.doc?.addEventListener('pointermove', handlePointerMove.bind(null, doc, index), opts);
detail.doc?.addEventListener('pointercancel', handlePointerCancel.bind(null, doc, index));
detail.doc?.addEventListener('pointerup', handlePointerUp.bind(null, doc, index));
detail.doc?.addEventListener('selectionchange', () => handleSelectionchange(doc));
// For PDF selections, enable right-click context menu to directly open translator popup.
@@ -935,6 +941,7 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => {
bookKey={bookKey}
annotation={editingAnnotation}
selection={selection}
handleColor={selectedColor}
getAnnotationText={getAnnotationText}
setSelection={setSelection}
onStartEdit={handleStartEditAnnotation}