diff options
author | James Maa <jmaa@berkeley.edu> | 2024-06-10 10:59:54 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-06-10 17:59:54 +0000 |
commit | e24075e1a91ab8b58bb5836cf7abcefae5cbf8c3 (patch) | |
tree | 57881c2de6a9d75185a390741d404d61d5c9ce69 | |
parent | 8eea3661714c64857aa32a8662f7cca6674dd3a4 (diff) |
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
-rw-r--r-- | dev/data/manifest-variants.json | 3 | ||||
-rw-r--r-- | ext/data/schemas/options-schema.json | 5 | ||||
-rw-r--r-- | ext/js/app/frontend.js | 13 | ||||
-rw-r--r-- | ext/js/background/backend.js | 15 | ||||
-rw-r--r-- | ext/js/data/options-util.js | 11 | ||||
-rw-r--r-- | ext/js/language/text-scanner.js | 11 | ||||
-rw-r--r-- | ext/settings.html | 8 | ||||
-rw-r--r-- | test/options-util.test.js | 3 | ||||
-rw-r--r-- | types/ext/application.d.ts | 4 | ||||
-rw-r--r-- | 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([ @@ -263,6 +264,13 @@ export class Frontend { /** * @returns {void} */ + _onApiScanSelectedText() { + void this._scanSelectedText(false, true, true); + } + + /** + * @returns {void} + */ _onActionScanTextAtSelection() { void this._scanSelectedText(false, false); } @@ -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<boolean>} */ - 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) { @@ -1314,6 +1315,16 @@ export class OptionsUtil { } /** + * - 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<chrome.tabs.Tab>} */ 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 @@ <label class="toggle"><input type="checkbox" data-setting="general.showGuide"><span class="toggle-body"><span class="toggle-track"></span><span class="toggle-knob"></span></span></label> </div> </div></div> + <div class="settings-item advanced-only"><div class="settings-item-inner"> + <div class="settings-item-left"> + <div class="settings-item-label">Show "Lookup in Yomitan" in right-click menu</div> + </div> + <div class="settings-item-right"> + <label class="toggle"><input type="checkbox" data-setting="general.enableContextMenuScanSelected"><span class="toggle-body"><span class="toggle-track"></span><span class="toggle-knob"></span></span></label> + </div> + </div></div> <div class="settings-item advanced-only"><div class="settings-item-inner settings-item-inner-wrappable"> <div class="settings-item-left"> <div class="settings-item-label">Maximum number of results</div> 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; |