From 3949db26d778bc3f593438211b148e8309921542 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Fri, 8 May 2020 19:05:50 -0400 Subject: Text scanner refactor (#517) * Fix return type * Pass search function as a constructor argument * Pass constructor as a details object For consistency with other complex constructors and improved semantics. * Convert _ignorePoints to a single optional function * Organize functions * Rename ignorePoints to ignorePoint --- ext/bg/js/search-query-parser.js | 16 +-- ext/fg/js/frontend.js | 18 ++-- ext/mixed/js/text-scanner.js | 203 +++++++++++++++++++-------------------- 3 files changed, 117 insertions(+), 120 deletions(-) diff --git a/ext/bg/js/search-query-parser.js b/ext/bg/js/search-query-parser.js index caace34b..e1e37d1c 100644 --- a/ext/bg/js/search-query-parser.js +++ b/ext/bg/js/search-query-parser.js @@ -34,12 +34,12 @@ class QueryParser { this._queryParser = document.querySelector('#query-parser-content'); this._queryParserSelect = document.querySelector('#query-parser-select-container'); this._queryParserGenerator = new QueryParserGenerator(); - this._textScanner = new TextScanner( - this._queryParser, - () => [], - [] - ); - this._textScanner.onSearchSource = this._onSearchSource.bind(this); + this._textScanner = new TextScanner({ + node: this._queryParser, + ignoreElements: () => [], + ignorePoint: null, + search: this._search.bind(this) + }); } async prepare() { @@ -74,11 +74,11 @@ class QueryParser { this._textScanner.searchAt(e.clientX, e.clientY, 'click'); } - async _onSearchSource(textSource, cause) { + async _search(textSource, cause) { if (textSource === null) { return null; } const searchText = this._textScanner.getTextSourceContent(textSource, this._options.scanning.length); - if (searchText.length === 0) { return; } + if (searchText.length === 0) { return null; } const {definitions, length} = await apiTermsFind(searchText, {}, this._getOptionsContext()); if (definitions.length === 0) { return null; } diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index 9de6597e..78440991 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -39,12 +39,12 @@ class Frontend { this._enabledEventListeners = new EventListenerCollection(); this._activeModifiers = new Set(); this._optionsUpdatePending = false; - this._textScanner = new TextScanner( - window, - () => this._popup.isProxy() ? [] : [this._popup.getContainer()], - [(x, y) => this._popup.containsPoint(x, y)] - ); - this._textScanner.onSearchSource = this._onSearchSource.bind(this); + this._textScanner = new TextScanner({ + node: window, + ignoreElements: () => this._popup.isProxy() ? [] : [this._popup.getContainer()], + ignorePoint: (x, y) => this._popup.containsPoint(x, y), + search: this._search.bind(this) + }); this._windowMessageHandlers = new Map([ ['popupClose', () => this._textScanner.clearSelection(false)], @@ -107,7 +107,7 @@ class Frontend { } async setTextSource(textSource) { - await this._onSearchSource(textSource, 'script'); + await this._search(textSource, 'script'); this._textScanner.setCurrentTextSource(textSource); } @@ -137,7 +137,7 @@ class Frontend { const textSourceCurrent = this._textScanner.getCurrentTextSource(); const causeCurrent = this._textScanner.causeCurrent; if (textSourceCurrent !== null && causeCurrent !== null) { - await this._onSearchSource(textSourceCurrent, causeCurrent); + await this._search(textSourceCurrent, causeCurrent); } } @@ -204,7 +204,7 @@ class Frontend { await this.updateOptions(); } - async _onSearchSource(textSource, cause) { + async _search(textSource, cause) { await this._updatePendingOptions(); let results = null; diff --git a/ext/mixed/js/text-scanner.js b/ext/mixed/js/text-scanner.js index 2ed6c3b9..b8688b08 100644 --- a/ext/mixed/js/text-scanner.js +++ b/ext/mixed/js/text-scanner.js @@ -22,11 +22,12 @@ */ class TextScanner extends EventDispatcher { - constructor(node, ignoreElements, ignorePoints) { + constructor({node, ignoreElements, ignorePoint, search}) { super(); this._node = node; this._ignoreElements = ignoreElements; - this._ignorePoints = ignorePoints; + this._ignorePoint = ignorePoint; + this._search = search; this._ignoreNodes = null; @@ -69,6 +70,103 @@ class TextScanner extends EventDispatcher { return this._causeCurrent; } + setEnabled(enabled) { + this._eventListeners.removeAllEventListeners(); + this._enabled = enabled; + if (this._enabled) { + this._hookEvents(); + } else { + this.clearSelection(true); + } + } + + setOptions(options) { + this._options = options; + } + + async searchAt(x, y, cause) { + try { + this._scanTimerClear(); + + if (this._pendingLookup) { + return; + } + + if (typeof this._ignorePoint === 'function' && await this._ignorePoint(x, y)) { + return; + } + + const textSource = docRangeFromPoint(x, y, this._options.scanning.deepDomScan); + try { + if (this._textSourceCurrent !== null && this._textSourceCurrent.equals(textSource)) { + return; + } + + this._pendingLookup = true; + const result = await this._search(textSource, cause); + if (result !== null) { + this._causeCurrent = cause; + this.setCurrentTextSource(textSource); + } + this._pendingLookup = false; + } finally { + if (textSource !== null) { + textSource.cleanup(); + } + } + } catch (e) { + yomichan.logError(e); + } + } + + getTextSourceContent(textSource, length) { + const clonedTextSource = textSource.clone(); + + clonedTextSource.setEndOffset(length); + + if (this._ignoreNodes !== null && clonedTextSource.range) { + length = clonedTextSource.text().length; + while (clonedTextSource.range && length > 0) { + const nodes = TextSourceRange.getNodesInRange(clonedTextSource.range); + if (!TextSourceRange.anyNodeMatchesSelector(nodes, this._ignoreNodes)) { + break; + } + --length; + clonedTextSource.setEndOffset(length); + } + } + + return clonedTextSource.text(); + } + + clearSelection(passive) { + if (!this._canClearSelection) { return; } + if (this._textSourceCurrent !== null) { + if (this._textSourceCurrentSelected) { + this._textSourceCurrent.deselect(); + } + this._textSourceCurrent = null; + this._textSourceCurrentSelected = false; + } + this.trigger('clearSelection', {passive}); + } + + getCurrentTextSource() { + return this._textSourceCurrent; + } + + setCurrentTextSource(textSource) { + this._textSourceCurrent = textSource; + if (this._options.scanning.selectText) { + this._textSourceCurrent.select(); + this._textSourceCurrentSelected = true; + } else { + this._textSourceCurrentSelected = false; + } + } + + // Private + _onMouseOver(e) { if (this._ignoreElements().includes(e.target)) { this._scanTimerClear(); @@ -221,10 +319,6 @@ class TextScanner extends EventDispatcher { e.preventDefault(); // Disable scroll } - async onSearchSource(_textSource, _cause) { - throw new Error('Override me'); - } - async _scanTimerWait() { const delay = this._options.scanning.delay; const promise = promiseTimeout(delay, true); @@ -245,16 +339,6 @@ class TextScanner extends EventDispatcher { } } - setEnabled(enabled) { - this._eventListeners.removeAllEventListeners(); - this._enabled = enabled; - if (this._enabled) { - this._hookEvents(); - } else { - this.clearSelection(true); - } - } - _hookEvents() { const eventListenerInfos = this._getMouseEventListeners(); if (this._options.scanning.touchInputEnabled) { @@ -287,93 +371,6 @@ class TextScanner extends EventDispatcher { ]; } - setOptions(options) { - this._options = options; - } - - async searchAt(x, y, cause) { - try { - this._scanTimerClear(); - - if (this._pendingLookup) { - return; - } - - for (const ignorePointFn of this._ignorePoints) { - if (await ignorePointFn(x, y)) { - return; - } - } - - const textSource = docRangeFromPoint(x, y, this._options.scanning.deepDomScan); - try { - if (this._textSourceCurrent !== null && this._textSourceCurrent.equals(textSource)) { - return; - } - - this._pendingLookup = true; - const result = await this.onSearchSource(textSource, cause); - if (result !== null) { - this._causeCurrent = cause; - this.setCurrentTextSource(textSource); - } - this._pendingLookup = false; - } finally { - if (textSource !== null) { - textSource.cleanup(); - } - } - } catch (e) { - yomichan.logError(e); - } - } - - getTextSourceContent(textSource, length) { - const clonedTextSource = textSource.clone(); - - clonedTextSource.setEndOffset(length); - - if (this._ignoreNodes !== null && clonedTextSource.range) { - length = clonedTextSource.text().length; - while (clonedTextSource.range && length > 0) { - const nodes = TextSourceRange.getNodesInRange(clonedTextSource.range); - if (!TextSourceRange.anyNodeMatchesSelector(nodes, this._ignoreNodes)) { - break; - } - --length; - clonedTextSource.setEndOffset(length); - } - } - - return clonedTextSource.text(); - } - - clearSelection(passive) { - if (!this._canClearSelection) { return; } - if (this._textSourceCurrent !== null) { - if (this._textSourceCurrentSelected) { - this._textSourceCurrent.deselect(); - } - this._textSourceCurrent = null; - this._textSourceCurrentSelected = false; - } - this.trigger('clearSelection', {passive}); - } - - getCurrentTextSource() { - return this._textSourceCurrent; - } - - setCurrentTextSource(textSource) { - this._textSourceCurrent = textSource; - if (this._options.scanning.selectText) { - this._textSourceCurrent.select(); - this._textSourceCurrentSelected = true; - } else { - this._textSourceCurrentSelected = false; - } - } - _isScanningModifierPressed(scanningModifier, mouseEvent) { switch (scanningModifier) { case 'alt': return mouseEvent.altKey; -- cgit v1.2.3