From ed368e1205ce00cd91a3b0b5cf1337f2706ea0ae Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Wed, 30 Apr 2025 17:36:52 +0800 Subject: [PATCH] fix: skip rt elements in annotation and search for Japanese books, closes #904 (#1004) --- .../reader/components/annotator/Annotator.tsx | 16 ++++++++++--- .../reader/components/sidebar/SearchBar.tsx | 23 ++++++++++++++++++- apps/readest-app/src/types/book.ts | 1 + apps/readest-app/src/utils/sel.ts | 23 +++++++++++++++++++ packages/foliate-js | 2 +- 5 files changed, 60 insertions(+), 5 deletions(-) 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 71085e40..a68f477a 100644 --- a/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx +++ b/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx @@ -22,7 +22,13 @@ import { useTranslation } from '@/hooks/useTranslation'; import { useResponsiveSize } from '@/hooks/useResponsiveSize'; import { useFoliateEvents } from '../../hooks/useFoliateEvents'; import { useNotesSync } from '../../hooks/useNotesSync'; -import { getPopupPosition, getPosition, Position, TextSelection } from '@/utils/sel'; +import { + getPopupPosition, + getPosition, + getTextFromRange, + Position, + TextSelection, +} from '@/utils/sel'; import { eventDispatcher } from '@/utils/event'; import { findTocItemBS } from '@/utils/toc'; import { transformContent } from '@/services/transformService'; @@ -48,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 isShowingPopup = useRef(false); const isTextSelected = useRef(false); @@ -98,6 +105,9 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => { transformers: ['punctuation'], reversePunctuationTransform: true, }; + const getAnnotationText = (range: Range) => { + return getTextFromRange(range, primaryLang.startsWith('ja') ? ['rt'] : []); + }; const makeSelection = async (sel: Selection, rebuildRange = false) => { isTextSelected.current = true; const range = sel.getRangeAt(0); @@ -105,7 +115,7 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => { sel.removeAllRanges(); sel.addRange(range); } - transformCtx['content'] = sel.toString(); + transformCtx['content'] = getAnnotationText(range); setSelection({ key: bookKey, text: await transformContent(transformCtx), range, index }); }; // FIXME: extremely hacky way to dismiss system selection tools on iOS @@ -117,7 +127,7 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => { setTimeout(async () => { if (!isTextSelected.current) return; sel.addRange(range); - transformCtx['content'] = range.toString(); + transformCtx['content'] = getAnnotationText(range); setSelection({ key: bookKey, text: await transformContent(transformCtx), range, index }); }, 40); }, 0); diff --git a/apps/readest-app/src/app/reader/components/sidebar/SearchBar.tsx b/apps/readest-app/src/app/reader/components/sidebar/SearchBar.tsx index f027e74d..bf02782d 100644 --- a/apps/readest-app/src/app/reader/components/sidebar/SearchBar.tsx +++ b/apps/readest-app/src/app/reader/components/sidebar/SearchBar.tsx @@ -32,6 +32,7 @@ const SearchBar: React.FC = ({ const _ = useTranslation(); const { envConfig } = useEnv(); const { settings } = useSettingsStore(); + const { getBookData } = useBookDataStore(); const { getConfig, saveConfig } = useBookDataStore(); const { getView, getProgress } = useReaderStore(); const [searchTerm, setSearchTerm] = useState(term); @@ -39,7 +40,9 @@ const SearchBar: React.FC = ({ const view = getView(bookKey)!; const config = getConfig(bookKey)!; + const bookData = getBookData(bookKey)!; const progress = getProgress(bookKey)!; + const primaryLang = bookData.book?.primaryLanguage || 'en'; const searchConfig = config.searchConfig! as BookSearchConfig; const queuedSearchTerm = useRef(''); @@ -120,12 +123,30 @@ const SearchBar: React.FC = ({ } }; + const createAcceptNode = ({ withRT = true } = {}) => { + return (node: Node): number => { + if (node.nodeType === Node.ELEMENT_NODE) { + const name = (node as Element).tagName.toLowerCase(); + if (name === 'script' || name === 'style' || (!withRT && name === 'rt')) { + return NodeFilter.FILTER_REJECT; + } + return NodeFilter.FILTER_SKIP; + } + return NodeFilter.FILTER_ACCEPT; + }; + }; + const handleSearch = async (term: string) => { console.log('searching for:', term); isSearchPending.current = true; const { section } = progress; const index = searchConfig.scope === 'section' ? section.current : undefined; - const generator = await view.search({ ...searchConfig, query: term, index }); + const generator = await view.search({ + ...searchConfig, + index, + query: term, + acceptNode: createAcceptNode({ withRT: !primaryLang.startsWith('ja') }), + }); const results: BookSearchResult[] = []; for await (const result of generator) { if (typeof result === 'string') { diff --git a/apps/readest-app/src/types/book.ts b/apps/readest-app/src/types/book.ts index 33124ad7..3fc697f1 100644 --- a/apps/readest-app/src/types/book.ts +++ b/apps/readest-app/src/types/book.ts @@ -143,6 +143,7 @@ export interface BookSearchConfig { matchDiacritics: boolean; index?: number; query?: string; + acceptNode?: (node: Node) => number; } export interface SearchExcerpt { diff --git a/apps/readest-app/src/utils/sel.ts b/apps/readest-app/src/utils/sel.ts index 646d8ed9..b90997ad 100644 --- a/apps/readest-app/src/utils/sel.ts +++ b/apps/readest-app/src/utils/sel.ts @@ -159,3 +159,26 @@ export const getPopupPosition = ( return { point: popupPoint, dir: position.dir } as Position; }; + +export const getTextFromRange = (range: Range, rejects: string[] = []): string => { + const clonedRange = range.cloneRange(); + const fragment = clonedRange.cloneContents(); + const walker = document.createTreeWalker(fragment, NodeFilter.SHOW_TEXT, { + acceptNode: (node) => { + const parent = node.parentElement; + if (rejects.includes(parent?.tagName.toLowerCase() || '')) { + return NodeFilter.FILTER_REJECT; + } + return NodeFilter.FILTER_ACCEPT; + }, + }); + + let text = ''; + let node: Text | null; + + while ((node = walker.nextNode() as Text | null)) { + text += node.nodeValue ?? ''; + } + + return text; +}; diff --git a/packages/foliate-js b/packages/foliate-js index 1f545c5e..4078287c 160000 --- a/packages/foliate-js +++ b/packages/foliate-js @@ -1 +1 @@ -Subproject commit 1f545c5ef5a270d31656ddd78b8f6504fb4322d5 +Subproject commit 4078287cae3322b45b34038f37de4a75eeeae9b7