diff --git a/app/Http/Controllers/UserPreferencesController.php b/app/Http/Controllers/UserPreferencesController.php index b8bb31468..c4718681a 100644 --- a/app/Http/Controllers/UserPreferencesController.php +++ b/app/Http/Controllers/UserPreferencesController.php @@ -35,7 +35,7 @@ class UserPreferencesController extends Controller public function updateShortcuts(Request $request) { $enabled = $request->get('enabled') === 'true'; - $providedShortcuts = $request->get('shortcuts', []); + $providedShortcuts = $request->get('shortcut', []); $shortcuts = new UserShortcutMap($providedShortcuts); setting()->putUser(user(), 'ui-shortcuts', $shortcuts->toJson()); diff --git a/resources/js/components/index.js b/resources/js/components/index.js index 6203d0b74..ee282b1fd 100644 --- a/resources/js/components/index.js +++ b/resources/js/components/index.js @@ -44,6 +44,7 @@ import settingAppColorPicker from "./setting-app-color-picker.js" import settingColorPicker from "./setting-color-picker.js" import shelfSort from "./shelf-sort.js" import shortcuts from "./shortcuts"; +import shortcutInput from "./shortcut-input"; import sidebar from "./sidebar.js" import sortableList from "./sortable-list.js" import submitOnChange from "./submit-on-change.js" @@ -103,6 +104,7 @@ const componentMapping = { "setting-color-picker": settingColorPicker, "shelf-sort": shelfSort, "shortcuts": shortcuts, + "shortcut-input": shortcutInput, "sidebar": sidebar, "sortable-list": sortableList, "submit-on-change": submitOnChange, diff --git a/resources/js/components/shortcut-input.js b/resources/js/components/shortcut-input.js new file mode 100644 index 000000000..fa1378988 --- /dev/null +++ b/resources/js/components/shortcut-input.js @@ -0,0 +1,57 @@ +/** + * Keys to ignore when recording shortcuts. + * @type {string[]} + */ +const ignoreKeys = ['Control', 'Alt', 'Shift', 'Meta', 'Super', ' ', '+', 'Tab', 'Escape']; + +/** + * @extends {Component} + */ +class ShortcutInput { + + setup() { + this.input = this.$el; + + this.setupListeners(); + } + + setupListeners() { + this.listenerRecordKey = this.listenerRecordKey.bind(this); + + this.input.addEventListener('focus', () => { + this.startListeningForInput(); + }); + + this.input.addEventListener('blur', () => { + this.stopListeningForInput(); + }) + } + + startListeningForInput() { + this.input.addEventListener('keydown', this.listenerRecordKey) + } + + /** + * @param {KeyboardEvent} event + */ + listenerRecordKey(event) { + if (ignoreKeys.includes(event.key)) { + return; + } + + const keys = [ + event.ctrlKey ? 'Ctrl' : '', + event.metaKey ? 'Cmd' : '', + event.key, + ]; + + this.input.value = keys.filter(s => Boolean(s)).join(' + '); + } + + stopListeningForInput() { + this.input.removeEventListener('keydown', this.listenerRecordKey); + } + +} + +export default ShortcutInput; \ No newline at end of file diff --git a/resources/js/components/shortcuts.js b/resources/js/components/shortcuts.js index ccad00f5d..cec8684c8 100644 --- a/resources/js/components/shortcuts.js +++ b/resources/js/components/shortcuts.js @@ -30,13 +30,7 @@ class Shortcuts { return; } - const shortcutId = this.mapByShortcut[event.key]; - if (shortcutId) { - const wasHandled = this.runShortcut(shortcutId); - if (wasHandled) { - event.preventDefault(); - } - } + this.handleShortcutPress(event); }); window.addEventListener('keydown', event => { @@ -46,6 +40,28 @@ class Shortcuts { }); } + /** + * @param {KeyboardEvent} event + */ + handleShortcutPress(event) { + + const keys = [ + event.ctrlKey ? 'Ctrl' : '', + event.metaKey ? 'Cmd' : '', + event.key, + ]; + + const combo = keys.filter(s => Boolean(s)).join(' + '); + + const shortcutId = this.mapByShortcut[combo]; + if (shortcutId) { + const wasHandled = this.runShortcut(shortcutId); + if (wasHandled) { + event.preventDefault(); + } + } + } + /** * Run the given shortcut, and return a boolean to indicate if the event * was successfully handled by a shortcut action. diff --git a/resources/views/users/preferences/parts/shortcut-control.blade.php b/resources/views/users/preferences/parts/shortcut-control.blade.php index 47fec3a5e..b85813ce0 100644 --- a/resources/views/users/preferences/parts/shortcut-control.blade.php +++ b/resources/views/users/preferences/parts/shortcut-control.blade.php @@ -1,7 +1,8 @@ <div class="flex-container-row justify-space-between items-center gap-m item-list-row"> - <label for="shortcut-{{ $label }}" class="bold flex px-m py-xs">{{ $label }}</label> + <label for="shortcut-{{ $id }}" class="bold flex px-m py-xs">{{ $label }}</label> <div class="px-m py-xs"> <input type="text" + component="shortcut-input" class="small flex-none shortcut-input px-s py-xs" id="shortcut-{{ $id }}" name="shortcut[{{ $id }}]" diff --git a/resources/views/users/preferences/shortcuts.blade.php b/resources/views/users/preferences/shortcuts.blade.php index 9bb8e8175..61c61dcf9 100644 --- a/resources/views/users/preferences/shortcuts.blade.php +++ b/resources/views/users/preferences/shortcuts.blade.php @@ -13,9 +13,12 @@ <div class="flex-container-row items-center gap-m wrap mb-m"> <p class="flex mb-none min-width-m text-small text-muted"> Here you can enable or disable keyboard system interface shortcuts, used for navigation - and actions. You can customize each of the shortcuts below. + and actions. + + You can customize each of the shortcuts below. Just press your desired key combination + after selecting the input for a shortcut. </p> - <div class="flex min-width-m text-m-right"> + <div class="flex min-width-m text-m-center"> @include('form.toggle-switch', [ 'name' => 'enabled', 'value' => $enabled,