diff options
-rw-r--r-- | ext/js/app/frontend.js | 51 | ||||
-rw-r--r-- | ext/js/display/display.js | 29 | ||||
-rw-r--r-- | ext/js/display/query-parser.js | 35 | ||||
-rw-r--r-- | ext/js/language/text-scanner.js | 93 | ||||
-rw-r--r-- | types/ext/text-scanner.d.ts | 33 |
5 files changed, 119 insertions, 122 deletions
diff --git a/ext/js/app/frontend.js b/ext/js/app/frontend.js index de1c5a46..5f412340 100644 --- a/ext/js/app/frontend.js +++ b/ext/js/app/frontend.js @@ -183,7 +183,9 @@ export class Frontend { chrome.runtime.onMessage.addListener(this._onRuntimeMessage.bind(this)); this._textScanner.on('clear', this._onTextScannerClear.bind(this)); - this._textScanner.on('searched', this._onSearched.bind(this)); + this._textScanner.on('searchSuccess', this._onSearchSuccess.bind(this)); + this._textScanner.on('searchEmpty', this._onSearchEmpty.bind(this)); + this._textScanner.on('searchError', this._onSearchError.bind(this)); /* eslint-disable no-multi-spaces */ this._application.crossFrame.registerHandlers([ @@ -369,31 +371,36 @@ export class Frontend { } /** - * @param {import('text-scanner').SearchedEventDetails} details + * @param {import('text-scanner').EventArgument<'searchSuccess'>} details */ - _onSearched({type, dictionaryEntries, sentence, inputInfo: {eventType, passive, detail: inputInfoDetail}, textSource, optionsContext, detail, error}) { + _onSearchSuccess({type, dictionaryEntries, sentence, inputInfo: {eventType, detail: inputInfoDetail}, textSource, optionsContext, detail}) { + this._stopClearSelectionDelayed(); + let focus = (eventType === 'mouseMove'); + if (typeof inputInfoDetail === 'object' && inputInfoDetail !== null) { + const focus2 = inputInfoDetail.focus; + if (typeof focus2 === 'boolean') { focus = focus2; } + } + this._showContent(textSource, focus, dictionaryEntries, type, sentence, detail !== null ? detail.documentTitle : null, optionsContext); + } + + /** */ + _onSearchEmpty() { const scanningOptions = /** @type {import('settings').ProfileOptions} */ (this._options).scanning; + if (scanningOptions.autoHideResults) { + this._clearSelectionDelayed(scanningOptions.hideDelay, false, false); + } + } - if (error !== null) { - if (this._application.webExtension.unloaded) { - if (textSource !== null && !passive) { - this._showExtensionUnloaded(textSource); - } - } else { - log.error(error); - } - } if (type !== null && optionsContext !== null) { - this._stopClearSelectionDelayed(); - let focus = (eventType === 'mouseMove'); - if (typeof inputInfoDetail === 'object' && inputInfoDetail !== null) { - const focus2 = inputInfoDetail.focus; - if (typeof focus2 === 'boolean') { focus = focus2; } + /** + * @param {import('text-scanner').EventArgument<'searchError'>} details + */ + _onSearchError({error, textSource, inputInfo: {passive}}) { + if (this._application.webExtension.unloaded) { + if (textSource !== null && !passive) { + this._showExtensionUnloaded(textSource); } - this._showContent(textSource, focus, dictionaryEntries, type, sentence, detail !== null ? detail.documentTitle : null, optionsContext); } else { - if (scanningOptions.autoHideResults) { - this._clearSelectionDelayed(scanningOptions.hideDelay, false, false); - } + log.error(error); } } @@ -888,7 +895,7 @@ export class Frontend { } /** - * @returns {Promise<{optionsContext: import('settings').OptionsContext, detail?: import('text-scanner').SearchResultDetail}>} + * @returns {Promise<import('text-scanner').SearchContext>} */ async _getSearchContext() { let url = window.location.href; diff --git a/ext/js/display/display.js b/ext/js/display/display.js index 4114cc45..676f1a4f 100644 --- a/ext/js/display/display.js +++ b/ext/js/display/display.js @@ -1850,7 +1850,8 @@ export class Display extends EventDispatcher { this._contentTextScanner.excludeSelector = '.scan-disable,.scan-disable *'; this._contentTextScanner.prepare(); this._contentTextScanner.on('clear', this._onContentTextScannerClear.bind(this)); - this._contentTextScanner.on('searched', this._onContentTextScannerSearched.bind(this)); + this._contentTextScanner.on('searchSuccess', this._onContentTextScannerSearchSuccess.bind(this)); + this._contentTextScanner.on('searchError', this._onContentTextScannerSearchError.bind(this)); } const {scanning: scanningOptions, sentenceParsing: sentenceParsingOptions} = options; @@ -1895,15 +1896,9 @@ export class Display extends EventDispatcher { } /** - * @param {import('text-scanner').SearchedEventDetails} details + * @param {import('text-scanner').EventArgument<'searchSuccess'>} details */ - _onContentTextScannerSearched({type, dictionaryEntries, sentence, textSource, optionsContext, error}) { - if (error !== null && !this._application.webExtension.unloaded) { - log.error(error); - } - - if (type === null) { return; } - + _onContentTextScannerSearchSuccess({type, dictionaryEntries, sentence, textSource, optionsContext}) { const query = textSource.text(); const url = window.location.href; const documentTitle = document.title; @@ -1933,10 +1928,24 @@ export class Display extends EventDispatcher { } /** + * @param {import('text-scanner').EventArgument<'searchError'>} details + */ + _onContentTextScannerSearchError({error}) { + if (!this._application.webExtension.unloaded) { + log.error(error); + } + } + + /** * @type {import('display').GetSearchContextCallback} */ _getSearchContext() { - return {optionsContext: this.getOptionsContext()}; + return { + optionsContext: this.getOptionsContext(), + detail: { + documentTitle: document.title + } + }; } /** diff --git a/ext/js/display/query-parser.js b/ext/js/display/query-parser.js index daa298d2..6ec803a0 100644 --- a/ext/js/display/query-parser.js +++ b/ext/js/display/query-parser.js @@ -82,7 +82,8 @@ export class QueryParser extends EventDispatcher { prepare() { this._textScanner.prepare(); this._textScanner.on('clear', this._onTextScannerClear.bind(this)); - this._textScanner.on('searched', this._onSearched.bind(this)); + this._textScanner.on('searchSuccess', this._onSearchSuccess.bind(this)); + this._textScanner.on('searchError', this._onSearchError.bind(this)); this._queryParserModeSelect.addEventListener('change', this._onParserChange.bind(this), false); } @@ -147,39 +148,29 @@ export class QueryParser extends EventDispatcher { } /** - * @param {import('text-scanner').SearchedEventDetails} e + * @param {import('text-scanner').EventArgument<'searchSuccess'>} details */ - _onSearched(e) { - const {error} = e; - if (error !== null) { - log.error(error); - return; - } - - const { - textScanner, - type, - dictionaryEntries, - sentence, - inputInfo, - textSource, - optionsContext - } = e; - if (type === null || dictionaryEntries === null || sentence === null || optionsContext === null) { return; } - + _onSearchSuccess({type, dictionaryEntries, sentence, inputInfo, textSource, optionsContext}) { this.trigger('searched', { - textScanner, + textScanner: this._textScanner, type, dictionaryEntries, sentence, inputInfo, textSource, optionsContext, - sentenceOffset: this._getSentenceOffset(e.textSource) + sentenceOffset: this._getSentenceOffset(textSource) }); } /** + * @param {import('text-scanner').EventArgument<'searchError'>} details + */ + _onSearchError({error}) { + log.error(error); + } + + /** * @param {Event} e */ _onParserChange(e) { diff --git a/ext/js/language/text-scanner.js b/ext/js/language/text-scanner.js index d78c4c74..5b125063 100644 --- a/ext/js/language/text-scanner.js +++ b/ext/js/language/text-scanner.js @@ -395,11 +395,10 @@ export class TextScanner extends EventDispatcher { /** * @param {import('text-source').TextSource} textSource * @param {import('text-scanner').InputInfoDetail} [inputDetail] - * @returns {Promise<?import('text-scanner').SearchedEventDetails>} */ async search(textSource, inputDetail) { const inputInfo = this._createInputInfo(null, 'script', 'script', true, [], [], inputDetail); - return await this._search(textSource, this._searchTerms, this._searchKanji, inputInfo); + await this._search(textSource, this._searchTerms, this._searchKanji, inputInfo); } // Private @@ -422,23 +421,8 @@ export class TextScanner extends EventDispatcher { * @param {boolean} searchTerms * @param {boolean} searchKanji * @param {import('text-scanner').InputInfo} inputInfo - * @returns {Promise<?import('text-scanner').SearchedEventDetails>} */ async _search(textSource, searchTerms, searchKanji, inputInfo) { - /** @type {?import('dictionary').DictionaryEntry[]} */ - let dictionaryEntries = null; - /** @type {?import('display').HistoryStateSentence} */ - let sentence = null; - /** @type {?import('display').PageType} */ - let type = null; - /** @type {?Error} */ - let error = null; - let searched = false; - /** @type {?import('settings').OptionsContext} */ - let optionsContext = null; - /** @type {?import('text-scanner').SearchResultDetail} */ - let detail = null; - try { const inputInfoDetail = inputInfo.detail; const selectionRestoreInfo = ( @@ -448,56 +432,59 @@ export class TextScanner extends EventDispatcher { ); if (this._textSourceCurrent !== null && this._textSourceCurrent.hasSameStart(textSource)) { - return null; + return; } const getSearchContextPromise = this._getSearchContext(); const getSearchContextResult = getSearchContextPromise instanceof Promise ? await getSearchContextPromise : getSearchContextPromise; - const {detail: detail2} = getSearchContextResult; - if (typeof detail2 !== 'undefined') { detail = detail2; } - optionsContext = this._createOptionsContextForInput(getSearchContextResult.optionsContext, inputInfo); - - searched = true; - - let valid = false; + const {detail} = getSearchContextResult; + const optionsContext = this._createOptionsContextForInput(getSearchContextResult.optionsContext, inputInfo); + + /** @type {?import('dictionary').DictionaryEntry[]} */ + let dictionaryEntries = null; + /** @type {?import('display').HistoryStateSentence} */ + let sentence = null; + /** @type {'terms'|'kanji'} */ + let type = 'terms'; const result = await this._findDictionaryEntries(textSource, searchTerms, searchKanji, optionsContext); if (result !== null) { ({dictionaryEntries, sentence, type} = result); - valid = true; } else if (textSource !== null && textSource instanceof TextSourceElement && await this._hasJapanese(textSource.fullContent)) { dictionaryEntries = []; sentence = {text: '', offset: 0}; - type = 'terms'; - valid = true; } - if (valid) { + if (dictionaryEntries !== null && sentence !== null) { this._inputInfoCurrent = inputInfo; this.setCurrentTextSource(textSource); - if (typeof selectionRestoreInfo !== 'undefined') { - this._selectionRestoreInfo = selectionRestoreInfo; - } + this._selectionRestoreInfo = selectionRestoreInfo; + + this.trigger('searchSuccess', { + type, + dictionaryEntries, + sentence, + inputInfo, + textSource, + optionsContext, + detail + }); + } else { + this._triggerSearchEmpty(inputInfo); } - } catch (e) { - error = e instanceof Error ? e : new Error(`A search error occurred: ${e}`); + } catch (error) { + this.trigger('searchError', { + error: error instanceof Error ? error : new Error(`A search error occurred: ${error}`), + textSource, + inputInfo + }); } + } - if (!searched) { return null; } - - /** @type {import('text-scanner').SearchedEventDetails} */ - const results = { - textScanner: this, - type, - dictionaryEntries, - sentence, - inputInfo, - textSource, - optionsContext, - detail, - error - }; - this.trigger('searched', results); - return results; + /** + * @param {import('text-scanner').InputInfo} inputInfo + */ + _triggerSearchEmpty(inputInfo) { + this.trigger('searchEmpty', {inputInfo}); } /** */ @@ -1287,10 +1274,10 @@ export class TextScanner extends EventDispatcher { try { await this._search(textSource, searchTerms, searchKanji, inputInfo); } finally { - if (textSource !== null) { - textSource.cleanup(); - } + textSource.cleanup(); } + } else { + this._triggerSearchEmpty(inputInfo); } } catch (e) { log.error(e); diff --git a/types/ext/text-scanner.d.ts b/types/ext/text-scanner.d.ts index 4253d6cc..8acc780d 100644 --- a/types/ext/text-scanner.d.ts +++ b/types/ext/text-scanner.d.ts @@ -15,7 +15,6 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import type {TextScanner} from '../../ext/js/language/text-scanner'; import type {TextSourceGenerator} from '../../ext/js/dom/text-source-generator'; import type {API} from '../../ext/js/comm/api'; import type * as Dictionary from './dictionary'; @@ -94,18 +93,6 @@ export type InputConfig = { preventPenScrolling: boolean; }; -export type SearchedEventDetails = { - textScanner: TextScanner; - type: Display.PageType | null; - dictionaryEntries: Dictionary.DictionaryEntry[] | null; - sentence: Display.HistoryStateSentence | null; - inputInfo: InputInfo; - textSource: TextSource.TextSource; - optionsContext: Settings.OptionsContext | null; - detail: SearchResultDetail | null; - error: Error | null; -}; - export type InputInfo = { input: InputConfig | null; pointerType: PointerType; @@ -122,10 +109,26 @@ export type InputInfoDetail = { }; export type Events = { - searched: SearchedEventDetails; clear: { reason: ClearReason; }; + searchSuccess: { + type: 'terms' | 'kanji'; + dictionaryEntries: Dictionary.DictionaryEntry[]; + sentence: Display.HistoryStateSentence; + inputInfo: InputInfo; + textSource: TextSource.TextSource; + optionsContext: Settings.OptionsContext; + detail: SearchResultDetail; + }; + searchEmpty: { + inputInfo: InputInfo; + }; + searchError: { + error: Error; + textSource: TextSource.TextSource; + inputInfo: InputInfo; + }; }; export type ClearReason = 'mousedown'; @@ -153,7 +156,7 @@ export type ConstructorDetails = { export type SearchContext = { optionsContext: Settings.OptionsContext; - detail?: SearchResultDetail; + detail: SearchResultDetail; }; export type SelectionRestoreInfo = { |