aboutsummaryrefslogtreecommitdiff
path: root/ext/js
diff options
context:
space:
mode:
Diffstat (limited to 'ext/js')
-rw-r--r--ext/js/data/options-util.js24
-rw-r--r--ext/js/display/display.js13
-rw-r--r--ext/js/dom/dom-data-binder.js2
-rw-r--r--ext/js/input/hotkey-handler.js8
-rw-r--r--ext/js/pages/settings/keyboard-shortcuts-controller.js174
5 files changed, 180 insertions, 41 deletions
diff --git a/ext/js/data/options-util.js b/ext/js/data/options-util.js
index fd62a558..5b2e2bd3 100644
--- a/ext/js/data/options-util.js
+++ b/ext/js/data/options-util.js
@@ -754,8 +754,32 @@ class OptionsUtil {
// Version 10 changes:
// Removed global option useSettingsV2.
// Added part-of-speech field template.
+ // Added an argument to hotkey inputs.
await this._applyAnkiFieldTemplatesPatch(options, '/data/templates/anki-field-templates-upgrade-v10.handlebars');
delete options.global.useSettingsV2;
+ for (const profile of options.profiles) {
+ for (const hotkey of profile.options.inputs.hotkeys) {
+ switch (hotkey.action) {
+ case 'previousEntry':
+ hotkey.argument = '1';
+ break;
+ case 'previousEntry3':
+ hotkey.action = 'previousEntry';
+ hotkey.argument = '3';
+ break;
+ case 'nextEntry':
+ hotkey.argument = '1';
+ break;
+ case 'nextEntry3':
+ hotkey.action = 'nextEntry';
+ hotkey.argument = '3';
+ break;
+ default:
+ hotkey.argument = '';
+ break;
+ }
+ }
+ }
return options;
}
}
diff --git a/ext/js/display/display.js b/ext/js/display/display.js
index 7cc3b437..b0805aca 100644
--- a/ext/js/display/display.js
+++ b/ext/js/display/display.js
@@ -115,10 +115,8 @@ class Display extends EventDispatcher {
this._hotkeyHandler.registerActions([
['close', () => { this._onHotkeyClose(); }],
- ['nextEntry', () => { this._focusEntry(this._index + 1, true); }],
- ['nextEntry3', () => { this._focusEntry(this._index + 3, true); }],
- ['previousEntry', () => { this._focusEntry(this._index - 1, true); }],
- ['previousEntry3', () => { this._focusEntry(this._index - 3, true); }],
+ ['nextEntry', this._onHotkeyActionMoveRelative.bind(this, 1)],
+ ['previousEntry', this._onHotkeyActionMoveRelative.bind(this, -1)],
['lastEntry', () => { this._focusEntry(this._definitions.length - 1, true); }],
['firstEntry', () => { this._focusEntry(0, true); }],
['historyBackward', () => { this._sourceTermView(); }],
@@ -1825,6 +1823,13 @@ class Display extends EventDispatcher {
this.close();
}
+ _onHotkeyActionMoveRelative(sign, argument) {
+ let count = Number.parseInt(argument, 10);
+ if (!Number.isFinite(count)) { count = 1; }
+ count = Math.max(0, Math.floor(count));
+ this._focusEntry(this._index + count * sign, true);
+ }
+
_closeAllPopupMenus() {
for (const popupMenu of PopupMenu.openMenus) {
popupMenu.close();
diff --git a/ext/js/dom/dom-data-binder.js b/ext/js/dom/dom-data-binder.js
index 292b2f67..65e91233 100644
--- a/ext/js/dom/dom-data-binder.js
+++ b/ext/js/dom/dom-data-binder.js
@@ -210,7 +210,7 @@ class DOMDataBinder {
static convertToNumber(value, constraints) {
value = parseFloat(value);
- if (!Number.isFinite(value)) { return 0; }
+ if (!Number.isFinite(value)) { value = 0; }
let {min, max, step} = constraints;
min = DOMDataBinder.convertToNumberOrNull(min);
diff --git a/ext/js/input/hotkey-handler.js b/ext/js/input/hotkey-handler.js
index d3b82d48..8388302d 100644
--- a/ext/js/input/hotkey-handler.js
+++ b/ext/js/input/hotkey-handler.js
@@ -166,12 +166,12 @@ class HotkeyHandler extends EventDispatcher {
}
_invokeHandlers(modifiers, hotkeyInfo) {
- for (const {modifiers: handlerModifiers, action} of hotkeyInfo.handlers) {
+ for (const {modifiers: handlerModifiers, action, argument} of hotkeyInfo.handlers) {
if (!this._areSame(handlerModifiers, modifiers)) { continue; }
const actionHandler = this._actions.get(action);
if (typeof actionHandler !== 'undefined') {
- const result = actionHandler();
+ const result = actionHandler(argument);
if (result !== false) {
return true;
}
@@ -196,7 +196,7 @@ class HotkeyHandler extends EventDispatcher {
this._hotkeys.clear();
for (const [scope, registrations] of this._hotkeyRegistrations.entries()) {
- for (const {action, key, modifiers, scopes, enabled} of registrations) {
+ for (const {action, argument, key, modifiers, scopes, enabled} of registrations) {
if (!(enabled && key !== null && action !== '' && scopes.includes(scope))) { continue; }
let hotkeyInfo = this._hotkeys.get(key);
@@ -205,7 +205,7 @@ class HotkeyHandler extends EventDispatcher {
this._hotkeys.set(key, hotkeyInfo);
}
- hotkeyInfo.handlers.push({modifiers: new Set(modifiers), action});
+ hotkeyInfo.handlers.push({modifiers: new Set(modifiers), action, argument});
}
}
this._updateEventHandlers();
diff --git a/ext/js/pages/settings/keyboard-shortcuts-controller.js b/ext/js/pages/settings/keyboard-shortcuts-controller.js
index 514928d7..2f636541 100644
--- a/ext/js/pages/settings/keyboard-shortcuts-controller.js
+++ b/ext/js/pages/settings/keyboard-shortcuts-controller.js
@@ -16,6 +16,7 @@
*/
/* global
+ * DOMDataBinder
* KeyboardMouseInputField
*/
@@ -30,6 +31,26 @@ class KeyboardShortcutController {
this._emptyIndicator = null;
this._stringComparer = new Intl.Collator('en-US'); // Invariant locale
this._scrollContainer = null;
+ this._actionDetails = new Map([
+ ['', {scopes: new Set()}],
+ ['close', {scopes: new Set(['popup', 'search'])}],
+ ['focusSearchBox', {scopes: new Set(['search'])}],
+ ['nextEntry', {scopes: new Set(['popup', 'search']), argument: {template: 'hotkey-argument-move-offset', default: '1'}}],
+ ['previousEntry', {scopes: new Set(['popup', 'search']), argument: {template: 'hotkey-argument-move-offset', default: '1'}}],
+ ['lastEntry', {scopes: new Set(['popup', 'search'])}],
+ ['firstEntry', {scopes: new Set(['popup', 'search'])}],
+ ['nextEntryDifferentDictionary', {scopes: new Set(['popup', 'search'])}],
+ ['previousEntryDifferentDictionary', {scopes: new Set(['popup', 'search'])}],
+ ['historyBackward', {scopes: new Set(['popup', 'search'])}],
+ ['historyForward', {scopes: new Set(['popup', 'search'])}],
+ ['addNoteKanji', {scopes: new Set(['popup', 'search'])}],
+ ['addNoteTermKanji', {scopes: new Set(['popup', 'search'])}],
+ ['addNoteTermKana', {scopes: new Set(['popup', 'search'])}],
+ ['viewNote', {scopes: new Set(['popup', 'search'])}],
+ ['playAudio', {scopes: new Set(['popup', 'search'])}],
+ ['copyHostSelection', {scopes: new Set(['popup'])}],
+ ['scanSelectedText', {scopes: new Set(['web'])}]
+ ]);
}
get settingsController() {
@@ -96,6 +117,10 @@ class KeyboardShortcutController {
return defaultOptions.profiles[0].options.inputs.hotkeys;
}
+ getActionDetails(action) {
+ return this._actionDetails.get(action);
+ }
+
// Private
_onOptionsChanged({options}) {
@@ -134,6 +159,7 @@ class KeyboardShortcutController {
async _addNewEntry() {
const newEntry = {
action: '',
+ argument: '',
key: null,
modifiers: [],
scopes: ['popup', 'search'],
@@ -169,6 +195,9 @@ class KeyboardShortcutHotkeyEntry {
this._enabledButton = null;
this._scopeMenu = null;
this._scopeMenuEventListeners = new EventListenerCollection();
+ this._argumentContainer = null;
+ this._argumentInput = null;
+ this._argumentEventListeners = new EventListenerCollection();
}
prepare() {
@@ -183,6 +212,7 @@ class KeyboardShortcutHotkeyEntry {
this._actionSelect = action;
this._enabledButton = enabledButton;
+ this._argumentContainer = node.querySelector('.hotkey-list-item-action-argument-container');
this._inputField = new KeyboardMouseInputField(input, null, this._os);
this._inputField.prepare(this._data.key, this._data.modifiers, false, true);
@@ -193,6 +223,7 @@ class KeyboardShortcutHotkeyEntry {
enabledToggle.dataset.setting = `${this._basePath}.enabled`;
this._updateScopesButton();
+ this._updateActionArgument();
this._eventListeners.addEventListener(scopesButton, 'menuOpen', this._onScopesMenuOpen.bind(this));
this._eventListeners.addEventListener(scopesButton, 'menuClose', this._onScopesMenuClose.bind(this));
@@ -205,6 +236,7 @@ class KeyboardShortcutHotkeyEntry {
this._eventListeners.removeAllEventListeners();
this._inputField.cleanup();
this._clearScopeMenu();
+ this._clearArgumentEventListeners();
if (this._node.parentNode !== null) {
this._node.parentNode.removeChild(this._node);
}
@@ -228,6 +260,11 @@ class KeyboardShortcutHotkeyEntry {
_onScopesMenuOpen(e) {
const {menu} = e.detail;
+ const validScopes = this._getValidScopesForAction(this._data.action);
+ if (validScopes.size === 0) {
+ menu.close();
+ return;
+ }
this._scopeMenu = menu;
this._updateScopeMenuItems(menu);
this._updateDisplay(menu.containerNode); // Fix a animation issue due to changing checkbox values
@@ -260,6 +297,21 @@ class KeyboardShortcutHotkeyEntry {
this._setAction(value);
}
+ _onArgumentValueChange(template, e) {
+ const node = e.currentTarget;
+ const value = this._getArgumentInputValue(node);
+ let newValue = value;
+ switch (template) {
+ case 'hotkey-argument-move-offset':
+ newValue = `${DOMDataBinder.convertToNumber(value, node)}`;
+ break;
+ }
+ if (value !== newValue) {
+ this._setArgumentInputValue(node, newValue);
+ }
+ this._setArgument(newValue);
+ }
+
async _delete() {
this._parent.deleteEntry(this._index);
}
@@ -294,13 +346,13 @@ class KeyboardShortcutHotkeyEntry {
scopes.splice(index, 1);
}
- this._updateScopesButton();
-
await this._modifyProfileSettings([{
action: 'set',
path: `${this._basePath}.scopes`,
value: scopes
}]);
+
+ this._updateScopesButton();
}
async _modifyProfileSettings(targets) {
@@ -326,58 +378,81 @@ class KeyboardShortcutHotkeyEntry {
}
async _setAction(value) {
- const targets = [{
- action: 'set',
- path: `${this._basePath}.action`,
- value
- }];
+ const validScopesOld = this._getValidScopesForAction(this._data.action);
+
+ const scopes = this._data.scopes;
+
+ let details = this._parent.getActionDetails(value);
+ if (typeof details === 'undefined') { details = {}; }
+
+ let validScopes = details.scopes;
+ if (typeof validScopes === 'undefined') { validScopes = new Set(); }
+
+ const {argument: argumentDetails} = details;
+ let defaultArgument = typeof argumentDetails !== 'undefined' ? argumentDetails.default : '';
+ if (typeof defaultArgument !== 'string') { defaultArgument = ''; }
this._data.action = value;
+ this._data.argument = defaultArgument;
- const scopes = this._data.scopes;
- const validScopes = this._getValidScopesForAction(value);
- if (validScopes !== null) {
- let changed = false;
+ let scopesChanged = false;
+ if ((validScopesOld !== null ? validScopesOld.size : 0) === scopes.length) {
+ scopes.length = 0;
+ scopesChanged = true;
+ } else {
for (let i = 0, ii = scopes.length; i < ii; ++i) {
if (!validScopes.has(scopes[i])) {
scopes.splice(i, 1);
--i;
--ii;
- changed = true;
+ scopesChanged = true;
}
}
- if (changed) {
- if (scopes.length === 0) {
- scopes.push(...validScopes);
- }
- targets.push({
- action: 'set',
- path: `${this._basePath}.scopes`,
- value: scopes
- });
- this._updateCheckboxStates();
- }
+ }
+ if (scopesChanged && scopes.length === 0) {
+ scopes.push(...validScopes);
}
- await this._modifyProfileSettings(targets);
+ await this._modifyProfileSettings([
+ {
+ action: 'set',
+ path: `${this._basePath}.action`,
+ value: this._data.action
+ },
+ {
+ action: 'set',
+ path: `${this._basePath}.argument`,
+ value: this._data.argument
+ },
+ {
+ action: 'set',
+ path: `${this._basePath}.scopes`,
+ value: this._data.scopes
+ }
+ ]);
- this._updateCheckboxVisibility();
+ this._updateScopesButton();
+ this._updateScopesMenu();
+ this._updateActionArgument();
}
- _updateCheckboxStates() {
- if (this._scopeMenu === null) { return; }
- this._updateScopeMenuItems(this._scopeMenu);
+ async _setArgument(value) {
+ this._data.argument = value;
+ await this._modifyProfileSettings([{
+ action: 'set',
+ path: `${this._basePath}.argument`,
+ value
+ }]);
}
- _updateCheckboxVisibility() {
+ _updateScopesMenu() {
if (this._scopeMenu === null) { return; }
this._updateScopeMenuItems(this._scopeMenu);
}
_getValidScopesForAction(action) {
- const optionNode = this._actionSelect.querySelector(`option[value="${action}"]`);
- const scopesString = (optionNode !== null ? optionNode.dataset.scopes : void 0);
- return (typeof scopesString === 'string' ? new Set(scopesString.split(' ')) : null);
+ const details = this._parent.getActionDetails(action);
+ return typeof details !== 'undefined' ? details.scopes : null;
}
_updateScopeMenuItems(menu) {
@@ -419,4 +494,39 @@ class KeyboardShortcutHotkeyEntry {
getComputedStyle(node).getPropertyValue('display');
style.display = display;
}
+
+ _updateActionArgument() {
+ this._clearArgumentEventListeners();
+
+ const {action, argument} = this._data;
+ const details = this._parent.getActionDetails(action);
+ const {argument: argumentDetails} = typeof details !== 'undefined' ? details : {};
+
+ this._argumentContainer.textContent = '';
+ if (typeof argumentDetails !== 'undefined') {
+ const {template} = argumentDetails;
+ const node = this._parent.settingsController.instantiateTemplate(template);
+ const inputSelector = '.hotkey-argument-input';
+ const inputNode = node.matches(inputSelector) ? node : node.querySelector(inputSelector);
+ if (inputNode !== null) {
+ this._setArgumentInputValue(inputNode, argument);
+ this._argumentInput = inputNode;
+ this._argumentEventListeners.addEventListener(inputNode, 'change', this._onArgumentValueChange.bind(this, template), false);
+ }
+ this._argumentContainer.appendChild(node);
+ }
+ }
+
+ _clearArgumentEventListeners() {
+ this._argumentEventListeners.removeAllEventListeners();
+ this._argumentInput = null;
+ }
+
+ _getArgumentInputValue(node) {
+ return node.value;
+ }
+
+ _setArgumentInputValue(node, value) {
+ node.value = value;
+ }
}