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:
parent
b936c3e4b1
commit
3949db26d7
@ -34,12 +34,12 @@ class QueryParser {
|
|||||||
this._queryParser = document.querySelector('#query-parser-content');
|
this._queryParser = document.querySelector('#query-parser-content');
|
||||||
this._queryParserSelect = document.querySelector('#query-parser-select-container');
|
this._queryParserSelect = document.querySelector('#query-parser-select-container');
|
||||||
this._queryParserGenerator = new QueryParserGenerator();
|
this._queryParserGenerator = new QueryParserGenerator();
|
||||||
this._textScanner = new TextScanner(
|
this._textScanner = new TextScanner({
|
||||||
this._queryParser,
|
node: this._queryParser,
|
||||||
() => [],
|
ignoreElements: () => [],
|
||||||
[]
|
ignorePoint: null,
|
||||||
);
|
search: this._search.bind(this)
|
||||||
this._textScanner.onSearchSource = this._onSearchSource.bind(this);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async prepare() {
|
async prepare() {
|
||||||
@ -74,11 +74,11 @@ class QueryParser {
|
|||||||
this._textScanner.searchAt(e.clientX, e.clientY, 'click');
|
this._textScanner.searchAt(e.clientX, e.clientY, 'click');
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onSearchSource(textSource, cause) {
|
async _search(textSource, cause) {
|
||||||
if (textSource === null) { return null; }
|
if (textSource === null) { return null; }
|
||||||
|
|
||||||
const searchText = this._textScanner.getTextSourceContent(textSource, this._options.scanning.length);
|
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());
|
const {definitions, length} = await apiTermsFind(searchText, {}, this._getOptionsContext());
|
||||||
if (definitions.length === 0) { return null; }
|
if (definitions.length === 0) { return null; }
|
||||||
|
@ -39,12 +39,12 @@ class Frontend {
|
|||||||
this._enabledEventListeners = new EventListenerCollection();
|
this._enabledEventListeners = new EventListenerCollection();
|
||||||
this._activeModifiers = new Set();
|
this._activeModifiers = new Set();
|
||||||
this._optionsUpdatePending = false;
|
this._optionsUpdatePending = false;
|
||||||
this._textScanner = new TextScanner(
|
this._textScanner = new TextScanner({
|
||||||
window,
|
node: window,
|
||||||
() => this._popup.isProxy() ? [] : [this._popup.getContainer()],
|
ignoreElements: () => this._popup.isProxy() ? [] : [this._popup.getContainer()],
|
||||||
[(x, y) => this._popup.containsPoint(x, y)]
|
ignorePoint: (x, y) => this._popup.containsPoint(x, y),
|
||||||
);
|
search: this._search.bind(this)
|
||||||
this._textScanner.onSearchSource = this._onSearchSource.bind(this);
|
});
|
||||||
|
|
||||||
this._windowMessageHandlers = new Map([
|
this._windowMessageHandlers = new Map([
|
||||||
['popupClose', () => this._textScanner.clearSelection(false)],
|
['popupClose', () => this._textScanner.clearSelection(false)],
|
||||||
@ -107,7 +107,7 @@ class Frontend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async setTextSource(textSource) {
|
async setTextSource(textSource) {
|
||||||
await this._onSearchSource(textSource, 'script');
|
await this._search(textSource, 'script');
|
||||||
this._textScanner.setCurrentTextSource(textSource);
|
this._textScanner.setCurrentTextSource(textSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,7 +137,7 @@ class Frontend {
|
|||||||
const textSourceCurrent = this._textScanner.getCurrentTextSource();
|
const textSourceCurrent = this._textScanner.getCurrentTextSource();
|
||||||
const causeCurrent = this._textScanner.causeCurrent;
|
const causeCurrent = this._textScanner.causeCurrent;
|
||||||
if (textSourceCurrent !== null && causeCurrent !== null) {
|
if (textSourceCurrent !== null && causeCurrent !== null) {
|
||||||
await this._onSearchSource(textSourceCurrent, causeCurrent);
|
await this._search(textSourceCurrent, causeCurrent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,7 +204,7 @@ class Frontend {
|
|||||||
await this.updateOptions();
|
await this.updateOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onSearchSource(textSource, cause) {
|
async _search(textSource, cause) {
|
||||||
await this._updatePendingOptions();
|
await this._updatePendingOptions();
|
||||||
|
|
||||||
let results = null;
|
let results = null;
|
||||||
|
@ -22,11 +22,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
class TextScanner extends EventDispatcher {
|
class TextScanner extends EventDispatcher {
|
||||||
constructor(node, ignoreElements, ignorePoints) {
|
constructor({node, ignoreElements, ignorePoint, search}) {
|
||||||
super();
|
super();
|
||||||
this._node = node;
|
this._node = node;
|
||||||
this._ignoreElements = ignoreElements;
|
this._ignoreElements = ignoreElements;
|
||||||
this._ignorePoints = ignorePoints;
|
this._ignorePoint = ignorePoint;
|
||||||
|
this._search = search;
|
||||||
|
|
||||||
this._ignoreNodes = null;
|
this._ignoreNodes = null;
|
||||||
|
|
||||||
@ -69,6 +70,103 @@ class TextScanner extends EventDispatcher {
|
|||||||
return this._causeCurrent;
|
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) {
|
_onMouseOver(e) {
|
||||||
if (this._ignoreElements().includes(e.target)) {
|
if (this._ignoreElements().includes(e.target)) {
|
||||||
this._scanTimerClear();
|
this._scanTimerClear();
|
||||||
@ -221,10 +319,6 @@ class TextScanner extends EventDispatcher {
|
|||||||
e.preventDefault(); // Disable scroll
|
e.preventDefault(); // Disable scroll
|
||||||
}
|
}
|
||||||
|
|
||||||
async onSearchSource(_textSource, _cause) {
|
|
||||||
throw new Error('Override me');
|
|
||||||
}
|
|
||||||
|
|
||||||
async _scanTimerWait() {
|
async _scanTimerWait() {
|
||||||
const delay = this._options.scanning.delay;
|
const delay = this._options.scanning.delay;
|
||||||
const promise = promiseTimeout(delay, true);
|
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() {
|
_hookEvents() {
|
||||||
const eventListenerInfos = this._getMouseEventListeners();
|
const eventListenerInfos = this._getMouseEventListeners();
|
||||||
if (this._options.scanning.touchInputEnabled) {
|
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) {
|
_isScanningModifierPressed(scanningModifier, mouseEvent) {
|
||||||
switch (scanningModifier) {
|
switch (scanningModifier) {
|
||||||
case 'alt': return mouseEvent.altKey;
|
case 'alt': return mouseEvent.altKey;
|
||||||
|
Loading…
Reference in New Issue
Block a user