This commit is contained in:
@@ -11,6 +11,7 @@ import { BookSearchConfig, BookSearchResult } from '@/types/book';
|
||||
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
|
||||
import { debounce } from '@/utils/debounce';
|
||||
import { isCJKStr } from '@/utils/lang';
|
||||
import { createRejecttFilter } from '@/utils/node';
|
||||
import Dropdown from '@/components/Dropdown';
|
||||
import SearchOptions from './SearchOptions';
|
||||
|
||||
@@ -117,19 +118,6 @@ const SearchBar: React.FC<SearchBarProps> = ({
|
||||
return searchTerm.length >= minLength;
|
||||
};
|
||||
|
||||
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 = useCallback(
|
||||
async (term: string) => {
|
||||
console.log('searching for:', term);
|
||||
@@ -139,7 +127,9 @@ const SearchBar: React.FC<SearchBarProps> = ({
|
||||
...searchConfig,
|
||||
index,
|
||||
query: term,
|
||||
acceptNode: createAcceptNode({ withRT: !primaryLang.startsWith('ja') }),
|
||||
acceptNode: createRejecttFilter({
|
||||
tags: primaryLang.startsWith('ja') ? ['rt'] : [],
|
||||
}),
|
||||
});
|
||||
const results: BookSearchResult[] = [];
|
||||
let lastProgressLogTime = 0;
|
||||
|
||||
@@ -212,7 +212,7 @@ const TTSControl: React.FC<TTSControlProps> = ({ bookKey }) => {
|
||||
await ttsController.initViewTTS();
|
||||
const ssml = view.tts?.from(ttsFromRange);
|
||||
if (ssml) {
|
||||
let lang = parseSSMLLang(ssml, primaryLang) || 'en';
|
||||
const lang = parseSSMLLang(ssml, primaryLang) || 'en';
|
||||
setIsPlaying(true);
|
||||
setTtsLang(lang);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { AppService } from '@/types/system';
|
||||
import { parseSSMLMarks } from '@/utils/ssml';
|
||||
import { Overlayer } from 'foliate-js/overlayer.js';
|
||||
import { TTSGranularity, TTSHighlightOptions, TTSMark, TTSVoice } from './types';
|
||||
import { createRejecttFilter } from '@/utils/node';
|
||||
import { WebSpeechClient } from './WebSpeechClient';
|
||||
import { NativeTTSClient } from './NativeTTSClient';
|
||||
import { EdgeTTSClient } from './EdgeTTSClient';
|
||||
@@ -106,7 +107,14 @@ export class TTSController extends EventTarget {
|
||||
granularity = supportedGranularities[0]!;
|
||||
}
|
||||
const highlightOptions: TTSHighlightOptions = { style: 'highlight', color: 'gray' };
|
||||
await this.view.initTTS(granularity, this.#getHighlighter(highlightOptions));
|
||||
await this.view.initTTS(
|
||||
granularity,
|
||||
createRejecttFilter({
|
||||
tags: ['rt', 'sup'],
|
||||
contents: [{ tag: 'a', content: /^\d+$/ }],
|
||||
}),
|
||||
this.#getHighlighter(highlightOptions),
|
||||
);
|
||||
}
|
||||
|
||||
async preloadSSML(ssml: string | undefined) {
|
||||
|
||||
@@ -20,7 +20,11 @@ export interface FoliateView extends HTMLElement {
|
||||
clearSearch: () => void;
|
||||
select: (target: string | number | { fraction: number }) => void;
|
||||
deselect: () => void;
|
||||
initTTS: (granularity?: TTSGranularity, highlight?: (range: Range) => void) => Promise<void>;
|
||||
initTTS: (
|
||||
granularity?: TTSGranularity,
|
||||
nodeFilter?: (node: Node) => number,
|
||||
highlight?: (range: Range) => void,
|
||||
) => Promise<void>;
|
||||
book: BookDoc;
|
||||
tts: TTS | null;
|
||||
language: {
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
export const createRejecttFilter = ({
|
||||
tags = [],
|
||||
classes = [],
|
||||
attributes = [],
|
||||
contents = [],
|
||||
}: {
|
||||
tags?: string[];
|
||||
classes?: string[];
|
||||
attributes?: string[];
|
||||
contents?: { tag: string; content: RegExp }[];
|
||||
}) => {
|
||||
return (node: Node): number => {
|
||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||
const name = (node as Element).tagName.toLowerCase();
|
||||
if (name === 'script' || name === 'style') {
|
||||
return NodeFilter.FILTER_REJECT;
|
||||
}
|
||||
if (tags.includes(name)) {
|
||||
return NodeFilter.FILTER_REJECT;
|
||||
}
|
||||
if (classes.some((cls) => (node as Element).classList.contains(cls))) {
|
||||
return NodeFilter.FILTER_REJECT;
|
||||
}
|
||||
if (attributes.some((attr) => (node as Element).hasAttribute(attr))) {
|
||||
return NodeFilter.FILTER_REJECT;
|
||||
}
|
||||
if (
|
||||
contents.some(({ tag, content }) => {
|
||||
return name === tag && content.test((node as Element).textContent || '');
|
||||
})
|
||||
) {
|
||||
return NodeFilter.FILTER_REJECT;
|
||||
}
|
||||
return NodeFilter.FILTER_SKIP;
|
||||
}
|
||||
return NodeFilter.FILTER_ACCEPT;
|
||||
};
|
||||
};
|
||||
@@ -183,13 +183,13 @@ export const getPopupPosition = (
|
||||
return { point: popupPoint, dir: position.dir } as Position;
|
||||
};
|
||||
|
||||
export const getTextFromRange = (range: Range, rejects: string[] = []): string => {
|
||||
export const getTextFromRange = (range: Range, rejectTags: 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() || '')) {
|
||||
if (rejectTags.includes(parent?.tagName.toLowerCase() || '')) {
|
||||
return NodeFilter.FILTER_REJECT;
|
||||
}
|
||||
return NodeFilter.FILTER_ACCEPT;
|
||||
|
||||
+1
-1
Submodule packages/foliate-js updated: ff36292b7a...97b906075f
Reference in New Issue
Block a user