From e24075e1a91ab8b58bb5836cf7abcefae5cbf8c3 Mon Sep 17 00:00:00 2001 From: James Maa Date: Mon, 10 Jun 2024 10:59:54 -0700 Subject: Add context menu interface for Yomitan (#1028) * --wip-- [skip ci] * Draft * Remove weird code * Use existing API instead of dulpicating * Small improvements * remove console.log * remove console.log * Add setting for contextMenu * Fix test * Address comments * Add option-util upgrade * fix option-utils --- ext/js/app/frontend.js | 13 +++++++++++-- ext/js/background/backend.js | 15 +++++++++++++++ ext/js/data/options-util.js | 11 +++++++++++ ext/js/language/text-scanner.js | 11 +++++++---- 4 files changed, 44 insertions(+), 6 deletions(-) (limited to 'ext/js') diff --git a/ext/js/app/frontend.js b/ext/js/app/frontend.js index 39176475..bdb8cfc5 100644 --- a/ext/js/app/frontend.js +++ b/ext/js/app/frontend.js @@ -115,6 +115,7 @@ export class Frontend { ['frontendRequestReadyBroadcast', this._onMessageRequestFrontendReadyBroadcast.bind(this)], ['frontendSetAllVisibleOverride', this._onApiSetAllVisibleOverride.bind(this)], ['frontendClearAllVisibleOverride', this._onApiClearAllVisibleOverride.bind(this)], + ['frontendScanSelectedText', this._onApiScanSelectedText.bind(this)], ]); this._hotkeyHandler.registerActions([ @@ -260,6 +261,13 @@ export class Frontend { void this._scanSelectedText(false, true); } + /** + * @returns {void} + */ + _onApiScanSelectedText() { + void this._scanSelectedText(false, true, true); + } + /** * @returns {void} */ @@ -934,13 +942,14 @@ export class Frontend { /** * @param {boolean} allowEmptyRange * @param {boolean} disallowExpandSelection + * @param {boolean} showEmpty show empty popup if no results are found * @returns {Promise} */ - async _scanSelectedText(allowEmptyRange, disallowExpandSelection) { + async _scanSelectedText(allowEmptyRange, disallowExpandSelection, showEmpty = false) { const range = this._getFirstSelectionRange(allowEmptyRange); if (range === null) { return false; } const source = disallowExpandSelection ? TextSourceRange.createLazy(range) : TextSourceRange.create(range); - await this._textScanner.search(source, {focus: true, restoreSelection: true}); + await this._textScanner.search(source, {focus: true, restoreSelection: true}, showEmpty); return true; } diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index a04566c9..88912b70 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -1310,6 +1310,21 @@ export class Backend { this._clipboardMonitor.stop(); } + if (options.general.enableContextMenuScanSelected) { + chrome.contextMenus.create({ + id: 'yomitan_lookup', + title: 'Lookup in Yomitan', + contexts: ['selection'], + }); + chrome.contextMenus.onClicked.addListener((info) => { + if (info.selectionText) { + this._sendMessageAllTabsIgnoreResponse({action: 'frontendScanSelectedText'}); + } + }); + } else { + chrome.contextMenus.remove('yomitan_lookup', () => this._checkLastError(chrome.runtime.lastError)); + } + void this._accessibilityController.update(this._getOptionsFull(false)); this._sendMessageAllTabsIgnoreResponse({action: 'applicationOptionsUpdated', params: {source}}); diff --git a/ext/js/data/options-util.js b/ext/js/data/options-util.js index 5ef30adb..c6bdf025 100644 --- a/ext/js/data/options-util.js +++ b/ext/js/data/options-util.js @@ -546,6 +546,7 @@ export class OptionsUtil { this._updateVersion36, this._updateVersion37, this._updateVersion38, + this._updateVersion39, ]; /* eslint-enable @typescript-eslint/unbound-method */ if (typeof targetVersion === 'number' && targetVersion < result.length) { @@ -1313,6 +1314,16 @@ export class OptionsUtil { await this._applyAnkiFieldTemplatesPatch(options, '/data/templates/anki-field-templates-upgrade-v38.handlebars'); } + /** + * - Add new setting enableContextMenuScanSelected + * @type {import('options-util').UpdateFunction} + */ + async _updateVersion39(options) { + for (const profile of options.profiles) { + profile.options.general.enableContextMenuScanSelected = true; + } + } + /** * @param {string} url * @returns {Promise} diff --git a/ext/js/language/text-scanner.js b/ext/js/language/text-scanner.js index aba44644..fdc33400 100644 --- a/ext/js/language/text-scanner.js +++ b/ext/js/language/text-scanner.js @@ -411,10 +411,11 @@ export class TextScanner extends EventDispatcher { /** * @param {import('text-source').TextSource} textSource * @param {import('text-scanner').InputInfoDetail} [inputDetail] + * @param {boolean} showEmpty */ - async search(textSource, inputDetail) { + async search(textSource, inputDetail, showEmpty = false) { const inputInfo = this._createInputInfo(null, 'script', 'script', true, [], [], inputDetail); - await this._search(textSource, this._searchTerms, this._searchKanji, inputInfo); + await this._search(textSource, this._searchTerms, this._searchKanji, inputInfo, showEmpty); } // Private @@ -437,8 +438,9 @@ export class TextScanner extends EventDispatcher { * @param {boolean} searchTerms * @param {boolean} searchKanji * @param {import('text-scanner').InputInfo} inputInfo + * @param {boolean} showEmpty shows a "No results found" popup if no results are found */ - async _search(textSource, searchTerms, searchKanji, inputInfo) { + async _search(textSource, searchTerms, searchKanji, inputInfo, showEmpty = false) { try { const inputInfoDetail = inputInfo.detail; const selectionRestoreInfo = ( @@ -465,7 +467,8 @@ export class TextScanner extends EventDispatcher { const result = await this._findDictionaryEntries(textSource, searchTerms, searchKanji, optionsContext); if (result !== null) { ({dictionaryEntries, sentence, type} = result); - } else if (textSource !== null && textSource instanceof TextSourceElement && await this._isTextLookupWorthy(textSource.fullContent)) { + } else if (showEmpty || (textSource !== null && textSource instanceof TextSourceElement && await this._isTextLookupWorthy(textSource.fullContent))) { + // Shows a "No results found" message dictionaryEntries = []; sentence = {text: '', offset: 0}; } -- cgit v1.2.3