diff --git a/ext/bg/css/settings2.css b/ext/bg/css/settings2.css
index 6ae9e335..b41ea7ea 100644
--- a/ext/bg/css/settings2.css
+++ b/ext/bg/css/settings2.css
@@ -1931,6 +1931,94 @@ input.sentence-termination-character-input2 {
margin-top: 0.5em;
}
+.hotkey-list {
+ margin: 0 calc(var(--modal-padding-horizontal) * -1);
+}
+.hotkey-list-item {
+ margin: 0.5em 0;
+}
+.hotkey-list-item+.hotkey-list-item {
+ border-top: var(--thin-border-size) solid var(--separator-color2);
+}
+.hotkey-list-item-grid {
+ display: grid;
+ grid-template-columns: auto auto 1fr auto;
+ grid-template-rows: auto;
+ grid-template-areas:
+ 'index input-label input button'
+ '. action-label action .';
+ width: 100%;
+ column-gap: 0.25em;
+ row-gap: 0.25em;
+ margin: 0.5em 0;
+ padding: 0 var(--modal-padding-horizontal);
+ box-sizing: border-box;
+}
+.hotkey-list-item-index-cell {
+ grid-area: index;
+ align-self: center;
+ text-align: center;
+ width: 2em;
+}
+.hotkey-list-item-button-cell {
+ grid-area: button;
+ align-self: center;
+}
+.hotkey-list-item-input-label-cell {
+ grid-area: input-label;
+ align-self: center;
+}
+.hotkey-list-item-input-cell {
+ grid-area: input;
+ display: flex;
+ flex-flow: row nowrap;
+ width: 100%;
+ align-items: stretch;
+ align-self: center;
+}
+.hotkey-list-item-input {
+ flex: 1 1 auto;
+}
+.hotkey-list-item-action-label-cell {
+ grid-area: action-label;
+ align-self: center;
+}
+.hotkey-list-item-action-cell {
+ grid-area: action;
+ align-self: center;
+ display: flex;
+ flex-flow: row nowrap;
+ width: 100%;
+ align-items: center;
+}
+.hotkey-list-item-action {
+ flex: 1 1 auto;
+}
+.hotkey-list-item-enabled-label {
+ align-self: center;
+ margin-left: 1em;
+}
+.hotkey-list-item-flex-row {
+ display: flex;
+ flex-flow: row nowrap;
+ align-items: center;
+}
+.hotkey-list-item-flex-row-label {
+ margin: 0 0.5em 0 1em;
+}
+.hotkey-scope-checkbox-container {
+ display: flex;
+ flex-flow: row nowrap;
+ align-items: center;
+ cursor: pointer;
+}
+.hotkey-scope-checkbox-container:not(:last-child) {
+ margin-right: 0.75em;
+}
+.hotkey-scope-checkbox-container>span {
+ padding-left: 0.375em;
+}
+
/* Generic layouts */
.margin-above {
diff --git a/ext/bg/data/options-schema.json b/ext/bg/data/options-schema.json
index 5d23df02..44bff10a 100644
--- a/ext/bg/data/options-schema.json
+++ b/ext/bg/data/options-schema.json
@@ -69,7 +69,8 @@
"dictionaries",
"parsing",
"anki",
- "sentenceParsing"
+ "sentenceParsing",
+ "inputs"
],
"properties": {
"general": {
@@ -914,6 +915,75 @@
]
}
}
+ },
+ "inputs": {
+ "type": "object",
+ "required": [
+ "hotkeys"
+ ],
+ "properties": {
+ "hotkeys": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": [
+ "action",
+ "key",
+ "modifiers",
+ "scopes",
+ "enabled"
+ ],
+ "properties": {
+ "action": {
+ "type": "string",
+ "default": ""
+ },
+ "key": {
+ "type": ["string", "null"],
+ "default": null
+ },
+ "modifiers": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": ["alt", "ctrl", "shift", "meta"],
+ "default": "alt"
+ }
+ },
+ "scopes": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": ["popup", "search"],
+ "default": "popup"
+ },
+ "default": ["popup", "search"]
+ },
+ "enabled": {
+ "type": "boolean",
+ "default": true
+ }
+ }
+ },
+ "default": [
+ {"action": "close", "key": "Escape", "modifiers": [], "scopes": ["popup", "search"], "enabled": true},
+ {"action": "previousEntry3", "key": "PageUp", "modifiers": ["alt"], "scopes": ["popup", "search"], "enabled": true},
+ {"action": "nextEntry3", "key": "PageDown", "modifiers": ["alt"], "scopes": ["popup", "search"], "enabled": true},
+ {"action": "lastEntry", "key": "End", "modifiers": ["alt"], "scopes": ["popup", "search"], "enabled": true},
+ {"action": "firstEntry", "key": "Home", "modifiers": ["alt"], "scopes": ["popup", "search"], "enabled": true},
+ {"action": "previousEntry", "key": "ArrowUp", "modifiers": ["alt"], "scopes": ["popup", "search"], "enabled": true},
+ {"action": "nextEntry", "key": "ArrowDown", "modifiers": ["alt"], "scopes": ["popup", "search"], "enabled": true},
+ {"action": "historyBackward", "key": "KeyB", "modifiers": ["alt"], "scopes": ["popup", "search"], "enabled": true},
+ {"action": "historyForward", "key": "KeyF", "modifiers": ["alt"], "scopes": ["popup", "search"], "enabled": true},
+ {"action": "addNoteKanji", "key": "KeyK", "modifiers": ["alt"], "scopes": ["popup", "search"], "enabled": true},
+ {"action": "addNoteTermKanji", "key": "KeyE", "modifiers": ["alt"], "scopes": ["popup", "search"], "enabled": true},
+ {"action": "addNoteTermKana", "key": "KeyR", "modifiers": ["alt"], "scopes": ["popup", "search"], "enabled": true},
+ {"action": "playAudio", "key": "KeyP", "modifiers": ["alt"], "scopes": ["popup", "search"], "enabled": true},
+ {"action": "viewNote", "key": "KeyV", "modifiers": ["alt"], "scopes": ["popup", "search"], "enabled": true},
+ {"action": "copyHostSelection", "key": "KeyC", "modifiers": ["ctrl"], "scopes": ["popup", "search"], "enabled": true}
+ ]
+ }
+ }
}
}
}
diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js
index 86f76698..0d3e42a1 100644
--- a/ext/bg/js/options.js
+++ b/ext/bg/js/options.js
@@ -712,6 +712,25 @@ class OptionsUtil {
};
delete profile.options.anki.sentenceExt;
profile.options.general.popupActionBarLocation = 'top';
+ profile.options.inputs = {
+ hotkeys: [
+ {action: 'close', key: 'Escape', modifiers: [], scopes: ['popup', 'search'], enabled: true},
+ {action: 'previousEntry3', key: 'PageUp', modifiers: ['alt'], scopes: ['popup', 'search'], enabled: true},
+ {action: 'nextEntry3', key: 'PageDown', modifiers: ['alt'], scopes: ['popup', 'search'], enabled: true},
+ {action: 'lastEntry', key: 'End', modifiers: ['alt'], scopes: ['popup', 'search'], enabled: true},
+ {action: 'firstEntry', key: 'Home', modifiers: ['alt'], scopes: ['popup', 'search'], enabled: true},
+ {action: 'previousEntry', key: 'ArrowUp', modifiers: ['alt'], scopes: ['popup', 'search'], enabled: true},
+ {action: 'nextEntry', key: 'ArrowDown', modifiers: ['alt'], scopes: ['popup', 'search'], enabled: true},
+ {action: 'historyBackward', key: 'KeyB', modifiers: ['alt'], scopes: ['popup', 'search'], enabled: true},
+ {action: 'historyForward', key: 'KeyF', modifiers: ['alt'], scopes: ['popup', 'search'], enabled: true},
+ {action: 'addNoteKanji', key: 'KeyK', modifiers: ['alt'], scopes: ['popup', 'search'], enabled: true},
+ {action: 'addNoteTermKanji', key: 'KeyE', modifiers: ['alt'], scopes: ['popup', 'search'], enabled: true},
+ {action: 'addNoteTermKana', key: 'KeyR', modifiers: ['alt'], scopes: ['popup', 'search'], enabled: true},
+ {action: 'playAudio', key: 'KeyP', modifiers: ['alt'], scopes: ['popup', 'search'], enabled: true},
+ {action: 'viewNote', key: 'KeyV', modifiers: ['alt'], scopes: ['popup', 'search'], enabled: true},
+ {action: 'copyHostSelection', key: 'KeyC', modifiers: ['ctrl'], scopes: ['popup', 'search'], enabled: true}
+ ]
+ };
}
return options;
}
diff --git a/ext/bg/js/settings/keyboard-mouse-input-field.js b/ext/bg/js/settings/keyboard-mouse-input-field.js
index 5e7a2f2e..d48b130f 100644
--- a/ext/bg/js/settings/keyboard-mouse-input-field.js
+++ b/ext/bg/js/settings/keyboard-mouse-input-field.js
@@ -49,11 +49,9 @@ class KeyboardMouseInputField extends EventDispatcher {
prepare(key, modifiers, mouseModifiersSupported=false, keySupported=false) {
this.cleanup();
- this._key = key;
- this._modifiers = this._sortModifiers(modifiers);
this._mouseModifiersSupported = mouseModifiersSupported;
this._keySupported = keySupported;
- this._updateDisplayString();
+ this.setInput(key, modifiers);
const events = [
[this._inputNode, 'keydown', this._onModifierKeyDown.bind(this), false]
];
@@ -73,6 +71,12 @@ class KeyboardMouseInputField extends EventDispatcher {
}
}
+ setInput(key, modifiers) {
+ this._key = key;
+ this._modifiers = this._sortModifiers(modifiers);
+ this._updateDisplayString();
+ }
+
cleanup() {
this._eventListeners.removeAllEventListeners();
this._modifiers = [];
@@ -131,11 +135,20 @@ class KeyboardMouseInputField extends EventDispatcher {
}
if (this._key !== null) {
if (!first) { displayValue += this._keySeparator; }
- displayValue += this._key;
+ displayValue += this._getDisplayKey(this._key);
}
this._inputNode.value = displayValue;
}
+ _getDisplayKey(key) {
+ if (typeof key === 'string') {
+ if (key.length === 4 && key.startsWith('Key')) {
+ key = key.substring(3);
+ }
+ }
+ return key;
+ }
+
_getModifierName(modifier) {
const pattern = this._mouseInputNamePattern;
const match = pattern.exec(modifier);
diff --git a/ext/bg/js/settings2/keyboard-shortcuts-controller.js b/ext/bg/js/settings2/keyboard-shortcuts-controller.js
new file mode 100644
index 00000000..83b457c8
--- /dev/null
+++ b/ext/bg/js/settings2/keyboard-shortcuts-controller.js
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2021 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