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 --- dev/data/manifest-variants.json | 3 ++- ext/data/schemas/options-schema.json | 5 +++++ 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 +++++++---- ext/settings.html | 8 ++++++++ test/options-util.test.js | 3 ++- types/ext/application.d.ts | 4 ++++ types/ext/settings.d.ts | 1 + 10 files changed, 66 insertions(+), 8 deletions(-) diff --git a/dev/data/manifest-variants.json b/dev/data/manifest-variants.json index 93841f46..3c299965 100644 --- a/dev/data/manifest-variants.json +++ b/dev/data/manifest-variants.json @@ -64,7 +64,8 @@ "unlimitedStorage", "declarativeNetRequest", "scripting", - "offscreen" + "offscreen", + "contextMenus" ], "optional_permissions": [ "clipboardRead", diff --git a/ext/data/schemas/options-schema.json b/ext/data/schemas/options-schema.json index 5db4c087..1bbdf183 100644 --- a/ext/data/schemas/options-schema.json +++ b/ext/data/schemas/options-schema.json @@ -99,6 +99,7 @@ "popupScaleRelativeToPageZoom", "popupScaleRelativeToVisualViewport", "showGuide", + "enableContextMenuScanSelected", "compactTags", "glossaryLayoutMode", "mainDictionary", @@ -206,6 +207,10 @@ "type": "boolean", "default": true }, + "enableContextMenuScanSelected": { + "type": "boolean", + "default": true + }, "compactTags": { "type": "boolean", "default": false 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}; } diff --git a/ext/settings.html b/ext/settings.html index 956e2664..0ea4653a 100644 --- a/ext/settings.html +++ b/ext/settings.html @@ -148,6 +148,14 @@ +
+
+
Show "Lookup in Yomitan" in right-click menu
+
+
+ +
+
Maximum number of results
diff --git a/test/options-util.test.js b/test/options-util.test.js index af4daef9..54bae173 100644 --- a/test/options-util.test.js +++ b/test/options-util.test.js @@ -259,6 +259,7 @@ function createProfileOptionsUpdatedTestData1() { popupScaleRelativeToPageZoom: false, popupScaleRelativeToVisualViewport: true, showGuide: true, + enableContextMenuScanSelected: true, compactTags: false, glossaryLayoutMode: 'default', mainDictionary: '', @@ -605,7 +606,7 @@ function createOptionsUpdatedTestData1() { }, ], profileCurrent: 0, - version: 38, + version: 39, global: { database: { prefixWildcardsSupported: false, diff --git a/types/ext/application.d.ts b/types/ext/application.d.ts index 8d80894d..8dc05da3 100644 --- a/types/ext/application.d.ts +++ b/types/ext/application.d.ts @@ -105,6 +105,10 @@ export type ApiSurface = { }; return: void; }; + frontendScanSelectedText: { + params: void; + return: void; + }; frameEndpointReady: { params: FrameEndpointReadyDetails; return: void; diff --git a/types/ext/settings.d.ts b/types/ext/settings.d.ts index b69679b0..80fb8a1f 100644 --- a/types/ext/settings.d.ts +++ b/types/ext/settings.d.ts @@ -119,6 +119,7 @@ export type GeneralOptions = { popupScaleRelativeToPageZoom: boolean; popupScaleRelativeToVisualViewport: boolean; showGuide: boolean; + enableContextMenuScanSelected: boolean; compactTags: boolean; glossaryLayoutMode: GlossaryLayoutMode; mainDictionary: string; -- cgit v1.2.3