Text scanner improvements (#1056)
* Only ignore nodes on non-web pages * Fix issue where options might not be assigned on nested frontends * Refactor default TextScanner options * Add option to enable search only on click * Simplify restore state assignment * Update options context passing * Fix empty title * Use TextScanner to scan content inside of Display * Rename ignoreNodes to excludeSelector(s) * Fix options update incorrectly triggering a re-search * Fix copy throwing an error on the search page * Replace _onSearchQueryUpdated with _search * Use include selector instead of exclude selector
This commit is contained in:
parent
12e5cec99c
commit
068b1eef71
@ -34,8 +34,6 @@ class QueryParser extends EventDispatcher {
|
|||||||
this._queryParserModeSelect = document.querySelector('#query-parser-mode-select');
|
this._queryParserModeSelect = document.querySelector('#query-parser-mode-select');
|
||||||
this._textScanner = new TextScanner({
|
this._textScanner = new TextScanner({
|
||||||
node: this._queryParser,
|
node: this._queryParser,
|
||||||
ignoreElements: () => [],
|
|
||||||
ignorePoint: null,
|
|
||||||
getOptionsContext,
|
getOptionsContext,
|
||||||
documentUtil,
|
documentUtil,
|
||||||
searchTerms: true,
|
searchTerms: true,
|
||||||
|
@ -62,7 +62,7 @@ class DisplaySearch extends Display {
|
|||||||
async prepare() {
|
async prepare() {
|
||||||
await super.prepare();
|
await super.prepare();
|
||||||
await this.updateOptions();
|
await this.updateOptions();
|
||||||
yomichan.on('optionsUpdated', () => this.updateOptions());
|
yomichan.on('optionsUpdated', this._onOptionsUpdated.bind(this));
|
||||||
|
|
||||||
this.on('contentUpdating', this._onContentUpdating.bind(this));
|
this.on('contentUpdating', this._onContentUpdating.bind(this));
|
||||||
this.on('modeChange', this._onModeChange.bind(this));
|
this.on('modeChange', this._onModeChange.bind(this));
|
||||||
@ -126,15 +126,6 @@ class DisplaySearch extends Display {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateOptions() {
|
|
||||||
await super.updateOptions();
|
|
||||||
if (!this._isPrepared) { return; }
|
|
||||||
const query = this._queryInput.value;
|
|
||||||
if (query) {
|
|
||||||
this._onSearchQueryUpdated(query, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
postProcessQuery(query) {
|
postProcessQuery(query) {
|
||||||
if (this._wanakanaEnabled) {
|
if (this._wanakanaEnabled) {
|
||||||
try {
|
try {
|
||||||
@ -148,6 +139,14 @@ class DisplaySearch extends Display {
|
|||||||
|
|
||||||
// Private
|
// Private
|
||||||
|
|
||||||
|
async _onOptionsUpdated() {
|
||||||
|
await this.updateOptions();
|
||||||
|
const query = this._queryInput.value;
|
||||||
|
if (query) {
|
||||||
|
this._search(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_onContentUpdating({type, content, source}) {
|
_onContentUpdating({type, content, source}) {
|
||||||
let animate = false;
|
let animate = false;
|
||||||
let valid = false;
|
let valid = false;
|
||||||
@ -183,12 +182,12 @@ class DisplaySearch extends Display {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopImmediatePropagation();
|
e.stopImmediatePropagation();
|
||||||
this.blurElement(e.currentTarget);
|
this.blurElement(e.currentTarget);
|
||||||
this._search();
|
this._search(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onSearch(e) {
|
_onSearch(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this._search();
|
this._search(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onCopy() {
|
_onCopy() {
|
||||||
@ -197,27 +196,8 @@ class DisplaySearch extends Display {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_onExternalSearchUpdate({text, animate=true}) {
|
_onExternalSearchUpdate({text, animate=true}) {
|
||||||
this._onSearchQueryUpdated(text, animate);
|
this._queryInput.value = text;
|
||||||
}
|
this._search(animate);
|
||||||
|
|
||||||
_onSearchQueryUpdated(query, animate) {
|
|
||||||
const details = {
|
|
||||||
focus: false,
|
|
||||||
history: false,
|
|
||||||
params: {
|
|
||||||
query
|
|
||||||
},
|
|
||||||
state: {
|
|
||||||
focusEntry: 0,
|
|
||||||
sentence: {text: query, offset: 0},
|
|
||||||
url: window.location.href
|
|
||||||
},
|
|
||||||
content: {
|
|
||||||
definitions: null,
|
|
||||||
animate
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.setContent(details);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_onWanakanaEnableChange(e) {
|
_onWanakanaEnableChange(e) {
|
||||||
@ -362,9 +342,25 @@ class DisplaySearch extends Display {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_search() {
|
_search(animate) {
|
||||||
const query = this._queryInput.value;
|
const query = this._queryInput.value;
|
||||||
this._onSearchQueryUpdated(query, true);
|
const details = {
|
||||||
|
focus: false,
|
||||||
|
history: false,
|
||||||
|
params: {
|
||||||
|
query
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
focusEntry: 0,
|
||||||
|
sentence: {text: query, offset: 0},
|
||||||
|
url: window.location.href
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
definitions: null,
|
||||||
|
animate
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.setContent(details);
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateSearchHeight() {
|
_updateSearchHeight() {
|
||||||
|
@ -322,11 +322,13 @@ class Frontend {
|
|||||||
});
|
});
|
||||||
this._updateTextScannerEnabled();
|
this._updateTextScannerEnabled();
|
||||||
|
|
||||||
const ignoreNodes = ['.scan-disable', '.scan-disable *'];
|
if (this._pageType !== 'web') {
|
||||||
if (!this._options.scanning.enableOnPopupExpressions) {
|
const excludeSelectors = ['.scan-disable', '.scan-disable *'];
|
||||||
ignoreNodes.push('.source-text', '.source-text *');
|
if (!scanningOptions.enableOnPopupExpressions) {
|
||||||
|
excludeSelectors.push('.source-text', '.source-text *');
|
||||||
|
}
|
||||||
|
this._textScanner.excludeSelector = excludeSelectors.join(',');
|
||||||
}
|
}
|
||||||
this._textScanner.ignoreNodes = ignoreNodes.join(',');
|
|
||||||
|
|
||||||
this._updateContentScale();
|
this._updateContentScale();
|
||||||
|
|
||||||
@ -527,7 +529,7 @@ class Frontend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_updateTextScannerEnabled() {
|
_updateTextScannerEnabled() {
|
||||||
const enabled = (this._options.general.enable && !this._disabledOverride);
|
const enabled = (this._options !== null && this._options.general.enable && !this._disabledOverride);
|
||||||
this._textScanner.setEnabled(enabled);
|
this._textScanner.setEnabled(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
* PopupFactory
|
* PopupFactory
|
||||||
* QueryParser
|
* QueryParser
|
||||||
* TemplateRendererProxy
|
* TemplateRendererProxy
|
||||||
|
* TextScanner
|
||||||
* WindowScroll
|
* WindowScroll
|
||||||
* api
|
* api
|
||||||
* dynamicLoader
|
* dynamicLoader
|
||||||
@ -48,7 +49,6 @@ class Display extends EventDispatcher {
|
|||||||
});
|
});
|
||||||
this._styleNode = null;
|
this._styleNode = null;
|
||||||
this._eventListeners = new EventListenerCollection();
|
this._eventListeners = new EventListenerCollection();
|
||||||
this._clickScanPrevent = false;
|
|
||||||
this._setContentToken = null;
|
this._setContentToken = null;
|
||||||
this._autoPlayAudioTimer = null;
|
this._autoPlayAudioTimer = null;
|
||||||
this._autoPlayAudioDelay = 400;
|
this._autoPlayAudioDelay = 400;
|
||||||
@ -104,6 +104,7 @@ class Display extends EventDispatcher {
|
|||||||
this._frameEndpoint = (pageType === 'popup' ? new FrameEndpoint() : null);
|
this._frameEndpoint = (pageType === 'popup' ? new FrameEndpoint() : null);
|
||||||
this._browser = null;
|
this._browser = null;
|
||||||
this._copyTextarea = null;
|
this._copyTextarea = null;
|
||||||
|
this._definitionTextScanner = null;
|
||||||
|
|
||||||
this.registerActions([
|
this.registerActions([
|
||||||
['close', () => { this.onEscape(); }],
|
['close', () => { this.onEscape(); }],
|
||||||
@ -311,6 +312,7 @@ class Display extends EventDispatcher {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this._updateNestedFrontend(options);
|
this._updateNestedFrontend(options);
|
||||||
|
this._updateDefinitionTextScanner(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
autoPlayAudio() {
|
autoPlayAudio() {
|
||||||
@ -348,6 +350,7 @@ class Display extends EventDispatcher {
|
|||||||
const url = `${location.protocol}//${location.host}${location.pathname}?${urlSearchParams.toString()}`;
|
const url = `${location.protocol}//${location.host}${location.pathname}?${urlSearchParams.toString()}`;
|
||||||
|
|
||||||
if (history && this._historyHasChanged) {
|
if (history && this._historyHasChanged) {
|
||||||
|
this._updateHistoryState();
|
||||||
this._history.pushState(state, content, url);
|
this._history.pushState(state, content, url);
|
||||||
} else {
|
} else {
|
||||||
this._history.clear();
|
this._history.clear();
|
||||||
@ -648,115 +651,27 @@ class Display extends EventDispatcher {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!this._historyHasState()) { return; }
|
if (!this._historyHasState()) { return; }
|
||||||
|
|
||||||
const link = e.target;
|
const {state: {sentence}} = this._history;
|
||||||
const {state} = this._history;
|
const optionsContext = this.getOptionsContext();
|
||||||
|
const query = e.currentTarget.textContent;
|
||||||
state.focusEntry = this._getClosestDefinitionIndex(link);
|
const definitions = await api.kanjiFind(query, optionsContext);
|
||||||
state.scrollX = this._windowScroll.x;
|
|
||||||
state.scrollY = this._windowScroll.y;
|
|
||||||
this._historyStateUpdate(state);
|
|
||||||
|
|
||||||
const query = link.textContent;
|
|
||||||
const definitions = await api.kanjiFind(query, this.getOptionsContext());
|
|
||||||
const details = {
|
const details = {
|
||||||
focus: false,
|
focus: false,
|
||||||
history: true,
|
history: true,
|
||||||
params: this._createSearchParams('kanji', query, false),
|
params: this._createSearchParams('kanji', query, false),
|
||||||
state: {
|
|
||||||
focusEntry: 0,
|
|
||||||
sentence: state.sentence,
|
|
||||||
optionsContext: state.optionsContext
|
|
||||||
},
|
|
||||||
content: {
|
|
||||||
definitions
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.setContent(details);
|
|
||||||
} catch (error) {
|
|
||||||
this.onError(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_onGlossaryMouseDown(e) {
|
|
||||||
if (DocumentUtil.isMouseButtonPressed(e, 'primary')) {
|
|
||||||
this._clickScanPrevent = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_onGlossaryMouseMove() {
|
|
||||||
this._clickScanPrevent = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
_onGlossaryMouseUp(e) {
|
|
||||||
if (!this._clickScanPrevent && DocumentUtil.isMouseButtonPressed(e, 'primary')) {
|
|
||||||
try {
|
|
||||||
this._onTermLookup(e);
|
|
||||||
} catch (error) {
|
|
||||||
this.onError(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async _onTermLookup(e) {
|
|
||||||
if (!this._historyHasState()) { return; }
|
|
||||||
|
|
||||||
const termLookupResults = await this._termLookup(e);
|
|
||||||
if (!termLookupResults || !this._historyHasState()) { return; }
|
|
||||||
|
|
||||||
const {state} = this._history;
|
|
||||||
const {textSource, definitions} = termLookupResults;
|
|
||||||
|
|
||||||
const scannedElement = e.target;
|
|
||||||
const sentenceExtent = this._options.anki.sentenceExt;
|
|
||||||
const layoutAwareScan = this._options.scanning.layoutAwareScan;
|
|
||||||
const sentence = this._documentUtil.extractSentence(textSource, sentenceExtent, layoutAwareScan);
|
|
||||||
|
|
||||||
state.focusEntry = this._getClosestDefinitionIndex(scannedElement);
|
|
||||||
state.scrollX = this._windowScroll.x;
|
|
||||||
state.scrollY = this._windowScroll.y;
|
|
||||||
this._historyStateUpdate(state);
|
|
||||||
|
|
||||||
const query = textSource.text();
|
|
||||||
const details = {
|
|
||||||
focus: false,
|
|
||||||
history: true,
|
|
||||||
params: this._createSearchParams('terms', query, false),
|
|
||||||
state: {
|
state: {
|
||||||
focusEntry: 0,
|
focusEntry: 0,
|
||||||
sentence,
|
sentence,
|
||||||
optionsContext: state.optionsContext
|
optionsContext
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
definitions
|
definitions
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.setContent(details);
|
this.setContent(details);
|
||||||
|
} catch (error) {
|
||||||
|
this.onError(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _termLookup(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const {length: scanLength, deepDomScan: deepScan, layoutAwareScan} = this._options.scanning;
|
|
||||||
const textSource = this._documentUtil.getRangeFromPoint(e.clientX, e.clientY, deepScan);
|
|
||||||
if (textSource === null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let definitions, length;
|
|
||||||
try {
|
|
||||||
textSource.setEndOffset(scanLength, layoutAwareScan);
|
|
||||||
|
|
||||||
({definitions, length} = await api.termsFind(textSource.text(), {}, this.getOptionsContext()));
|
|
||||||
if (definitions.length === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
textSource.setEndOffset(length, layoutAwareScan);
|
|
||||||
} finally {
|
|
||||||
textSource.cleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {textSource, definitions};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_onAudioPlay(e) {
|
_onAudioPlay(e) {
|
||||||
@ -942,7 +857,7 @@ class Display extends EventDispatcher {
|
|||||||
if (this._setContentToken !== token) { return true; }
|
if (this._setContentToken !== token) { return true; }
|
||||||
|
|
||||||
if (changeHistory) {
|
if (changeHistory) {
|
||||||
this._historyStateUpdate(state, content);
|
this._replaceHistoryStateNoNavigate(state, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
eventArgs.source = source;
|
eventArgs.source = source;
|
||||||
@ -1054,17 +969,17 @@ class Display extends EventDispatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_setTitleText(text) {
|
_setTitleText(text) {
|
||||||
let title = '';
|
let title = this._defaultTitle;
|
||||||
if (text.length > 0) {
|
if (text.length > 0) {
|
||||||
// Chrome limits title to 1024 characters
|
// Chrome limits title to 1024 characters
|
||||||
const ellipsis = '...';
|
const ellipsis = '...';
|
||||||
const separator = ' - ';
|
const separator = ' - ';
|
||||||
const maxLength = this._titleMaxLength - this._defaultTitle.length - separator.length;
|
const maxLength = this._titleMaxLength - title.length - separator.length;
|
||||||
if (text.length > maxLength) {
|
if (text.length > maxLength) {
|
||||||
text = `${text.substring(0, Math.max(0, maxLength - ellipsis.length))}${ellipsis}`;
|
text = `${text.substring(0, Math.max(0, maxLength - ellipsis.length))}${ellipsis}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
title = `${text}${separator}${this._defaultTitle}`;
|
title = `${text}${separator}${title}`;
|
||||||
}
|
}
|
||||||
document.title = title;
|
document.title = title;
|
||||||
}
|
}
|
||||||
@ -1384,12 +1299,20 @@ class Display extends EventDispatcher {
|
|||||||
return isObject(this._history.state);
|
return isObject(this._history.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
_historyStateUpdate(state, content) {
|
_updateHistoryState() {
|
||||||
|
const {state, content} = this._history;
|
||||||
|
if (!isObject(state)) { return; }
|
||||||
|
|
||||||
|
state.focusEntry = this._index;
|
||||||
|
state.scrollX = this._windowScroll.x;
|
||||||
|
state.scrollY = this._windowScroll.y;
|
||||||
|
this._replaceHistoryStateNoNavigate(state, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
_replaceHistoryStateNoNavigate(state, content) {
|
||||||
const historyChangeIgnorePre = this._historyChangeIgnore;
|
const historyChangeIgnorePre = this._historyChangeIgnore;
|
||||||
try {
|
try {
|
||||||
this._historyChangeIgnore = true;
|
this._historyChangeIgnore = true;
|
||||||
if (typeof state === 'undefined') { state = this._history.state; }
|
|
||||||
if (typeof content === 'undefined') { content = this._history.content; }
|
|
||||||
this._history.replaceState(state, content);
|
this._history.replaceState(state, content);
|
||||||
} finally {
|
} finally {
|
||||||
this._historyChangeIgnore = historyChangeIgnorePre;
|
this._historyChangeIgnore = historyChangeIgnorePre;
|
||||||
@ -1702,7 +1625,7 @@ class Display extends EventDispatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_copyHostSelection() {
|
_copyHostSelection() {
|
||||||
if (window.getSelection().toString()) { return false; }
|
if (this._ownerFrameId === null || window.getSelection().toString()) { return false; }
|
||||||
this._copyHostSelectionInner();
|
this._copyHostSelectionInner();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -1766,10 +1689,89 @@ class Display extends EventDispatcher {
|
|||||||
this._addMultipleEventListeners(entry, '.action-play-audio', 'click', this._onAudioPlay.bind(this));
|
this._addMultipleEventListeners(entry, '.action-play-audio', 'click', this._onAudioPlay.bind(this));
|
||||||
this._addMultipleEventListeners(entry, '.kanji-link', 'click', this._onKanjiLookup.bind(this));
|
this._addMultipleEventListeners(entry, '.kanji-link', 'click', this._onKanjiLookup.bind(this));
|
||||||
this._addMultipleEventListeners(entry, '.debug-log-link', 'click', this._onDebugLogClick.bind(this));
|
this._addMultipleEventListeners(entry, '.debug-log-link', 'click', this._onDebugLogClick.bind(this));
|
||||||
if (this._options !== null && this._options.scanning.enablePopupSearch) {
|
}
|
||||||
this._addMultipleEventListeners(entry, '.term-glossary-item,.tag', 'mouseup', this._onGlossaryMouseUp.bind(this));
|
|
||||||
this._addMultipleEventListeners(entry, '.term-glossary-item,.tag', 'mousedown', this._onGlossaryMouseDown.bind(this));
|
_updateDefinitionTextScanner(options) {
|
||||||
this._addMultipleEventListeners(entry, '.term-glossary-item,.tag', 'mousemove', this._onGlossaryMouseMove.bind(this));
|
if (!options.scanning.enablePopupSearch) {
|
||||||
}
|
if (this._definitionTextScanner !== null) {
|
||||||
|
this._definitionTextScanner.setEnabled(false);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._definitionTextScanner === null) {
|
||||||
|
this._definitionTextScanner = new TextScanner({
|
||||||
|
node: window,
|
||||||
|
getOptionsContext: this.getOptionsContext.bind(this),
|
||||||
|
documentUtil: this._documentUtil,
|
||||||
|
searchTerms: true,
|
||||||
|
searchKanji: false,
|
||||||
|
searchOnClick: true,
|
||||||
|
searchOnClickOnly: true
|
||||||
|
});
|
||||||
|
this._definitionTextScanner.prepare();
|
||||||
|
this._definitionTextScanner.on('searched', this._onDefinitionTextScannerSearched.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
const scanningOptions = options.scanning;
|
||||||
|
this._definitionTextScanner.setOptions({
|
||||||
|
inputs: [{
|
||||||
|
include: 'mouse0',
|
||||||
|
exclude: '',
|
||||||
|
types: {mouse: true, pen: false, touch: false},
|
||||||
|
options: {
|
||||||
|
searchTerms: true,
|
||||||
|
searchKanji: true,
|
||||||
|
scanOnTouchMove: false,
|
||||||
|
scanOnPenHover: false,
|
||||||
|
scanOnPenPress: false,
|
||||||
|
scanOnPenRelease: false,
|
||||||
|
preventTouchScrolling: false
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
deepContentScan: scanningOptions.deepDomScan,
|
||||||
|
selectText: false,
|
||||||
|
delay: scanningOptions.delay,
|
||||||
|
touchInputEnabled: false,
|
||||||
|
pointerEventsEnabled: false,
|
||||||
|
scanLength: scanningOptions.length,
|
||||||
|
sentenceExtent: options.anki.sentenceExt,
|
||||||
|
layoutAwareScan: scanningOptions.layoutAwareScan,
|
||||||
|
preventMiddleMouse: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const includeSelector = '.term-glossary-item,.term-glossary-item *,.tag,.tag *';
|
||||||
|
this._definitionTextScanner.includeSelector = includeSelector;
|
||||||
|
|
||||||
|
this._definitionTextScanner.setEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onDefinitionTextScannerSearched({type, definitions, sentence, textSource, optionsContext, error}) {
|
||||||
|
if (error !== null && !yomichan.isExtensionUnloaded) {
|
||||||
|
yomichan.logError(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === null) { return; }
|
||||||
|
|
||||||
|
const query = textSource.text();
|
||||||
|
const details = {
|
||||||
|
focus: false,
|
||||||
|
history: true,
|
||||||
|
params: {
|
||||||
|
type,
|
||||||
|
query,
|
||||||
|
wildcards: 'off'
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
focusEntry: 0,
|
||||||
|
sentence,
|
||||||
|
optionsContext
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
definitions
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this._definitionTextScanner.clearSelection(true);
|
||||||
|
this.setContent(details);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -261,6 +261,18 @@ class DocumentUtil {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static everyNodeMatchesSelector(nodes, selector) {
|
||||||
|
const ELEMENT_NODE = Node.ELEMENT_NODE;
|
||||||
|
for (let node of nodes) {
|
||||||
|
while (true) {
|
||||||
|
if (node === null) { return false; }
|
||||||
|
if (node.nodeType === ELEMENT_NODE && node.matches(selector)) { break; }
|
||||||
|
node = node.parentNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static getModifierKeys(os) {
|
static getModifierKeys(os) {
|
||||||
switch (os) {
|
switch (os) {
|
||||||
case 'win':
|
case 'win':
|
||||||
|
@ -21,19 +21,31 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
class TextScanner extends EventDispatcher {
|
class TextScanner extends EventDispatcher {
|
||||||
constructor({node, ignoreElements, ignorePoint, documentUtil, getOptionsContext, searchTerms=false, searchKanji=false, searchOnClick=false}) {
|
constructor({
|
||||||
|
node,
|
||||||
|
documentUtil,
|
||||||
|
getOptionsContext,
|
||||||
|
ignoreElements=null,
|
||||||
|
ignorePoint=null,
|
||||||
|
searchTerms=false,
|
||||||
|
searchKanji=false,
|
||||||
|
searchOnClick=false,
|
||||||
|
searchOnClickOnly=false
|
||||||
|
}) {
|
||||||
super();
|
super();
|
||||||
this._node = node;
|
this._node = node;
|
||||||
this._ignoreElements = ignoreElements;
|
|
||||||
this._ignorePoint = ignorePoint;
|
|
||||||
this._documentUtil = documentUtil;
|
this._documentUtil = documentUtil;
|
||||||
this._getOptionsContext = getOptionsContext;
|
this._getOptionsContext = getOptionsContext;
|
||||||
|
this._ignoreElements = ignoreElements;
|
||||||
|
this._ignorePoint = ignorePoint;
|
||||||
this._searchTerms = searchTerms;
|
this._searchTerms = searchTerms;
|
||||||
this._searchKanji = searchKanji;
|
this._searchKanji = searchKanji;
|
||||||
this._searchOnClick = searchOnClick;
|
this._searchOnClick = searchOnClick;
|
||||||
|
this._searchOnClickOnly = searchOnClickOnly;
|
||||||
|
|
||||||
this._isPrepared = false;
|
this._isPrepared = false;
|
||||||
this._ignoreNodes = null;
|
this._includeSelector = null;
|
||||||
|
this._excludeSelector = null;
|
||||||
|
|
||||||
this._inputInfoCurrent = null;
|
this._inputInfoCurrent = null;
|
||||||
this._scanTimerPromise = null;
|
this._scanTimerPromise = null;
|
||||||
@ -76,12 +88,20 @@ class TextScanner extends EventDispatcher {
|
|||||||
this._canClearSelection = value;
|
this._canClearSelection = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
get ignoreNodes() {
|
get includeSelector() {
|
||||||
return this._ignoreNodes;
|
return this._includeSelector;
|
||||||
}
|
}
|
||||||
|
|
||||||
set ignoreNodes(value) {
|
set includeSelector(value) {
|
||||||
this._ignoreNodes = value;
|
this._includeSelector = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get excludeSelector() {
|
||||||
|
return this._excludeSelector;
|
||||||
|
}
|
||||||
|
|
||||||
|
set excludeSelector(value) {
|
||||||
|
this._excludeSelector = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
prepare() {
|
prepare() {
|
||||||
@ -178,15 +198,8 @@ class TextScanner extends EventDispatcher {
|
|||||||
|
|
||||||
clonedTextSource.setEndOffset(length, layoutAwareScan);
|
clonedTextSource.setEndOffset(length, layoutAwareScan);
|
||||||
|
|
||||||
if (this._ignoreNodes !== null) {
|
if (this._excludeSelector !== null) {
|
||||||
length = clonedTextSource.text().length;
|
this._constrainTextSource(clonedTextSource, this._includeSelector, this._excludeSelector, layoutAwareScan);
|
||||||
while (
|
|
||||||
length > 0 &&
|
|
||||||
DocumentUtil.anyNodeMatchesSelector(clonedTextSource.getNodesInRange(), this._ignoreNodes)
|
|
||||||
) {
|
|
||||||
--length;
|
|
||||||
clonedTextSource.setEndOffset(length, layoutAwareScan);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return clonedTextSource.text();
|
return clonedTextSource.text();
|
||||||
@ -287,7 +300,7 @@ class TextScanner extends EventDispatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_onMouseOver(e) {
|
_onMouseOver(e) {
|
||||||
if (this._ignoreElements().includes(e.target)) {
|
if (this._ignoreElements !== null && this._ignoreElements().includes(e.target)) {
|
||||||
this._scanTimerClear();
|
this._scanTimerClear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -613,7 +626,9 @@ class TextScanner extends EventDispatcher {
|
|||||||
|
|
||||||
_hookEvents() {
|
_hookEvents() {
|
||||||
let eventListenerInfos;
|
let eventListenerInfos;
|
||||||
if (this._arePointerEventsSupported()) {
|
if (this._searchOnClickOnly) {
|
||||||
|
eventListenerInfos = this._getMouseClickOnlyEventListeners();
|
||||||
|
} else if (this._arePointerEventsSupported()) {
|
||||||
eventListenerInfos = this._getPointerEventListeners();
|
eventListenerInfos = this._getPointerEventListeners();
|
||||||
} else {
|
} else {
|
||||||
eventListenerInfos = this._getMouseEventListeners();
|
eventListenerInfos = this._getMouseEventListeners();
|
||||||
@ -652,6 +667,11 @@ class TextScanner extends EventDispatcher {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_getMouseClickOnlyEventListeners() {
|
||||||
|
return [
|
||||||
|
[this._node, 'click', this._onClick.bind(this)]
|
||||||
|
];
|
||||||
|
}
|
||||||
_getTouchEventListeners() {
|
_getTouchEventListeners() {
|
||||||
return [
|
return [
|
||||||
[this._node, 'auxclick', this._onAuxClick.bind(this)],
|
[this._node, 'auxclick', this._onAuxClick.bind(this)],
|
||||||
@ -873,4 +893,20 @@ class TextScanner extends EventDispatcher {
|
|||||||
const cachedPointerType = this._pointerIdTypeMap.get(e.pointerId);
|
const cachedPointerType = this._pointerIdTypeMap.get(e.pointerId);
|
||||||
return (typeof cachedPointerType !== 'undefined' ? cachedPointerType : e.pointerType);
|
return (typeof cachedPointerType !== 'undefined' ? cachedPointerType : e.pointerType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_constrainTextSource(textSource, includeSelector, excludeSelector, layoutAwareScan) {
|
||||||
|
let length = textSource.text().length;
|
||||||
|
while (length > 0) {
|
||||||
|
const nodes = textSource.getNodesInRange();
|
||||||
|
if (
|
||||||
|
(includeSelector !== null && !DocumentUtil.everyNodeMatchesSelector(nodes, includeSelector)) ||
|
||||||
|
(excludeSelector !== null && DocumentUtil.anyNodeMatchesSelector(nodes, excludeSelector))
|
||||||
|
) {
|
||||||
|
--length;
|
||||||
|
textSource.setEndOffset(length, layoutAwareScan);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user