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
This commit is contained in:
toasted-nutbread 2020-05-08 19:05:50 -04:00 committed by GitHub
parent b936c3e4b1
commit 3949db26d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 117 additions and 120 deletions

View File

@ -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; }

View File

@ -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;

View File

@ -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;