diff options
Diffstat (limited to 'ext/bg/js')
-rw-r--r-- | ext/bg/js/backend.js | 40 | ||||
-rw-r--r-- | ext/bg/js/profile-conditions.js | 256 | ||||
-rw-r--r-- | ext/bg/js/settings/conditions-ui.js | 6 | ||||
-rw-r--r-- | ext/bg/js/settings/main.js | 19 | ||||
-rw-r--r-- | ext/bg/js/settings/profiles.js | 4 |
5 files changed, 169 insertions, 156 deletions
diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 9936baf8..557ceb29 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -24,6 +24,7 @@ * ClipboardMonitor * Database * DictionaryImporter + * Environment * JsonSchema * Mecab * ObjectPropertyAccessor @@ -35,6 +36,7 @@ * optionsLoad * optionsSave * profileConditionsDescriptor + * profileConditionsDescriptorPromise * requestJson * requestText * utilIsolate @@ -42,6 +44,7 @@ class Backend { constructor() { + this.environment = new Environment(); this.database = new Database(); this.dictionaryImporter = new DictionaryImporter(); this.translator = new Translator(this.database); @@ -100,7 +103,7 @@ class Backend { ['broadcastTab', {async: false, contentScript: true, handler: this._onApiBroadcastTab.bind(this)}], ['frameInformationGet', {async: true, contentScript: true, handler: this._onApiFrameInformationGet.bind(this)}], ['injectStylesheet', {async: true, contentScript: true, handler: this._onApiInjectStylesheet.bind(this)}], - ['getEnvironmentInfo', {async: true, contentScript: true, handler: this._onApiGetEnvironmentInfo.bind(this)}], + ['getEnvironmentInfo', {async: false, contentScript: true, handler: this._onApiGetEnvironmentInfo.bind(this)}], ['clipboardGet', {async: true, contentScript: true, handler: this._onApiClipboardGet.bind(this)}], ['getDisplayTemplatesHtml', {async: true, contentScript: true, handler: this._onApiGetDisplayTemplatesHtml.bind(this)}], ['getQueryParserTemplatesHtml', {async: true, contentScript: true, handler: this._onApiGetQueryParserTemplatesHtml.bind(this)}], @@ -140,9 +143,12 @@ class Backend { }, 1000); this._updateBadge(); + await this.environment.prepare(); await this.database.prepare(); await this.translator.prepare(); + await profileConditionsDescriptorPromise; + this.optionsSchema = await requestJson(chrome.runtime.getURL('/bg/data/options-schema.json'), 'GET'); this.defaultAnkiFieldTemplates = await requestText(chrome.runtime.getURL('/bg/data/default-anki-field-templates.handlebars'), 'GET'); this.options = await optionsLoad(); @@ -635,15 +641,8 @@ class Backend { }); } - async _onApiGetEnvironmentInfo() { - const browser = await Backend._getBrowser(); - const platform = await new Promise((resolve) => chrome.runtime.getPlatformInfo(resolve)); - return { - browser, - platform: { - os: platform.os - } - }; + _onApiGetEnvironmentInfo() { + return this.environment.getInfo(); } async _onApiClipboardGet() { @@ -659,7 +658,7 @@ class Backend { being an extension with clipboard permissions. It effectively asks for the non-extension permission for clipboard access. */ - const browser = await Backend._getBrowser(); + const {browser} = this.environment.getInfo(); if (browser === 'firefox' || browser === 'firefox-mobile') { return await navigator.clipboard.readText(); } else { @@ -1211,23 +1210,4 @@ class Backend { // Edge throws exception for no reason here. } } - - static async _getBrowser() { - if (EXTENSION_IS_BROWSER_EDGE) { - return 'edge'; - } - if (typeof browser !== 'undefined') { - try { - const info = await browser.runtime.getBrowserInfo(); - if (info.name === 'Fennec') { - return 'firefox-mobile'; - } - } catch (e) { - // NOP - } - return 'firefox'; - } else { - return 'chrome'; - } - } } diff --git a/ext/bg/js/profile-conditions.js b/ext/bg/js/profile-conditions.js index 32309c64..97e09f1c 100644 --- a/ext/bg/js/profile-conditions.js +++ b/ext/bg/js/profile-conditions.js @@ -15,6 +15,9 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +/* global + * Environment + */ function _profileConditionTestDomain(urlDomain, domain) { return ( @@ -36,135 +39,140 @@ function _profileConditionTestDomainList(url, domainList) { return false; } -const _profileModifierKeys = [ - {optionValue: 'alt', name: 'Alt'}, - {optionValue: 'ctrl', name: 'Ctrl'}, - {optionValue: 'shift', name: 'Shift'} -]; +let profileConditionsDescriptor = null; -if (!hasOwn(window, 'netscape')) { - _profileModifierKeys.push({optionValue: 'meta', name: 'Meta'}); -} +const profileConditionsDescriptorPromise = (async () => { + const environment = new Environment(); + await environment.prepare(); -const _profileModifierValueToName = new Map( - _profileModifierKeys.map(({optionValue, name}) => [optionValue, name]) -); + const modifiers = environment.getInfo().modifiers; + const modifierSeparator = modifiers.separator; + const modifierKeyValues = modifiers.keys.map( + ({value, name}) => ({optionValue: value, name}) + ); -const _profileModifierNameToValue = new Map( - _profileModifierKeys.map(({optionValue, name}) => [name, optionValue]) -); + const modifierValueToName = new Map( + modifierKeyValues.map(({optionValue, name}) => [optionValue, name]) + ); -const profileConditionsDescriptor = { - popupLevel: { - name: 'Popup Level', - description: 'Use profile depending on the level of the popup.', - placeholder: 'Number', - type: 'number', - step: 1, - defaultValue: 0, - defaultOperator: 'equal', - transform: (optionValue) => parseInt(optionValue, 10), - transformReverse: (transformedOptionValue) => `${transformedOptionValue}`, - validateTransformed: (transformedOptionValue) => Number.isFinite(transformedOptionValue), - operators: { - equal: { - name: '=', - test: ({depth}, optionValue) => (depth === optionValue) - }, - notEqual: { - name: '\u2260', - test: ({depth}, optionValue) => (depth !== optionValue) - }, - lessThan: { - name: '<', - test: ({depth}, optionValue) => (depth < optionValue) - }, - greaterThan: { - name: '>', - test: ({depth}, optionValue) => (depth > optionValue) - }, - lessThanOrEqual: { - name: '\u2264', - test: ({depth}, optionValue) => (depth <= optionValue) - }, - greaterThanOrEqual: { - name: '\u2265', - test: ({depth}, optionValue) => (depth >= optionValue) + const modifierNameToValue = new Map( + modifierKeyValues.map(({optionValue, name}) => [name, optionValue]) + ); + + profileConditionsDescriptor = { + popupLevel: { + name: 'Popup Level', + description: 'Use profile depending on the level of the popup.', + placeholder: 'Number', + type: 'number', + step: 1, + defaultValue: 0, + defaultOperator: 'equal', + transform: (optionValue) => parseInt(optionValue, 10), + transformReverse: (transformedOptionValue) => `${transformedOptionValue}`, + validateTransformed: (transformedOptionValue) => Number.isFinite(transformedOptionValue), + operators: { + equal: { + name: '=', + test: ({depth}, optionValue) => (depth === optionValue) + }, + notEqual: { + name: '\u2260', + test: ({depth}, optionValue) => (depth !== optionValue) + }, + lessThan: { + name: '<', + test: ({depth}, optionValue) => (depth < optionValue) + }, + greaterThan: { + name: '>', + test: ({depth}, optionValue) => (depth > optionValue) + }, + lessThanOrEqual: { + name: '\u2264', + test: ({depth}, optionValue) => (depth <= optionValue) + }, + greaterThanOrEqual: { + name: '\u2265', + test: ({depth}, optionValue) => (depth >= optionValue) + } } - } - }, - url: { - name: 'URL', - description: 'Use profile depending on the URL of the current website.', - defaultOperator: 'matchDomain', - operators: { - matchDomain: { - name: 'Matches Domain', - placeholder: 'Comma separated list of domains', - defaultValue: 'example.com', - transformCache: {}, - transform: (optionValue) => optionValue.split(/[,;\s]+/).map((v) => v.trim().toLowerCase()).filter((v) => v.length > 0), - transformReverse: (transformedOptionValue) => transformedOptionValue.join(', '), - validateTransformed: (transformedOptionValue) => (transformedOptionValue.length > 0), - test: ({url}, transformedOptionValue) => _profileConditionTestDomainList(url, transformedOptionValue) - }, - matchRegExp: { - name: 'Matches RegExp', - placeholder: 'Regular expression', - defaultValue: 'example\\.com', - transformCache: {}, - transform: (optionValue) => new RegExp(optionValue, 'i'), - transformReverse: (transformedOptionValue) => transformedOptionValue.source, - test: ({url}, transformedOptionValue) => (transformedOptionValue !== null && transformedOptionValue.test(url)) + }, + url: { + name: 'URL', + description: 'Use profile depending on the URL of the current website.', + defaultOperator: 'matchDomain', + operators: { + matchDomain: { + name: 'Matches Domain', + placeholder: 'Comma separated list of domains', + defaultValue: 'example.com', + transformCache: {}, + transform: (optionValue) => optionValue.split(/[,;\s]+/).map((v) => v.trim().toLowerCase()).filter((v) => v.length > 0), + transformReverse: (transformedOptionValue) => transformedOptionValue.join(', '), + validateTransformed: (transformedOptionValue) => (transformedOptionValue.length > 0), + test: ({url}, transformedOptionValue) => _profileConditionTestDomainList(url, transformedOptionValue) + }, + matchRegExp: { + name: 'Matches RegExp', + placeholder: 'Regular expression', + defaultValue: 'example\\.com', + transformCache: {}, + transform: (optionValue) => new RegExp(optionValue, 'i'), + transformReverse: (transformedOptionValue) => transformedOptionValue.source, + test: ({url}, transformedOptionValue) => (transformedOptionValue !== null && transformedOptionValue.test(url)) + } } - } - }, - modifierKeys: { - name: 'Modifier Keys', - description: 'Use profile depending on the active modifier keys.', - values: _profileModifierKeys, - defaultOperator: 'are', - operators: { - are: { - name: 'are', - placeholder: 'Press one or more modifier keys here', - defaultValue: [], - type: 'keyMulti', - transformInput: (optionValue) => optionValue - .split(' + ') - .filter((v) => v.length > 0) - .map((v) => _profileModifierNameToValue.get(v)), - transformReverse: (transformedOptionValue) => transformedOptionValue - .map((v) => _profileModifierValueToName.get(v)) - .join(' + '), - test: ({modifierKeys}, optionValue) => areSetsEqual(new Set(modifierKeys), new Set(optionValue)) - }, - areNot: { - name: 'are not', - placeholder: 'Press one or more modifier keys here', - defaultValue: [], - type: 'keyMulti', - transformInput: (optionValue) => optionValue - .split(' + ') - .filter((v) => v.length > 0) - .map((v) => _profileModifierNameToValue.get(v)), - transformReverse: (transformedOptionValue) => transformedOptionValue - .map((v) => _profileModifierValueToName.get(v)) - .join(' + '), - test: ({modifierKeys}, optionValue) => !areSetsEqual(new Set(modifierKeys), new Set(optionValue)) - }, - include: { - name: 'include', - type: 'select', - defaultValue: 'alt', - test: ({modifierKeys}, optionValue) => modifierKeys.includes(optionValue) - }, - notInclude: { - name: 'don\'t include', - type: 'select', - defaultValue: 'alt', - test: ({modifierKeys}, optionValue) => !modifierKeys.includes(optionValue) + }, + modifierKeys: { + name: 'Modifier Keys', + description: 'Use profile depending on the active modifier keys.', + values: modifierKeyValues, + defaultOperator: 'are', + operators: { + are: { + name: 'are', + placeholder: 'Press one or more modifier keys here', + defaultValue: [], + type: 'keyMulti', + keySeparator: modifierSeparator, + transformInput: (optionValue) => optionValue + .split(modifierSeparator) + .filter((v) => v.length > 0) + .map((v) => modifierNameToValue.get(v)), + transformReverse: (transformedOptionValue) => transformedOptionValue + .map((v) => modifierValueToName.get(v)) + .join(modifierSeparator), + test: ({modifierKeys}, optionValue) => areSetsEqual(new Set(modifierKeys), new Set(optionValue)) + }, + areNot: { + name: 'are not', + placeholder: 'Press one or more modifier keys here', + defaultValue: [], + type: 'keyMulti', + keySeparator: modifierSeparator, + transformInput: (optionValue) => optionValue + .split(modifierSeparator) + .filter((v) => v.length > 0) + .map((v) => modifierNameToValue.get(v)), + transformReverse: (transformedOptionValue) => transformedOptionValue + .map((v) => modifierValueToName.get(v)) + .join(modifierSeparator), + test: ({modifierKeys}, optionValue) => !areSetsEqual(new Set(modifierKeys), new Set(optionValue)) + }, + include: { + name: 'include', + type: 'select', + defaultValue: 'alt', + test: ({modifierKeys}, optionValue) => modifierKeys.includes(optionValue) + }, + notInclude: { + name: 'don\'t include', + type: 'select', + defaultValue: 'alt', + test: ({modifierKeys}, optionValue) => !modifierKeys.includes(optionValue) + } } } - } -}; + }; +})(); diff --git a/ext/bg/js/settings/conditions-ui.js b/ext/bg/js/settings/conditions-ui.js index 0670de5a..031689a7 100644 --- a/ext/bg/js/settings/conditions-ui.js +++ b/ext/bg/js/settings/conditions-ui.js @@ -310,10 +310,14 @@ ConditionsUI.Condition = class Condition { inputInner.prop('readonly', true); let values = []; + let keySeparator = ' + '; for (const object of objects) { if (hasOwn(object, 'values')) { values = object.values; } + if (hasOwn(object, 'keySeparator')) { + keySeparator = object.keySeparator; + } } const pressedKeyIndices = new Set(); @@ -347,7 +351,7 @@ ConditionsUI.Condition = class Condition { } } - const inputValue = [...pressedKeyIndices].map((i) => values[i].name).join(' + '); + const inputValue = [...pressedKeyIndices].map((i) => values[i].name).join(keySeparator); inputInner.val(inputValue); inputInner.change(); }; diff --git a/ext/bg/js/settings/main.js b/ext/bg/js/settings/main.js index cf75d629..61395b1c 100644 --- a/ext/bg/js/settings/main.js +++ b/ext/bg/js/settings/main.js @@ -22,6 +22,7 @@ * ankiTemplatesInitialize * ankiTemplatesUpdateValue * apiForwardLogsToBackend + * apiGetEnvironmentInfo * apiOptionsSave * appearanceInitialize * audioSettingsInitialize @@ -285,6 +286,23 @@ function showExtensionInformation() { node.textContent = `${manifest.name} v${manifest.version}`; } +async function settingsPopulateModifierKeys() { + const scanModifierKeySelect = document.querySelector('#scan-modifier-key'); + scanModifierKeySelect.textContent = ''; + + const environment = await apiGetEnvironmentInfo(); + const modifierKeys = [ + {value: 'none', name: 'None'}, + ...environment.modifiers.keys + ]; + for (const {value, name} of modifierKeys) { + const option = document.createElement('option'); + option.value = value; + option.textContent = name; + scanModifierKeySelect.appendChild(option); + } +} + async function onReady() { apiForwardLogsToBackend(); @@ -292,6 +310,7 @@ async function onReady() { showExtensionInformation(); + await settingsPopulateModifierKeys(); formSetupEventListeners(); appearanceInitialize(); await audioSettingsInitialize(); diff --git a/ext/bg/js/settings/profiles.js b/ext/bg/js/settings/profiles.js index 3f4b1da7..bdf5a13d 100644 --- a/ext/bg/js/settings/profiles.js +++ b/ext/bg/js/settings/profiles.js @@ -23,6 +23,7 @@ * getOptionsFullMutable * getOptionsMutable * profileConditionsDescriptor + * profileConditionsDescriptorPromise * settingsSaveOptions * utilBackgroundIsolate */ @@ -98,6 +99,7 @@ async function profileFormWrite(optionsFull) { profileConditionsContainer.cleanup(); } + await profileConditionsDescriptorPromise; profileConditionsContainer = new ConditionsUI.Container( profileConditionsDescriptor, 'popupLevel', @@ -128,7 +130,7 @@ function profileOptionsPopulateSelect(select, profiles, currentValue, ignoreIndi } async function profileOptionsUpdateTarget(optionsFull) { - profileFormWrite(optionsFull); + await profileFormWrite(optionsFull); const optionsContext = getOptionsContext(); const options = await getOptionsMutable(optionsContext); |