summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortoasted-nutbread <toasted-nutbread@users.noreply.github.com>2020-09-05 21:43:19 -0400
committerGitHub <noreply@github.com>2020-09-05 21:43:19 -0400
commit2f8408ffcc0be1321bcd105e7675a1210b8f7df8 (patch)
treeee64b9b3ea3914f18ab7680f2eb23b53435aba39
parentdd8e32e7c40011de95e6c81211e2b434be2fde78 (diff)
Text scanner refactor (#771)
* Create searchAt wrappers * Add optional support for searching on the click event * Update QueryParser to use TextScanner's searchOnClick functionality * Move/rename searchAt * Move pendingLookup checks * Add 'searched' event to TextScanner * Use common searched event for Frontend and QueryParser * Move functions, make private
-rw-r--r--ext/bg/js/query-parser.js29
-rw-r--r--ext/fg/js/frontend.js81
-rw-r--r--ext/mixed/js/text-scanner.js246
3 files changed, 196 insertions, 160 deletions
diff --git a/ext/bg/js/query-parser.js b/ext/bg/js/query-parser.js
index b0ef2d78..a2a04606 100644
--- a/ext/bg/js/query-parser.js
+++ b/ext/bg/js/query-parser.js
@@ -36,15 +36,18 @@ class QueryParser extends EventDispatcher {
node: this._queryParser,
ignoreElements: () => [],
ignorePoint: null,
- search: this._search.bind(this),
- documentUtil
+ getOptionsContext,
+ documentUtil,
+ searchTerms: true,
+ searchKanji: false,
+ searchOnClick: true
});
}
async prepare() {
await this._queryParserGenerator.prepare();
this._textScanner.prepare();
- this._queryParser.addEventListener('click', this._onClick.bind(this));
+ this._textScanner.on('searched', this._onSearched.bind(this));
}
setOptions({selectedParser, termSpacing, scanning}) {
@@ -76,18 +79,12 @@ class QueryParser extends EventDispatcher {
// Private
- _onClick(e) {
- this._textScanner.searchAt(e.clientX, e.clientY, 'click');
- }
-
- async _search(textSource, cause) {
- if (textSource === null) { return null; }
-
- const optionsContext = this._getOptionsContext();
- const results = await this._textScanner.findTerms(textSource, optionsContext);
- if (results === null) { return null; }
-
- const {definitions, sentence, type} = results;
+ _onSearched({type, definitions, sentence, cause, textSource, optionsContext, error}) {
+ if (error !== null) {
+ yomichan.logError(error);
+ return;
+ }
+ if (type === null) { return; }
this.trigger('searched', {
type,
@@ -97,8 +94,6 @@ class QueryParser extends EventDispatcher {
textSource,
optionsContext
});
-
- return {definitions, type};
}
_onParserChange(e) {
diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js
index 44690028..8c4cfc82 100644
--- a/ext/fg/js/frontend.js
+++ b/ext/fg/js/frontend.js
@@ -47,8 +47,10 @@ class Frontend {
node: window,
ignoreElements: this._ignoreElements.bind(this),
ignorePoint: this._ignorePoint.bind(this),
- search: this._search.bind(this),
- documentUtil: this._documentUtil
+ getOptionsContext: this._getUpToDateOptionsContext.bind(this),
+ documentUtil: this._documentUtil,
+ searchTerms: true,
+ searchKanji: true
});
this._parentPopupId = parentPopupId;
this._parentFrameId = parentFrameId;
@@ -105,6 +107,7 @@ class Frontend {
this._textScanner.on('clearSelection', this._onClearSelection.bind(this));
this._textScanner.on('activeModifiersChanged', this._onActiveModifiersChanged.bind(this));
+ this._textScanner.on('searched', this._onSearched.bind(this));
api.crossFrame.registerHandlers([
['getUrl', {async: false, handler: this._onApiGetUrl.bind(this)}],
@@ -126,8 +129,7 @@ class Frontend {
}
async setTextSource(textSource) {
- await this._search(textSource, 'script');
- this._textScanner.setCurrentTextSource(textSource);
+ await this._textScanner.search(textSource, 'script');
}
async getOptionsContext() {
@@ -247,6 +249,27 @@ class Frontend {
await this.updateOptions();
}
+ _onSearched({textScanner, type, definitions, sentence, cause, textSource, optionsContext, error}) {
+ if (error !== null) {
+ if (yomichan.isExtensionUnloaded) {
+ if (textSource !== null && this._options.scanning.modifier !== 'none') {
+ this._showExtensionUnloaded(textSource);
+ }
+ } else {
+ yomichan.logError(error);
+ }
+ } else {
+ if (type !== null) {
+ const focus = (cause === 'mouse');
+ this._showContent(textSource, focus, definitions, type, sentence, optionsContext);
+ }
+ }
+
+ if (type === null && this._options.scanning.autoHideResults) {
+ textScanner.clearSelection(false);
+ }
+ }
+
async _updateOptionsInternal() {
const optionsContext = await this.getOptionsContext();
const options = await api.optionsGet(optionsContext);
@@ -279,7 +302,7 @@ class Frontend {
const textSourceCurrent = this._textScanner.getCurrentTextSource();
const causeCurrent = this._textScanner.causeCurrent;
if (textSourceCurrent !== null && causeCurrent !== null) {
- await this._search(textSourceCurrent, causeCurrent);
+ await this._textScanner.search(textSourceCurrent, causeCurrent);
}
}
@@ -402,44 +425,6 @@ class Frontend {
}
}
- async _search(textSource, cause) {
- if (this._popup === null) {
- return null;
- }
-
- await this._updatePendingOptions();
-
- let results = null;
-
- try {
- if (textSource !== null) {
- const optionsContext = await this.getOptionsContext();
- results = (
- await this._textScanner.findTerms(textSource, optionsContext) ||
- await this._textScanner.findKanji(textSource, optionsContext)
- );
- if (results !== null) {
- const focus = (cause === 'mouse');
- this._showContent(textSource, focus, results.definitions, results.type, optionsContext);
- }
- }
- } catch (e) {
- if (yomichan.isExtensionUnloaded) {
- if (textSource !== null && this._options.scanning.modifier !== 'none') {
- this._showExtensionUnloaded(textSource);
- }
- } else {
- yomichan.logError(e);
- }
- } finally {
- if (results === null && this._options.scanning.autoHideResults) {
- this._textScanner.clearSelection(false);
- }
- }
-
- return results;
- }
-
async _showExtensionUnloaded(textSource) {
if (textSource === null) {
textSource = this._textScanner.getCurrentTextSource();
@@ -448,11 +433,8 @@ class Frontend {
this._showPopupContent(textSource, await this.getOptionsContext());
}
- _showContent(textSource, focus, definitions, type, optionsContext) {
+ _showContent(textSource, focus, definitions, type, sentence, optionsContext) {
const {url} = optionsContext;
- const sentenceExtent = this._options.anki.sentenceExt;
- const layoutAwareScan = this._options.scanning.layoutAwareScan;
- const sentence = this._documentUtil.extractSentence(textSource, sentenceExtent, layoutAwareScan);
const query = textSource.text();
const details = {
focus,
@@ -571,4 +553,9 @@ class Frontend {
api.broadcastTab('requestFrontendReadyBroadcast', {frameId: this._frameId});
await promise;
}
+
+ async _getUpToDateOptionsContext() {
+ await this._updatePendingOptions();
+ return await this.getOptionsContext();
+ }
}
diff --git a/ext/mixed/js/text-scanner.js b/ext/mixed/js/text-scanner.js
index 5a64c14a..2aeac565 100644
--- a/ext/mixed/js/text-scanner.js
+++ b/ext/mixed/js/text-scanner.js
@@ -21,13 +21,16 @@
*/
class TextScanner extends EventDispatcher {
- constructor({node, ignoreElements, ignorePoint, search, documentUtil}) {
+ constructor({node, ignoreElements, ignorePoint, documentUtil, getOptionsContext, searchTerms=false, searchKanji=false, searchOnClick=false}) {
super();
this._node = node;
this._ignoreElements = ignoreElements;
this._ignorePoint = ignorePoint;
- this._search = search;
this._documentUtil = documentUtil;
+ this._getOptionsContext = getOptionsContext;
+ this._searchTerms = searchTerms;
+ this._searchKanji = searchKanji;
+ this._searchOnClick = searchOnClick;
this._isPrepared = false;
this._ignoreNodes = null;
@@ -125,41 +128,6 @@ class TextScanner extends EventDispatcher {
}
}
- 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 = this._documentUtil.getRangeFromPoint(x, y, this._deepContentScan);
- 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, layoutAwareScan) {
const clonedTextSource = textSource.clone();
@@ -206,35 +174,44 @@ class TextScanner extends EventDispatcher {
}
}
- async findTerms(textSource, optionsContext) {
- const scanLength = this._scanLength;
- const sentenceExtent = this._sentenceExtent;
- const layoutAwareScan = this._layoutAwareScan;
- const searchText = this.getTextSourceContent(textSource, scanLength, layoutAwareScan);
- if (searchText.length === 0) { return null; }
-
- const {definitions, length} = await api.termsFind(searchText, {}, optionsContext);
- if (definitions.length === 0) { return null; }
-
- textSource.setEndOffset(length, layoutAwareScan);
- const sentence = this._documentUtil.extractSentence(textSource, sentenceExtent, layoutAwareScan);
+ async search(textSource, cause) {
+ let definitions = null;
+ let sentence = null;
+ let type = null;
+ let error = null;
+ let searched = false;
+ let optionsContext = null;
- return {definitions, sentence, type: 'terms'};
- }
+ try {
+ if (this._textSourceCurrent !== null && this._textSourceCurrent.equals(textSource)) {
+ return;
+ }
- async findKanji(textSource, optionsContext) {
- const sentenceExtent = this._sentenceExtent;
- const layoutAwareScan = this._layoutAwareScan;
- const searchText = this.getTextSourceContent(textSource, 1, layoutAwareScan);
- if (searchText.length === 0) { return null; }
+ optionsContext = await this._getOptionsContext();
+ searched = true;
- const definitions = await api.kanjiFind(searchText, optionsContext);
- if (definitions.length === 0) { return null; }
+ const result = await this._findDefinitions(textSource, cause);
+ if (result !== null) {
+ ({definitions, sentence, type} = result);
+ this._causeCurrent = cause;
+ this.setCurrentTextSource(textSource);
+ }
+ } catch (e) {
+ error = e;
+ }
- textSource.setEndOffset(1, layoutAwareScan);
- const sentence = this._documentUtil.extractSentence(textSource, sentenceExtent, layoutAwareScan);
+ if (!searched) { return; }
- return {definitions, sentence, type: 'kanji'};
+ this.trigger('searched', {
+ textScanner: this,
+ type,
+ definitions,
+ sentence,
+ cause,
+ textSource,
+ optionsContext,
+ error
+ });
}
// Private
@@ -248,7 +225,7 @@ class TextScanner extends EventDispatcher {
_onMouseMove(e) {
this._scanTimerClear();
- if (this._pendingLookup || DocumentUtil.isMouseButtonDown(e, 'primary')) {
+ if (DocumentUtil.isMouseButtonDown(e, 'primary')) {
return;
}
@@ -262,18 +239,7 @@ class TextScanner extends EventDispatcher {
return;
}
- const search = async () => {
- if (this._modifier === 'none') {
- if (!await this._scanTimerWait()) {
- // Aborted
- return;
- }
- }
-
- await this.searchAt(e.clientX, e.clientY, 'mouse');
- };
-
- search();
+ this._searchAtFromMouse(e.clientX, e.clientY);
}
_onMouseDown(e) {
@@ -296,6 +262,10 @@ class TextScanner extends EventDispatcher {
}
_onClick(e) {
+ if (this._searchOnClick) {
+ this._searchAt(e.clientX, e.clientY, 'click');
+ }
+
if (this._preventNextClick) {
this._preventNextClick = false;
e.preventDefault();
@@ -334,25 +304,7 @@ class TextScanner extends EventDispatcher {
this._primaryTouchIdentifier = primaryTouch.identifier;
- if (this._pendingLookup) {
- return;
- }
-
- const textSourceCurrentPrevious = this._textSourceCurrent !== null ? this._textSourceCurrent.clone() : null;
-
- this.searchAt(primaryTouch.clientX, primaryTouch.clientY, 'touchStart')
- .then(() => {
- if (
- this._textSourceCurrent === null ||
- this._textSourceCurrent.equals(textSourceCurrentPrevious)
- ) {
- return;
- }
-
- this._preventScroll = true;
- this._preventNextContextMenu = true;
- this._preventNextMouseDown = true;
- });
+ this._searchAtFromTouchStart(primaryTouch.clientX, primaryTouch.clientY);
}
_onTouchEnd(e) {
@@ -384,7 +336,7 @@ class TextScanner extends EventDispatcher {
return;
}
- this.searchAt(primaryTouch.clientX, primaryTouch.clientY, 'touchMove');
+ this._searchAt(primaryTouch.clientX, primaryTouch.clientY, 'touchMove');
e.preventDefault(); // Disable scroll
}
@@ -425,13 +377,13 @@ class TextScanner extends EventDispatcher {
[this._node, 'mousedown', this._onMouseDown.bind(this)],
[this._node, 'mousemove', this._onMouseMove.bind(this)],
[this._node, 'mouseover', this._onMouseOver.bind(this)],
- [this._node, 'mouseout', this._onMouseOut.bind(this)]
+ [this._node, 'mouseout', this._onMouseOut.bind(this)],
+ [this._node, 'click', this._onClick.bind(this)]
];
}
_getTouchEventListeners() {
return [
- [this._node, 'click', this._onClick.bind(this)],
[this._node, 'auxclick', this._onAuxClick.bind(this)],
[this._node, 'touchstart', this._onTouchStart.bind(this)],
[this._node, 'touchend', this._onTouchEnd.bind(this)],
@@ -460,4 +412,106 @@ class TextScanner extends EventDispatcher {
}
return null;
}
+
+ async _findDefinitions(textSource, optionsContext) {
+ if (textSource === null) {
+ return null;
+ }
+ if (this._searchTerms) {
+ const results = await this._findTerms(textSource, optionsContext);
+ if (results !== null) { return results; }
+ }
+ if (this._searchKanji) {
+ const results = await this._findKanji(textSource, optionsContext);
+ if (results !== null) { return results; }
+ }
+ return null;
+ }
+
+ async _findTerms(textSource, optionsContext) {
+ const scanLength = this._scanLength;
+ const sentenceExtent = this._sentenceExtent;
+ const layoutAwareScan = this._layoutAwareScan;
+ const searchText = this.getTextSourceContent(textSource, scanLength, layoutAwareScan);
+ if (searchText.length === 0) { return null; }
+
+ const {definitions, length} = await api.termsFind(searchText, {}, optionsContext);
+ if (definitions.length === 0) { return null; }
+
+ textSource.setEndOffset(length, layoutAwareScan);
+ const sentence = this._documentUtil.extractSentence(textSource, sentenceExtent, layoutAwareScan);
+
+ return {definitions, sentence, type: 'terms'};
+ }
+
+ async _findKanji(textSource, optionsContext) {
+ const sentenceExtent = this._sentenceExtent;
+ const layoutAwareScan = this._layoutAwareScan;
+ const searchText = this.getTextSourceContent(textSource, 1, layoutAwareScan);
+ if (searchText.length === 0) { return null; }
+
+ const definitions = await api.kanjiFind(searchText, optionsContext);
+ if (definitions.length === 0) { return null; }
+
+ textSource.setEndOffset(1, layoutAwareScan);
+ const sentence = this._documentUtil.extractSentence(textSource, sentenceExtent, layoutAwareScan);
+
+ return {definitions, sentence, type: 'kanji'};
+ }
+
+ async _searchAt(x, y, cause) {
+ if (this._pendingLookup) { return; }
+
+ try {
+ this._pendingLookup = true;
+ this._scanTimerClear();
+
+ if (typeof this._ignorePoint === 'function' && await this._ignorePoint(x, y)) {
+ return;
+ }
+
+ const textSource = this._documentUtil.getRangeFromPoint(x, y, this._deepContentScan);
+ try {
+ await this.search(textSource, cause);
+ } finally {
+ if (textSource !== null) {
+ textSource.cleanup();
+ }
+ }
+ } catch (e) {
+ yomichan.logError(e);
+ } finally {
+ this._pendingLookup = false;
+ }
+ }
+
+ async _searchAtFromMouse(x, y) {
+ if (this._pendingLookup) { return; }
+
+ if (this._modifier === 'none') {
+ if (!await this._scanTimerWait()) {
+ // Aborted
+ return;
+ }
+ }
+
+ await this._searchAt(x, y, 'mouse');
+ }
+
+ async _searchAtFromTouchStart(x, y) {
+ if (this._pendingLookup) { return; }
+
+ const textSourceCurrentPrevious = this._textSourceCurrent !== null ? this._textSourceCurrent.clone() : null;
+
+ await this._searchAt(x, y, 'touchStart');
+
+ if (
+ this._textSourceCurrent !== null &&
+ !this._textSourceCurrent.equals(textSourceCurrentPrevious)
+ ) {
+ this._preventScroll = true;
+ this._preventNextContextMenu = true;
+ this._preventNextMouseDown = true;
+ }
+ }
}