Modifier key platform names (#519)
* wip * add environment class * use Environment class * use Environment for scanning modifier options * remove Environment in favor of API * await promise * use modifier symbols on macOS * fix key separator issues * if else to switch * simplify variable names
This commit is contained in:
parent
48cf646973
commit
d6a3825a38
@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
<script src="/mixed/js/core.js"></script>
|
<script src="/mixed/js/core.js"></script>
|
||||||
<script src="/mixed/js/dom.js"></script>
|
<script src="/mixed/js/dom.js"></script>
|
||||||
|
<script src="/mixed/js/environment.js"></script>
|
||||||
<script src="/mixed/js/japanese.js"></script>
|
<script src="/mixed/js/japanese.js"></script>
|
||||||
|
|
||||||
<script src="/bg/js/anki.js"></script>
|
<script src="/bg/js/anki.js"></script>
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
* ClipboardMonitor
|
* ClipboardMonitor
|
||||||
* Database
|
* Database
|
||||||
* DictionaryImporter
|
* DictionaryImporter
|
||||||
|
* Environment
|
||||||
* JsonSchema
|
* JsonSchema
|
||||||
* Mecab
|
* Mecab
|
||||||
* ObjectPropertyAccessor
|
* ObjectPropertyAccessor
|
||||||
@ -35,6 +36,7 @@
|
|||||||
* optionsLoad
|
* optionsLoad
|
||||||
* optionsSave
|
* optionsSave
|
||||||
* profileConditionsDescriptor
|
* profileConditionsDescriptor
|
||||||
|
* profileConditionsDescriptorPromise
|
||||||
* requestJson
|
* requestJson
|
||||||
* requestText
|
* requestText
|
||||||
* utilIsolate
|
* utilIsolate
|
||||||
@ -42,6 +44,7 @@
|
|||||||
|
|
||||||
class Backend {
|
class Backend {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
this.environment = new Environment();
|
||||||
this.database = new Database();
|
this.database = new Database();
|
||||||
this.dictionaryImporter = new DictionaryImporter();
|
this.dictionaryImporter = new DictionaryImporter();
|
||||||
this.translator = new Translator(this.database);
|
this.translator = new Translator(this.database);
|
||||||
@ -100,7 +103,7 @@ class Backend {
|
|||||||
['broadcastTab', {async: false, contentScript: true, handler: this._onApiBroadcastTab.bind(this)}],
|
['broadcastTab', {async: false, contentScript: true, handler: this._onApiBroadcastTab.bind(this)}],
|
||||||
['frameInformationGet', {async: true, contentScript: true, handler: this._onApiFrameInformationGet.bind(this)}],
|
['frameInformationGet', {async: true, contentScript: true, handler: this._onApiFrameInformationGet.bind(this)}],
|
||||||
['injectStylesheet', {async: true, contentScript: true, handler: this._onApiInjectStylesheet.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)}],
|
['clipboardGet', {async: true, contentScript: true, handler: this._onApiClipboardGet.bind(this)}],
|
||||||
['getDisplayTemplatesHtml', {async: true, contentScript: true, handler: this._onApiGetDisplayTemplatesHtml.bind(this)}],
|
['getDisplayTemplatesHtml', {async: true, contentScript: true, handler: this._onApiGetDisplayTemplatesHtml.bind(this)}],
|
||||||
['getQueryParserTemplatesHtml', {async: true, contentScript: true, handler: this._onApiGetQueryParserTemplatesHtml.bind(this)}],
|
['getQueryParserTemplatesHtml', {async: true, contentScript: true, handler: this._onApiGetQueryParserTemplatesHtml.bind(this)}],
|
||||||
@ -140,9 +143,12 @@ class Backend {
|
|||||||
}, 1000);
|
}, 1000);
|
||||||
this._updateBadge();
|
this._updateBadge();
|
||||||
|
|
||||||
|
await this.environment.prepare();
|
||||||
await this.database.prepare();
|
await this.database.prepare();
|
||||||
await this.translator.prepare();
|
await this.translator.prepare();
|
||||||
|
|
||||||
|
await profileConditionsDescriptorPromise;
|
||||||
|
|
||||||
this.optionsSchema = await requestJson(chrome.runtime.getURL('/bg/data/options-schema.json'), 'GET');
|
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.defaultAnkiFieldTemplates = await requestText(chrome.runtime.getURL('/bg/data/default-anki-field-templates.handlebars'), 'GET');
|
||||||
this.options = await optionsLoad();
|
this.options = await optionsLoad();
|
||||||
@ -635,15 +641,8 @@ class Backend {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onApiGetEnvironmentInfo() {
|
_onApiGetEnvironmentInfo() {
|
||||||
const browser = await Backend._getBrowser();
|
return this.environment.getInfo();
|
||||||
const platform = await new Promise((resolve) => chrome.runtime.getPlatformInfo(resolve));
|
|
||||||
return {
|
|
||||||
browser,
|
|
||||||
platform: {
|
|
||||||
os: platform.os
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onApiClipboardGet() {
|
async _onApiClipboardGet() {
|
||||||
@ -659,7 +658,7 @@ class Backend {
|
|||||||
being an extension with clipboard permissions. It effectively asks for the
|
being an extension with clipboard permissions. It effectively asks for the
|
||||||
non-extension permission for clipboard access.
|
non-extension permission for clipboard access.
|
||||||
*/
|
*/
|
||||||
const browser = await Backend._getBrowser();
|
const {browser} = this.environment.getInfo();
|
||||||
if (browser === 'firefox' || browser === 'firefox-mobile') {
|
if (browser === 'firefox' || browser === 'firefox-mobile') {
|
||||||
return await navigator.clipboard.readText();
|
return await navigator.clipboard.readText();
|
||||||
} else {
|
} else {
|
||||||
@ -1211,23 +1210,4 @@ class Backend {
|
|||||||
// Edge throws exception for no reason here.
|
// 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';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,9 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* global
|
||||||
|
* Environment
|
||||||
|
*/
|
||||||
|
|
||||||
function _profileConditionTestDomain(urlDomain, domain) {
|
function _profileConditionTestDomain(urlDomain, domain) {
|
||||||
return (
|
return (
|
||||||
@ -36,25 +39,27 @@ function _profileConditionTestDomainList(url, domainList) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const _profileModifierKeys = [
|
let profileConditionsDescriptor = null;
|
||||||
{optionValue: 'alt', name: 'Alt'},
|
|
||||||
{optionValue: 'ctrl', name: 'Ctrl'},
|
|
||||||
{optionValue: 'shift', name: 'Shift'}
|
|
||||||
];
|
|
||||||
|
|
||||||
if (!hasOwn(window, 'netscape')) {
|
const profileConditionsDescriptorPromise = (async () => {
|
||||||
_profileModifierKeys.push({optionValue: 'meta', name: 'Meta'});
|
const environment = new Environment();
|
||||||
}
|
await environment.prepare();
|
||||||
|
|
||||||
const _profileModifierValueToName = new Map(
|
const modifiers = environment.getInfo().modifiers;
|
||||||
_profileModifierKeys.map(({optionValue, name}) => [optionValue, name])
|
const modifierSeparator = modifiers.separator;
|
||||||
|
const modifierKeyValues = modifiers.keys.map(
|
||||||
|
({value, name}) => ({optionValue: value, name})
|
||||||
);
|
);
|
||||||
|
|
||||||
const _profileModifierNameToValue = new Map(
|
const modifierValueToName = new Map(
|
||||||
_profileModifierKeys.map(({optionValue, name}) => [name, optionValue])
|
modifierKeyValues.map(({optionValue, name}) => [optionValue, name])
|
||||||
);
|
);
|
||||||
|
|
||||||
const profileConditionsDescriptor = {
|
const modifierNameToValue = new Map(
|
||||||
|
modifierKeyValues.map(({optionValue, name}) => [name, optionValue])
|
||||||
|
);
|
||||||
|
|
||||||
|
profileConditionsDescriptor = {
|
||||||
popupLevel: {
|
popupLevel: {
|
||||||
name: 'Popup Level',
|
name: 'Popup Level',
|
||||||
description: 'Use profile depending on the level of the popup.',
|
description: 'Use profile depending on the level of the popup.',
|
||||||
@ -122,7 +127,7 @@ const profileConditionsDescriptor = {
|
|||||||
modifierKeys: {
|
modifierKeys: {
|
||||||
name: 'Modifier Keys',
|
name: 'Modifier Keys',
|
||||||
description: 'Use profile depending on the active modifier keys.',
|
description: 'Use profile depending on the active modifier keys.',
|
||||||
values: _profileModifierKeys,
|
values: modifierKeyValues,
|
||||||
defaultOperator: 'are',
|
defaultOperator: 'are',
|
||||||
operators: {
|
operators: {
|
||||||
are: {
|
are: {
|
||||||
@ -130,13 +135,14 @@ const profileConditionsDescriptor = {
|
|||||||
placeholder: 'Press one or more modifier keys here',
|
placeholder: 'Press one or more modifier keys here',
|
||||||
defaultValue: [],
|
defaultValue: [],
|
||||||
type: 'keyMulti',
|
type: 'keyMulti',
|
||||||
|
keySeparator: modifierSeparator,
|
||||||
transformInput: (optionValue) => optionValue
|
transformInput: (optionValue) => optionValue
|
||||||
.split(' + ')
|
.split(modifierSeparator)
|
||||||
.filter((v) => v.length > 0)
|
.filter((v) => v.length > 0)
|
||||||
.map((v) => _profileModifierNameToValue.get(v)),
|
.map((v) => modifierNameToValue.get(v)),
|
||||||
transformReverse: (transformedOptionValue) => transformedOptionValue
|
transformReverse: (transformedOptionValue) => transformedOptionValue
|
||||||
.map((v) => _profileModifierValueToName.get(v))
|
.map((v) => modifierValueToName.get(v))
|
||||||
.join(' + '),
|
.join(modifierSeparator),
|
||||||
test: ({modifierKeys}, optionValue) => areSetsEqual(new Set(modifierKeys), new Set(optionValue))
|
test: ({modifierKeys}, optionValue) => areSetsEqual(new Set(modifierKeys), new Set(optionValue))
|
||||||
},
|
},
|
||||||
areNot: {
|
areNot: {
|
||||||
@ -144,13 +150,14 @@ const profileConditionsDescriptor = {
|
|||||||
placeholder: 'Press one or more modifier keys here',
|
placeholder: 'Press one or more modifier keys here',
|
||||||
defaultValue: [],
|
defaultValue: [],
|
||||||
type: 'keyMulti',
|
type: 'keyMulti',
|
||||||
|
keySeparator: modifierSeparator,
|
||||||
transformInput: (optionValue) => optionValue
|
transformInput: (optionValue) => optionValue
|
||||||
.split(' + ')
|
.split(modifierSeparator)
|
||||||
.filter((v) => v.length > 0)
|
.filter((v) => v.length > 0)
|
||||||
.map((v) => _profileModifierNameToValue.get(v)),
|
.map((v) => modifierNameToValue.get(v)),
|
||||||
transformReverse: (transformedOptionValue) => transformedOptionValue
|
transformReverse: (transformedOptionValue) => transformedOptionValue
|
||||||
.map((v) => _profileModifierValueToName.get(v))
|
.map((v) => modifierValueToName.get(v))
|
||||||
.join(' + '),
|
.join(modifierSeparator),
|
||||||
test: ({modifierKeys}, optionValue) => !areSetsEqual(new Set(modifierKeys), new Set(optionValue))
|
test: ({modifierKeys}, optionValue) => !areSetsEqual(new Set(modifierKeys), new Set(optionValue))
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
@ -168,3 +175,4 @@ const profileConditionsDescriptor = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
})();
|
||||||
|
@ -310,10 +310,14 @@ ConditionsUI.Condition = class Condition {
|
|||||||
inputInner.prop('readonly', true);
|
inputInner.prop('readonly', true);
|
||||||
|
|
||||||
let values = [];
|
let values = [];
|
||||||
|
let keySeparator = ' + ';
|
||||||
for (const object of objects) {
|
for (const object of objects) {
|
||||||
if (hasOwn(object, 'values')) {
|
if (hasOwn(object, 'values')) {
|
||||||
values = object.values;
|
values = object.values;
|
||||||
}
|
}
|
||||||
|
if (hasOwn(object, 'keySeparator')) {
|
||||||
|
keySeparator = object.keySeparator;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const pressedKeyIndices = new Set();
|
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.val(inputValue);
|
||||||
inputInner.change();
|
inputInner.change();
|
||||||
};
|
};
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
* ankiTemplatesInitialize
|
* ankiTemplatesInitialize
|
||||||
* ankiTemplatesUpdateValue
|
* ankiTemplatesUpdateValue
|
||||||
* apiForwardLogsToBackend
|
* apiForwardLogsToBackend
|
||||||
|
* apiGetEnvironmentInfo
|
||||||
* apiOptionsSave
|
* apiOptionsSave
|
||||||
* appearanceInitialize
|
* appearanceInitialize
|
||||||
* audioSettingsInitialize
|
* audioSettingsInitialize
|
||||||
@ -285,6 +286,23 @@ function showExtensionInformation() {
|
|||||||
node.textContent = `${manifest.name} v${manifest.version}`;
|
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() {
|
async function onReady() {
|
||||||
apiForwardLogsToBackend();
|
apiForwardLogsToBackend();
|
||||||
@ -292,6 +310,7 @@ async function onReady() {
|
|||||||
|
|
||||||
showExtensionInformation();
|
showExtensionInformation();
|
||||||
|
|
||||||
|
await settingsPopulateModifierKeys();
|
||||||
formSetupEventListeners();
|
formSetupEventListeners();
|
||||||
appearanceInitialize();
|
appearanceInitialize();
|
||||||
await audioSettingsInitialize();
|
await audioSettingsInitialize();
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
* getOptionsFullMutable
|
* getOptionsFullMutable
|
||||||
* getOptionsMutable
|
* getOptionsMutable
|
||||||
* profileConditionsDescriptor
|
* profileConditionsDescriptor
|
||||||
|
* profileConditionsDescriptorPromise
|
||||||
* settingsSaveOptions
|
* settingsSaveOptions
|
||||||
* utilBackgroundIsolate
|
* utilBackgroundIsolate
|
||||||
*/
|
*/
|
||||||
@ -98,6 +99,7 @@ async function profileFormWrite(optionsFull) {
|
|||||||
profileConditionsContainer.cleanup();
|
profileConditionsContainer.cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await profileConditionsDescriptorPromise;
|
||||||
profileConditionsContainer = new ConditionsUI.Container(
|
profileConditionsContainer = new ConditionsUI.Container(
|
||||||
profileConditionsDescriptor,
|
profileConditionsDescriptor,
|
||||||
'popupLevel',
|
'popupLevel',
|
||||||
@ -128,7 +130,7 @@ function profileOptionsPopulateSelect(select, profiles, currentValue, ignoreIndi
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function profileOptionsUpdateTarget(optionsFull) {
|
async function profileOptionsUpdateTarget(optionsFull) {
|
||||||
profileFormWrite(optionsFull);
|
await profileFormWrite(optionsFull);
|
||||||
|
|
||||||
const optionsContext = getOptionsContext();
|
const optionsContext = getOptionsContext();
|
||||||
const options = await getOptionsMutable(optionsContext);
|
const options = await getOptionsMutable(optionsContext);
|
||||||
|
@ -412,13 +412,7 @@
|
|||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="scan-modifier-key">Scan modifier key</label>
|
<label for="scan-modifier-key">Scan modifier key</label>
|
||||||
<select class="form-control" id="scan-modifier-key">
|
<select class="form-control" id="scan-modifier-key"></select>
|
||||||
<option value="none">None</option>
|
|
||||||
<option value="alt">Alt</option>
|
|
||||||
<option value="ctrl">Ctrl</option>
|
|
||||||
<option value="shift">Shift</option>
|
|
||||||
<option data-hide-for-browser="firefox firefox-mobile" value="meta">Meta</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -1131,6 +1125,7 @@
|
|||||||
|
|
||||||
<script src="/mixed/js/core.js"></script>
|
<script src="/mixed/js/core.js"></script>
|
||||||
<script src="/mixed/js/dom.js"></script>
|
<script src="/mixed/js/dom.js"></script>
|
||||||
|
<script src="/mixed/js/environment.js"></script>
|
||||||
<script src="/mixed/js/api.js"></script>
|
<script src="/mixed/js/api.js"></script>
|
||||||
<script src="/mixed/js/japanese.js"></script>
|
<script src="/mixed/js/japanese.js"></script>
|
||||||
|
|
||||||
|
114
ext/mixed/js/environment.js
Normal file
114
ext/mixed/js/environment.js
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Yomichan Authors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
class Environment {
|
||||||
|
constructor() {
|
||||||
|
this._cachedEnvironmentInfo = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async prepare() {
|
||||||
|
this._cachedEnvironmentInfo = await this._loadEnvironmentInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
getInfo() {
|
||||||
|
if (this._cachedEnvironmentInfo === null) { throw new Error('Not prepared'); }
|
||||||
|
return this._cachedEnvironmentInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _loadEnvironmentInfo() {
|
||||||
|
const browser = await this._getBrowser();
|
||||||
|
const platform = await new Promise((resolve) => chrome.runtime.getPlatformInfo(resolve));
|
||||||
|
const modifierInfo = this._getModifierInfo(browser, platform.os);
|
||||||
|
return {
|
||||||
|
browser,
|
||||||
|
platform: {
|
||||||
|
os: platform.os
|
||||||
|
},
|
||||||
|
modifiers: modifierInfo
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_getModifierInfo(browser, os) {
|
||||||
|
let osKeys;
|
||||||
|
let separator;
|
||||||
|
switch (os) {
|
||||||
|
case 'win':
|
||||||
|
separator = ' + ';
|
||||||
|
osKeys = [
|
||||||
|
['alt', 'Alt'],
|
||||||
|
['ctrl', 'Ctrl'],
|
||||||
|
['shift', 'Shift'],
|
||||||
|
['meta', 'Windows']
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
case 'mac':
|
||||||
|
separator = '';
|
||||||
|
osKeys = [
|
||||||
|
['alt', '⌥'],
|
||||||
|
['ctrl', '⌃'],
|
||||||
|
['shift', '⇧'],
|
||||||
|
['meta', '⌘']
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
case 'linux':
|
||||||
|
case 'openbsd':
|
||||||
|
case 'cros':
|
||||||
|
case 'android':
|
||||||
|
separator = ' + ';
|
||||||
|
osKeys = [
|
||||||
|
['alt', 'Alt'],
|
||||||
|
['ctrl', 'Ctrl'],
|
||||||
|
['shift', 'Shift'],
|
||||||
|
['meta', 'Super']
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Invalid OS: ${os}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isFirefox = (browser === 'firefox' || browser === 'firefox-mobile');
|
||||||
|
const keys = [];
|
||||||
|
|
||||||
|
for (const [value, name] of osKeys) {
|
||||||
|
// Firefox doesn't support event.metaKey on platforms other than macOS
|
||||||
|
if (value === 'meta' && isFirefox && os !== 'mac') { continue; }
|
||||||
|
keys.push({value, name});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {keys, separator};
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user