From a17d585a1292ada13365ba1ecb70138dbe3ce245 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 31 Jan 2022 16:55:45 +0100 Subject: [PATCH] Work towards unifying `KeyboardShortcuts` and `KeyBindingsDefaults` #2 (#7674) --- src/KeyBindingsDefaults.ts | 429 +++-------------- src/KeyBindingsManager.ts | 155 +----- src/accessibility/KeyboardShortcuts.ts | 444 +++++++++++------- src/components/structures/LeftPanel.tsx | 7 +- src/components/structures/LoggedInView.tsx | 35 +- src/components/structures/RoomSearch.tsx | 8 +- src/components/structures/RoomView.tsx | 9 +- src/components/structures/ScrollPanel.tsx | 11 +- .../views/rooms/BasicMessageComposer.tsx | 32 +- .../views/rooms/EditMessageComposer.tsx | 11 +- src/components/views/rooms/RoomSublist.tsx | 7 +- .../views/rooms/SendMessageComposer.tsx | 15 +- .../views/spaces/SpaceTreeLevel.tsx | 7 +- src/i18n/strings/en_EN.json | 8 +- test/KeyBindingsManager-test.ts | 4 +- 15 files changed, 450 insertions(+), 732 deletions(-) diff --git a/src/KeyBindingsDefaults.ts b/src/KeyBindingsDefaults.ts index b6296e2dd4..76d55b42d3 100644 --- a/src/KeyBindingsDefaults.ts +++ b/src/KeyBindingsDefaults.ts @@ -1,5 +1,6 @@ /* Copyright 2021 Clemens Zeidler +Copyright 2022 Šimon Brandner Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,134 +15,55 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { - AutocompleteAction, - IKeyBindingsProvider, - KeyBinding, - MessageComposerAction, - NavigationAction, - RoomAction, - RoomListAction, - LabsAction, -} from "./KeyBindingsManager"; import { isMac, Key } from "./Keyboard"; import SettingsStore from "./settings/SettingsStore"; import SdkConfig from "./SdkConfig"; +import { + IKeyBindingsProvider, + KeyBinding, + KeyCombo, +} from "./KeyBindingsManager"; +import { + CATEGORIES, + CategoryName, + getCustomizableShortcuts, + KeyBindingAction, +} from "./accessibility/KeyboardShortcuts"; + +export const getBindingsByCategory = ( + category: CategoryName, +): KeyBinding[] => { + return CATEGORIES[category].settingNames.reduce((bindings, name) => { + const value = getCustomizableShortcuts()[name]?.default; + if (value) { + bindings.push({ + action: name as KeyBindingAction, + keyCombo: value as KeyCombo, + }); + } + return bindings; + }, []); +}; + +const messageComposerBindings = (): KeyBinding[] => { + const bindings = getBindingsByCategory(CategoryName.COMPOSER); -const messageComposerBindings = (): KeyBinding[] => { - const bindings: KeyBinding[] = [ - { - action: MessageComposerAction.SelectPrevSendHistory, - keyCombo: { - key: Key.ARROW_UP, - altKey: true, - ctrlKey: true, - }, - }, - { - action: MessageComposerAction.SelectNextSendHistory, - keyCombo: { - key: Key.ARROW_DOWN, - altKey: true, - ctrlKey: true, - }, - }, - { - action: MessageComposerAction.EditPrevMessage, - keyCombo: { - key: Key.ARROW_UP, - }, - }, - { - action: MessageComposerAction.EditNextMessage, - keyCombo: { - key: Key.ARROW_DOWN, - }, - }, - { - action: MessageComposerAction.CancelEditing, - keyCombo: { - key: Key.ESCAPE, - }, - }, - { - action: MessageComposerAction.FormatBold, - keyCombo: { - key: Key.B, - ctrlOrCmd: true, - }, - }, - { - action: MessageComposerAction.FormatItalics, - keyCombo: { - key: Key.I, - ctrlOrCmd: true, - }, - }, - { - action: MessageComposerAction.FormatQuote, - keyCombo: { - key: Key.GREATER_THAN, - ctrlOrCmd: true, - shiftKey: true, - }, - }, - { - action: MessageComposerAction.EditUndo, - keyCombo: { - key: Key.Z, - ctrlOrCmd: true, - }, - }, - { - action: MessageComposerAction.MoveCursorToStart, - keyCombo: { - key: Key.HOME, - ctrlOrCmd: true, - }, - }, - { - action: MessageComposerAction.MoveCursorToEnd, - keyCombo: { - key: Key.END, - ctrlOrCmd: true, - }, - }, - ]; - if (isMac) { - bindings.push({ - action: MessageComposerAction.EditRedo, - keyCombo: { - key: Key.Z, - ctrlOrCmd: true, - shiftKey: true, - }, - }); - } else { - bindings.push({ - action: MessageComposerAction.EditRedo, - keyCombo: { - key: Key.Y, - ctrlOrCmd: true, - }, - }); - } if (SettingsStore.getValue('MessageComposerInput.ctrlEnterToSend')) { bindings.push({ - action: MessageComposerAction.Send, + action: KeyBindingAction.SendMessage, keyCombo: { key: Key.ENTER, - ctrlOrCmd: true, + ctrlOrCmdKey: true, }, }); bindings.push({ - action: MessageComposerAction.NewLine, + action: KeyBindingAction.NewLine, keyCombo: { key: Key.ENTER, }, }); bindings.push({ - action: MessageComposerAction.NewLine, + action: KeyBindingAction.NewLine, keyCombo: { key: Key.ENTER, shiftKey: true, @@ -149,13 +71,13 @@ const messageComposerBindings = (): KeyBinding[] => { }); } else { bindings.push({ - action: MessageComposerAction.Send, + action: KeyBindingAction.SendMessage, keyCombo: { key: Key.ENTER, }, }); bindings.push({ - action: MessageComposerAction.NewLine, + action: KeyBindingAction.NewLine, keyCombo: { key: Key.ENTER, shiftKey: true, @@ -163,7 +85,7 @@ const messageComposerBindings = (): KeyBinding[] => { }); if (isMac) { bindings.push({ - action: MessageComposerAction.NewLine, + action: KeyBindingAction.NewLine, keyCombo: { key: Key.ENTER, altKey: true, @@ -171,156 +93,56 @@ const messageComposerBindings = (): KeyBinding[] => { }); } } + return bindings; }; -const autocompleteBindings = (): KeyBinding[] => { - return [ - { - action: AutocompleteAction.ForceComplete, - keyCombo: { - key: Key.TAB, - }, +const autocompleteBindings = (): KeyBinding[] => { + const bindings = getBindingsByCategory(CategoryName.AUTOCOMPLETE); + + bindings.push({ + action: KeyBindingAction.ForceCompleteAutocomplete, + keyCombo: { + key: Key.TAB, }, - { - action: AutocompleteAction.ForceComplete, - keyCombo: { - key: Key.TAB, - ctrlKey: true, - }, + }); + bindings.push({ + action: KeyBindingAction.ForceCompleteAutocomplete, + keyCombo: { + key: Key.TAB, + ctrlKey: true, }, - { - action: AutocompleteAction.Complete, - keyCombo: { - key: Key.ENTER, - }, + }); + bindings.push({ + action: KeyBindingAction.CompleteAutocomplete, + keyCombo: { + key: Key.ENTER, }, - { - action: AutocompleteAction.Complete, - keyCombo: { - key: Key.ENTER, - ctrlKey: true, - }, + }); + bindings.push({ + action: KeyBindingAction.CompleteAutocomplete, + keyCombo: { + key: Key.ENTER, + ctrlKey: true, }, - { - action: AutocompleteAction.Cancel, - keyCombo: { - key: Key.ESCAPE, - }, - }, - { - action: AutocompleteAction.PrevSelection, - keyCombo: { - key: Key.ARROW_UP, - }, - }, - { - action: AutocompleteAction.NextSelection, - keyCombo: { - key: Key.ARROW_DOWN, - }, - }, - ]; + }); + + return bindings; }; -const roomListBindings = (): KeyBinding[] => { - return [ - { - action: RoomListAction.ClearSearch, - keyCombo: { - key: Key.ESCAPE, - }, - }, - { - action: RoomListAction.PrevRoom, - keyCombo: { - key: Key.ARROW_UP, - }, - }, - { - action: RoomListAction.NextRoom, - keyCombo: { - key: Key.ARROW_DOWN, - }, - }, - { - action: RoomListAction.SelectRoom, - keyCombo: { - key: Key.ENTER, - }, - }, - { - action: RoomListAction.CollapseSection, - keyCombo: { - key: Key.ARROW_LEFT, - }, - }, - { - action: RoomListAction.ExpandSection, - keyCombo: { - key: Key.ARROW_RIGHT, - }, - }, - ]; +const roomListBindings = (): KeyBinding[] => { + return getBindingsByCategory(CategoryName.ROOM_LIST); }; -const roomBindings = (): KeyBinding[] => { - const bindings: KeyBinding[] = [ - { - action: RoomAction.ScrollUp, - keyCombo: { - key: Key.PAGE_UP, - }, - }, - { - action: RoomAction.RoomScrollDown, - keyCombo: { - key: Key.PAGE_DOWN, - }, - }, - { - action: RoomAction.DismissReadMarker, - keyCombo: { - key: Key.ESCAPE, - }, - }, - { - action: RoomAction.JumpToOldestUnread, - keyCombo: { - key: Key.PAGE_UP, - shiftKey: true, - }, - }, - { - action: RoomAction.UploadFile, - keyCombo: { - key: Key.U, - ctrlOrCmd: true, - shiftKey: true, - }, - }, - { - action: RoomAction.JumpToFirstMessage, - keyCombo: { - key: Key.HOME, - ctrlKey: true, - }, - }, - { - action: RoomAction.JumpToLatestMessage, - keyCombo: { - key: Key.END, - ctrlKey: true, - }, - }, - ]; +const roomBindings = (): KeyBinding[] => { + const bindings = getBindingsByCategory(CategoryName.ROOM); if (SettingsStore.getValue('ctrlFForSearch')) { bindings.push({ - action: RoomAction.FocusSearch, + action: KeyBindingAction.SearchInRoom, keyCombo: { key: Key.F, - ctrlOrCmd: true, + ctrlOrCmdKey: true, }, }); } @@ -328,113 +150,14 @@ const roomBindings = (): KeyBinding[] => { return bindings; }; -const navigationBindings = (): KeyBinding[] => { - return [ - { - action: NavigationAction.FocusRoomSearch, - keyCombo: { - key: Key.K, - ctrlOrCmd: true, - }, - }, - { - action: NavigationAction.ToggleSpacePanel, - keyCombo: { - key: Key.D, - ctrlOrCmd: true, - shiftKey: true, - }, - }, - { - action: NavigationAction.ToggleRoomSidePanel, - keyCombo: { - key: Key.PERIOD, - ctrlOrCmd: true, - }, - }, - { - action: NavigationAction.ToggleUserMenu, - // Ideally this would be CTRL+P for "Profile", but that's - // taken by the print dialog. CTRL+I for "Information" - // was previously chosen but conflicted with italics in - // composer, so CTRL+` it is - keyCombo: { - key: Key.BACKTICK, - ctrlOrCmd: true, - }, - }, - { - action: NavigationAction.OpenShortCutDialog, - keyCombo: { - key: Key.SLASH, - ctrlOrCmd: true, - }, - }, - { - action: NavigationAction.OpenShortCutDialog, - keyCombo: { - key: Key.SLASH, - ctrlOrCmd: true, - shiftKey: true, - }, - }, - { - action: NavigationAction.GoToHome, - keyCombo: { - key: Key.H, - ctrlOrCmd: true, - altKey: !isMac, - shiftKey: isMac, - }, - }, - { - action: NavigationAction.SelectPrevRoom, - keyCombo: { - key: Key.ARROW_UP, - altKey: true, - }, - }, - { - action: NavigationAction.SelectNextRoom, - keyCombo: { - key: Key.ARROW_DOWN, - altKey: true, - }, - }, - { - action: NavigationAction.SelectPrevUnreadRoom, - keyCombo: { - key: Key.ARROW_UP, - altKey: true, - shiftKey: true, - }, - }, - { - action: NavigationAction.SelectNextUnreadRoom, - keyCombo: { - key: Key.ARROW_DOWN, - altKey: true, - shiftKey: true, - }, - }, - ]; +const navigationBindings = (): KeyBinding[] => { + return getBindingsByCategory(CategoryName.NAVIGATION); }; -const labsBindings = (): KeyBinding[] => { - if (!SdkConfig.get()['showLabsSettings']) { - return []; - } +const labsBindings = (): KeyBinding[] => { + if (!SdkConfig.get()['showLabsSettings']) return []; - return [ - { - action: LabsAction.ToggleHiddenEventVisibility, - keyCombo: { - key: Key.H, - ctrlOrCmd: true, - shiftKey: true, - }, - }, - ]; + return getBindingsByCategory(CategoryName.LABS); }; export const defaultBindingsProvider: IKeyBindingsProvider = { diff --git a/src/KeyBindingsManager.ts b/src/KeyBindingsManager.ts index 48968e3121..995ca3bf32 100644 --- a/src/KeyBindingsManager.ts +++ b/src/KeyBindingsManager.ts @@ -1,5 +1,6 @@ /* Copyright 2021 Clemens Zeidler +Copyright 2022 Šimon Brandner Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,127 +15,10 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { KeyBindingAction } from "./accessibility/KeyboardShortcuts"; import { defaultBindingsProvider } from './KeyBindingsDefaults'; import { isMac } from './Keyboard'; -/** Actions for the chat message composer component */ -export enum MessageComposerAction { - /** Send a message */ - Send = 'KeyBinding.sendMessageInComposer', - /** Go backwards through the send history and use the message in composer view */ - SelectPrevSendHistory = 'KeyBinding.previousMessageInComposerHistory', - /** Go forwards through the send history */ - SelectNextSendHistory = 'KeyBinding.nextMessageInComposerHistory', - /** Start editing the user's last sent message */ - EditPrevMessage = 'KeyBinding.editPreviousMessage', - /** Start editing the user's next sent message */ - EditNextMessage = 'KeyBinding.editNextMessage', - /** Cancel editing a message or cancel replying to a message */ - CancelEditing = 'KeyBinding.cancelReplyInComposer', - - /** Set bold format the current selection */ - FormatBold = 'KeyBinding.toggleBoldInComposer', - /** Set italics format the current selection */ - FormatItalics = 'KeyBinding.toggleItalicsInComposer', - /** Format the current selection as quote */ - FormatQuote = 'KeyBinding.toggleQuoteInComposer', - /** Undo the last editing */ - EditUndo = 'KeyBinding.editUndoInComposer', - /** Redo editing */ - EditRedo = 'KeyBinding.editRedoInComposer', - /** Insert new line */ - NewLine = 'KeyBinding.newLineInComposer', - /** Move the cursor to the start of the message */ - MoveCursorToStart = 'KeyBinding.jumpToStartInComposer', - /** Move the cursor to the end of the message */ - MoveCursorToEnd = 'KeyBinding.jumpToEndInComposer', -} - -/** Actions for text editing autocompletion */ -export enum AutocompleteAction { - /** Accepts chosen autocomplete selection */ - Complete = 'KeyBinding.completeAutocomplete', - /** Accepts chosen autocomplete selection or, - * if the autocompletion window is not shown, open the window and select the first selection */ - ForceComplete = 'KeyBinding.forceCompleteAutocomplete', - /** Move to the previous autocomplete selection */ - PrevSelection = 'KeyBinding.previousOptionInAutoComplete', - /** Move to the next autocomplete selection */ - NextSelection = 'KeyBinding.nextOptionInAutoComplete', - /** Close the autocompletion window */ - Cancel = 'KeyBinding.cancelAutoComplete', -} - -/** Actions for the room list sidebar */ -export enum RoomListAction { - /** Clear room list filter field */ - ClearSearch = 'KeyBinding.clearRoomFilter', - /** Navigate up/down in the room list */ - PrevRoom = 'KeyBinding.downerRoom', - /** Navigate down in the room list */ - NextRoom = 'KeyBinding.upperRoom', - /** Select room from the room list */ - SelectRoom = 'KeyBinding.selectRoomInRoomList', - /** Collapse room list section */ - CollapseSection = 'KeyBinding.collapseSectionInRoomList', - /** Expand room list section, if already expanded, jump to first room in the selection */ - ExpandSection = 'KeyBinding.expandSectionInRoomList', -} - -/** Actions for the current room view */ -export enum RoomAction { - /** Scroll up in the timeline */ - ScrollUp = 'KeyBinding.scrollUpInTimeline', - /** Scroll down in the timeline */ - RoomScrollDown = 'KeyBinding.scrollDownInTimeline', - /** Dismiss read marker and jump to bottom */ - DismissReadMarker = 'KeyBinding.dismissReadMarkerAndJumpToBottom', - /** Jump to oldest unread message */ - JumpToOldestUnread = 'KeyBinding.jumpToOldestUnreadMessage', - /** Upload a file */ - UploadFile = 'KeyBinding.uploadFileToRoom', - /** Focus search message in a room (must be enabled) */ - FocusSearch = 'KeyBinding.searchInRoom', - /** Jump to the first (downloaded) message in the room */ - JumpToFirstMessage = 'KeyBinding.jumpToFirstMessageInTimeline', - /** Jump to the latest message in the room */ - JumpToLatestMessage = 'KeyBinding.jumpToLastMessageInTimeline', -} - -/** Actions for navigating do various menus, dialogs or screens */ -export enum NavigationAction { - /** Jump to room search (search for a room) */ - FocusRoomSearch = 'KeyBinding.filterRooms', - /** Toggle the space panel */ - ToggleSpacePanel = 'KeyBinding.toggleSpacePanel', - /** Toggle the room side panel */ - ToggleRoomSidePanel = 'KeyBinding.toggleRightPanel', - /** Toggle the user menu */ - ToggleUserMenu = 'KeyBinding.toggleTopLeftMenu', - /** Toggle the short cut help dialog */ - OpenShortCutDialog = 'KeyBinding.showKeyBindingsSettings', - /** Got to the Element home screen */ - GoToHome = 'KeyBinding.goToHomeView', - /** Select prev room */ - SelectPrevRoom = 'KeyBinding.previousRoom', - /** Select next room */ - SelectNextRoom = 'KeyBinding.nextRoom', - /** Select prev room with unread messages */ - SelectPrevUnreadRoom = 'KeyBinding.previousUnreadRoom', - /** Select next room with unread messages */ - SelectNextUnreadRoom = 'KeyBinding.nextUnreadRoom', -} - -/** Actions only available when labs are enabled */ -export enum LabsAction { - /** Toggle visibility of hidden events */ - ToggleHiddenEventVisibility = 'KeyBinding.toggleHiddenEventVisibility', -} - -export type KeyBindingAction = ( - MessageComposerAction | AutocompleteAction | RoomListAction | RoomAction | NavigationAction | LabsAction -); - /** * Represent a key combination. * @@ -144,7 +28,7 @@ export type KeyCombo = { key?: string; /** On PC: ctrl is pressed; on Mac: meta is pressed */ - ctrlOrCmd?: boolean; + ctrlOrCmdKey?: boolean; altKey?: boolean; ctrlKey?: boolean; @@ -152,8 +36,8 @@ export type KeyCombo = { shiftKey?: boolean; }; -export type KeyBinding = { - action: T; +export type KeyBinding = { + action: KeyBindingAction; keyCombo: KeyCombo; }; @@ -186,7 +70,7 @@ export function isKeyComboMatch(ev: KeyboardEvent | React.KeyboardEvent, combo: const evShift = ev.shiftKey ?? false; const evMeta = ev.metaKey ?? false; // When ctrlOrCmd is set, the keys need do evaluated differently on PC and Mac - if (combo.ctrlOrCmd) { + if (combo.ctrlOrCmdKey) { if (onMac) { if (!evMeta || evCtrl !== comboCtrl @@ -215,15 +99,10 @@ export function isKeyComboMatch(ev: KeyboardEvent | React.KeyboardEvent, combo: return true; } -export type KeyBindingGetter = () => KeyBinding[]; +export type KeyBindingGetter = () => KeyBinding[]; export interface IKeyBindingsProvider { - getMessageComposerBindings: KeyBindingGetter; - getAutocompleteBindings: KeyBindingGetter; - getRoomListBindings: KeyBindingGetter; - getRoomBindings: KeyBindingGetter; - getNavigationBindings: KeyBindingGetter; - getLabsBindings: KeyBindingGetter; + [key: string]: KeyBindingGetter; } export class KeyBindingsManager { @@ -242,10 +121,10 @@ export class KeyBindingsManager { /** * Finds a matching KeyAction for a given KeyboardEvent */ - private getAction( - getters: KeyBindingGetter[], + private getAction( + getters: KeyBindingGetter[], ev: KeyboardEvent | React.KeyboardEvent, - ): T | undefined { + ): KeyBindingAction | undefined { for (const getter of getters) { const bindings = getter(); const binding = bindings.find(it => isKeyComboMatch(ev, it.keyCombo, isMac)); @@ -256,27 +135,27 @@ export class KeyBindingsManager { return undefined; } - getMessageComposerAction(ev: KeyboardEvent | React.KeyboardEvent): MessageComposerAction | undefined { + getMessageComposerAction(ev: KeyboardEvent | React.KeyboardEvent): KeyBindingAction | undefined { return this.getAction(this.bindingsProviders.map(it => it.getMessageComposerBindings), ev); } - getAutocompleteAction(ev: KeyboardEvent | React.KeyboardEvent): AutocompleteAction | undefined { + getAutocompleteAction(ev: KeyboardEvent | React.KeyboardEvent): KeyBindingAction | undefined { return this.getAction(this.bindingsProviders.map(it => it.getAutocompleteBindings), ev); } - getRoomListAction(ev: KeyboardEvent | React.KeyboardEvent): RoomListAction | undefined { + getRoomListAction(ev: KeyboardEvent | React.KeyboardEvent): KeyBindingAction | undefined { return this.getAction(this.bindingsProviders.map(it => it.getRoomListBindings), ev); } - getRoomAction(ev: KeyboardEvent | React.KeyboardEvent): RoomAction | undefined { + getRoomAction(ev: KeyboardEvent | React.KeyboardEvent): KeyBindingAction | undefined { return this.getAction(this.bindingsProviders.map(it => it.getRoomBindings), ev); } - getNavigationAction(ev: KeyboardEvent | React.KeyboardEvent): NavigationAction | undefined { + getNavigationAction(ev: KeyboardEvent | React.KeyboardEvent): KeyBindingAction | undefined { return this.getAction(this.bindingsProviders.map(it => it.getNavigationBindings), ev); } - getLabsAction(ev: KeyboardEvent | React.KeyboardEvent): LabsAction | undefined { + getLabsAction(ev: KeyboardEvent | React.KeyboardEvent): KeyBindingAction | undefined { return this.getAction(this.bindingsProviders.map(it => it.getLabsBindings), ev); } } diff --git a/src/accessibility/KeyboardShortcuts.ts b/src/accessibility/KeyboardShortcuts.ts index 51bba7c02d..29e3d3e75f 100644 --- a/src/accessibility/KeyboardShortcuts.ts +++ b/src/accessibility/KeyboardShortcuts.ts @@ -19,15 +19,104 @@ import { _td } from "../languageHandler"; import { isMac, Key } from "../Keyboard"; import { ISetting } from "../settings/Settings"; import SettingsStore from "../settings/SettingsStore"; -import { - AutocompleteAction, - KeyBindingAction, - LabsAction, - MessageComposerAction, - NavigationAction, - RoomAction, - RoomListAction, -} from "../KeyBindingsManager"; + +export enum KeyBindingAction { + /** Send a message */ + SendMessage = 'KeyBinding.sendMessageInComposer', + /** Go backwards through the send history and use the message in composer view */ + SelectPrevSendHistory = 'KeyBinding.previousMessageInComposerHistory', + /** Go forwards through the send history */ + SelectNextSendHistory = 'KeyBinding.nextMessageInComposerHistory', + /** Start editing the user's last sent message */ + EditPrevMessage = 'KeyBinding.editPreviousMessage', + /** Start editing the user's next sent message */ + EditNextMessage = 'KeyBinding.editNextMessage', + /** Cancel editing a message or cancel replying to a message */ + CancelReplyOrEdit = 'KeyBinding.cancelReplyInComposer', + + /** Set bold format the current selection */ + FormatBold = 'KeyBinding.toggleBoldInComposer', + /** Set italics format the current selection */ + FormatItalics = 'KeyBinding.toggleItalicsInComposer', + /** Format the current selection as quote */ + FormatQuote = 'KeyBinding.toggleQuoteInComposer', + /** Undo the last editing */ + EditUndo = 'KeyBinding.editUndoInComposer', + /** Redo editing */ + EditRedo = 'KeyBinding.editRedoInComposer', + /** Insert new line */ + NewLine = 'KeyBinding.newLineInComposer', + /** Move the cursor to the start of the message */ + MoveCursorToStart = 'KeyBinding.jumpToStartInComposer', + /** Move the cursor to the end of the message */ + MoveCursorToEnd = 'KeyBinding.jumpToEndInComposer', + + /** Accepts chosen autocomplete selection */ + CompleteAutocomplete = 'KeyBinding.completeAutocomplete', + /** Accepts chosen autocomplete selection or, + * if the autocompletion window is not shown, open the window and select the first selection */ + ForceCompleteAutocomplete = 'KeyBinding.forceCompleteAutocomplete', + /** Move to the previous autocomplete selection */ + PrevSelectionInAutocomplete = 'KeyBinding.previousOptionInAutoComplete', + /** Move to the next autocomplete selection */ + NextSelectionInAutocomplete = 'KeyBinding.nextOptionInAutoComplete', + /** Close the autocompletion window */ + CancelAutocomplete = 'KeyBinding.cancelAutoComplete', + + /** Clear room list filter field */ + ClearRoomFilter = 'KeyBinding.clearRoomFilter', + /** Navigate up/down in the room list */ + PrevRoom = 'KeyBinding.downerRoom', + /** Navigate down in the room list */ + NextRoom = 'KeyBinding.upperRoom', + /** Select room from the room list */ + SelectRoomInRoomList = 'KeyBinding.selectRoomInRoomList', + /** Collapse room list section */ + CollapseRoomListSection = 'KeyBinding.collapseSectionInRoomList', + /** Expand room list section, if already expanded, jump to first room in the selection */ + ExpandRoomListSection = 'KeyBinding.expandSectionInRoomList', + + /** Scroll up in the timeline */ + ScrollUp = 'KeyBinding.scrollUpInTimeline', + /** Scroll down in the timeline */ + ScrollDown = 'KeyBinding.scrollDownInTimeline', + /** Dismiss read marker and jump to bottom */ + DismissReadMarker = 'KeyBinding.dismissReadMarkerAndJumpToBottom', + /** Jump to oldest unread message */ + JumpToOldestUnread = 'KeyBinding.jumpToOldestUnreadMessage', + /** Upload a file */ + UploadFile = 'KeyBinding.uploadFileToRoom', + /** Focus search message in a room (must be enabled) */ + SearchInRoom = 'KeyBinding.searchInRoom', + /** Jump to the first (downloaded) message in the room */ + JumpToFirstMessage = 'KeyBinding.jumpToFirstMessageInTimeline', + /** Jump to the latest message in the room */ + JumpToLatestMessage = 'KeyBinding.jumpToLastMessageInTimeline', + + /** Jump to room search (search for a room) */ + FilterRooms = 'KeyBinding.filterRooms', + /** Toggle the space panel */ + ToggleSpacePanel = 'KeyBinding.toggleSpacePanel', + /** Toggle the room side panel */ + ToggleRoomSidePanel = 'KeyBinding.toggleRightPanel', + /** Toggle the user menu */ + ToggleUserMenu = 'KeyBinding.toggleTopLeftMenu', + /** Toggle the short cut help dialog */ + ShowKeyboardSettings = 'KeyBinding.showKeyBindingsSettings', + /** Got to the Element home screen */ + GoToHome = 'KeyBinding.goToHomeView', + /** Select prev room */ + SelectPrevRoom = 'KeyBinding.previousRoom', + /** Select next room */ + SelectNextRoom = 'KeyBinding.nextRoom', + /** Select prev room with unread messages */ + SelectPrevUnreadRoom = 'KeyBinding.previousUnreadRoom', + /** Select next room with unread messages */ + SelectNextUnreadRoom = 'KeyBinding.nextUnreadRoom', + + /** Toggle visibility of hidden events */ + ToggleHiddenEventVisibility = 'KeyBinding.toggleHiddenEventVisibility', +} type IKeyboardShortcuts = { // TODO: We should figure out what to do with the keyboard shortcuts that are not handled by KeybindingManager @@ -81,20 +170,20 @@ export const CATEGORIES: Record = { [CategoryName.COMPOSER]: { categoryLabel: _td("Composer"), settingNames: [ - MessageComposerAction.Send, - MessageComposerAction.FormatBold, - MessageComposerAction.FormatItalics, - MessageComposerAction.FormatQuote, - MessageComposerAction.NewLine, - MessageComposerAction.CancelEditing, - MessageComposerAction.EditNextMessage, - MessageComposerAction.EditPrevMessage, - MessageComposerAction.MoveCursorToStart, - MessageComposerAction.MoveCursorToEnd, - MessageComposerAction.SelectNextSendHistory, - MessageComposerAction.EditPrevMessage, - MessageComposerAction.EditUndo, - MessageComposerAction.EditRedo, + KeyBindingAction.SendMessage, + KeyBindingAction.NewLine, + KeyBindingAction.FormatBold, + KeyBindingAction.FormatItalics, + KeyBindingAction.FormatQuote, + KeyBindingAction.EditUndo, + KeyBindingAction.EditRedo, + KeyBindingAction.MoveCursorToStart, + KeyBindingAction.MoveCursorToEnd, + KeyBindingAction.CancelReplyOrEdit, + KeyBindingAction.EditNextMessage, + KeyBindingAction.EditPrevMessage, + KeyBindingAction.SelectNextSendHistory, + KeyBindingAction.SelectPrevSendHistory, ], }, [CategoryName.CALLS]: { categoryLabel: _td("Calls"), @@ -105,54 +194,54 @@ export const CATEGORIES: Record = { }, [CategoryName.ROOM]: { categoryLabel: _td("Room"), settingNames: [ - RoomAction.DismissReadMarker, - RoomAction.JumpToOldestUnread, - RoomAction.UploadFile, - RoomAction.FocusSearch, - RoomAction.ScrollUp, - RoomAction.RoomScrollDown, - RoomAction.JumpToFirstMessage, - RoomAction.JumpToLatestMessage, + KeyBindingAction.SearchInRoom, + KeyBindingAction.UploadFile, + KeyBindingAction.DismissReadMarker, + KeyBindingAction.JumpToOldestUnread, + KeyBindingAction.ScrollUp, + KeyBindingAction.ScrollDown, + KeyBindingAction.JumpToFirstMessage, + KeyBindingAction.JumpToLatestMessage, ], }, [CategoryName.ROOM_LIST]: { categoryLabel: _td("Room List"), settingNames: [ - RoomListAction.SelectRoom, - RoomListAction.CollapseSection, - RoomListAction.ExpandSection, - RoomListAction.ClearSearch, - RoomListAction.NextRoom, - RoomListAction.PrevRoom, + KeyBindingAction.SelectRoomInRoomList, + KeyBindingAction.ClearRoomFilter, + KeyBindingAction.CollapseRoomListSection, + KeyBindingAction.ExpandRoomListSection, + KeyBindingAction.NextRoom, + KeyBindingAction.PrevRoom, ], }, [CategoryName.NAVIGATION]: { categoryLabel: _td("Navigation"), settingNames: [ - NavigationAction.ToggleUserMenu, + KeyBindingAction.ToggleUserMenu, "KeyBinding.closeDialogOrContextMenu", "KeyBinding.activateSelectedButton", - NavigationAction.ToggleRoomSidePanel, - NavigationAction.OpenShortCutDialog, - NavigationAction.GoToHome, - NavigationAction.SelectNextUnreadRoom, - NavigationAction.SelectPrevUnreadRoom, - NavigationAction.SelectNextRoom, - NavigationAction.SelectPrevRoom, - NavigationAction.ToggleSpacePanel, - NavigationAction.FocusRoomSearch, + KeyBindingAction.ToggleRoomSidePanel, + KeyBindingAction.ToggleSpacePanel, + KeyBindingAction.ShowKeyboardSettings, + KeyBindingAction.GoToHome, + KeyBindingAction.FilterRooms, + KeyBindingAction.SelectNextUnreadRoom, + KeyBindingAction.SelectPrevUnreadRoom, + KeyBindingAction.SelectNextRoom, + KeyBindingAction.SelectPrevRoom, ], }, [CategoryName.AUTOCOMPLETE]: { categoryLabel: _td("Autocomplete"), settingNames: [ - AutocompleteAction.Cancel, - AutocompleteAction.NextSelection, - AutocompleteAction.PrevSelection, - AutocompleteAction.Complete, - AutocompleteAction.ForceComplete, + KeyBindingAction.CancelAutocomplete, + KeyBindingAction.NextSelectionInAutocomplete, + KeyBindingAction.PrevSelectionInAutocomplete, + KeyBindingAction.CompleteAutocomplete, + KeyBindingAction.ForceCompleteAutocomplete, ], }, [CategoryName.LABS]: { categoryLabel: _td("Labs"), settingNames: [ - LabsAction.ToggleHiddenEventVisibility, + KeyBindingAction.ToggleHiddenEventVisibility, ], }, }; @@ -161,73 +250,73 @@ export const CATEGORIES: Record = { // to implement customizable keyboard shortcuts // TODO: TravisR will fix this nightmare when the new version of the SettingsStore becomes a thing const KEYBOARD_SHORTCUTS: IKeyboardShortcuts = { - [MessageComposerAction.FormatBold]: { + [KeyBindingAction.FormatBold]: { default: { ctrlOrCmdKey: true, key: Key.B, }, displayName: _td("Toggle Bold"), }, - [MessageComposerAction.FormatItalics]: { + [KeyBindingAction.FormatItalics]: { default: { ctrlOrCmdKey: true, key: Key.I, }, displayName: _td("Toggle Italics"), }, - [MessageComposerAction.FormatQuote]: { + [KeyBindingAction.FormatQuote]: { default: { ctrlOrCmdKey: true, key: Key.GREATER_THAN, }, displayName: _td("Toggle Quote"), }, - [MessageComposerAction.CancelEditing]: { + [KeyBindingAction.CancelReplyOrEdit]: { default: { key: Key.ESCAPE, }, displayName: _td("Cancel replying to a message"), }, - [MessageComposerAction.EditNextMessage]: { - default: { - key: Key.ARROW_UP, - }, - displayName: _td("Navigate to next message to edit"), - }, - [MessageComposerAction.EditPrevMessage]: { + [KeyBindingAction.EditNextMessage]: { default: { key: Key.ARROW_DOWN, }, + displayName: _td("Navigate to next message to edit"), + }, + [KeyBindingAction.EditPrevMessage]: { + default: { + key: Key.ARROW_UP, + }, displayName: _td("Navigate to previous message to edit"), }, - [MessageComposerAction.MoveCursorToStart]: { + [KeyBindingAction.MoveCursorToStart]: { default: { ctrlOrCmdKey: true, key: Key.HOME, }, displayName: _td("Jump to start of the composer"), }, - [MessageComposerAction.MoveCursorToEnd]: { + [KeyBindingAction.MoveCursorToEnd]: { default: { ctrlOrCmdKey: true, key: Key.END, }, displayName: _td("Jump to end of the composer"), }, - [MessageComposerAction.SelectNextSendHistory]: { - default: { - altKey: true, - ctrlKey: true, - key: Key.ARROW_UP, - }, - displayName: _td("Navigate to next message in composer history"), - }, - [MessageComposerAction.SelectPrevSendHistory]: { + [KeyBindingAction.SelectNextSendHistory]: { default: { altKey: true, ctrlKey: true, key: Key.ARROW_DOWN, }, + displayName: _td("Navigate to next message in composer history"), + }, + [KeyBindingAction.SelectPrevSendHistory]: { + default: { + altKey: true, + ctrlKey: true, + key: Key.ARROW_UP, + }, displayName: _td("Navigate to previous message in composer history"), }, "KeyBinding.toggleMicInCall": { @@ -244,20 +333,20 @@ const KEYBOARD_SHORTCUTS: IKeyboardShortcuts = { }, displayName: _td("Toggle webcam on/off"), }, - [RoomAction.DismissReadMarker]: { + [KeyBindingAction.DismissReadMarker]: { default: { key: Key.ESCAPE, }, displayName: _td("Dismiss read marker and jump to bottom"), }, - [RoomAction.JumpToOldestUnread]: { + [KeyBindingAction.JumpToOldestUnread]: { default: { shiftKey: true, key: Key.PAGE_UP, }, displayName: _td("Jump to oldest unread message"), }, - [RoomAction.UploadFile]: { + [KeyBindingAction.UploadFile]: { default: { ctrlOrCmdKey: true, shiftKey: true, @@ -265,102 +354,83 @@ const KEYBOARD_SHORTCUTS: IKeyboardShortcuts = { }, displayName: _td("Upload a file"), }, - [RoomAction.FocusSearch]: { - default: { - ctrlOrCmdKey: true, - key: Key.F, - }, - displayName: _td("Search (must be enabled)"), - }, - [RoomAction.ScrollUp]: { + [KeyBindingAction.ScrollUp]: { default: { key: Key.PAGE_UP, }, displayName: _td("Scroll up in the timeline"), }, - [RoomAction.RoomScrollDown]: { + [KeyBindingAction.ScrollDown]: { default: { key: Key.PAGE_DOWN, }, displayName: _td("Scroll down in the timeline"), }, - [NavigationAction.FocusRoomSearch]: { + [KeyBindingAction.FilterRooms]: { default: { ctrlOrCmdKey: true, key: Key.K, }, displayName: _td("Jump to room search"), }, - [RoomListAction.SelectRoom]: { + [KeyBindingAction.SelectRoomInRoomList]: { default: { key: Key.ENTER, }, displayName: _td("Select room from the room list"), }, - [RoomListAction.CollapseSection]: { + [KeyBindingAction.CollapseRoomListSection]: { default: { key: Key.ARROW_LEFT, }, displayName: _td("Collapse room list section"), }, - [RoomListAction.ExpandSection]: { + [KeyBindingAction.ExpandRoomListSection]: { default: { key: Key.ARROW_RIGHT, }, displayName: _td("Expand room list section"), }, - [RoomListAction.ClearSearch]: { + [KeyBindingAction.ClearRoomFilter]: { default: { key: Key.ESCAPE, }, displayName: _td("Clear room list filter field"), }, - [RoomListAction.NextRoom]: { - default: { - key: Key.ARROW_UP, - }, - displayName: _td("Navigate up in the room list"), - }, - [RoomListAction.PrevRoom]: { + [KeyBindingAction.NextRoom]: { default: { key: Key.ARROW_DOWN, }, + displayName: _td("Navigate up in the room list"), + }, + [KeyBindingAction.PrevRoom]: { + default: { + key: Key.ARROW_UP, + }, displayName: _td("Navigate down in the room list"), }, - [NavigationAction.ToggleUserMenu]: { + [KeyBindingAction.ToggleUserMenu]: { default: { ctrlOrCmdKey: true, key: Key.BACKTICK, }, displayName: _td("Toggle the top left menu"), }, - "KeyBinding.closeDialogOrContextMenu": { - default: { - key: Key.ESCAPE, - }, - displayName: _td("Close dialog or context menu"), - }, - "KeyBinding.activateSelectedButton": { - default: { - key: Key.ENTER, - }, - displayName: _td("Activate selected button"), - }, - [NavigationAction.ToggleRoomSidePanel]: { + [KeyBindingAction.ToggleRoomSidePanel]: { default: { ctrlOrCmdKey: true, key: Key.PERIOD, }, displayName: _td("Toggle right panel"), }, - [NavigationAction.OpenShortCutDialog]: { + [KeyBindingAction.ShowKeyboardSettings]: { default: { ctrlOrCmdKey: true, key: Key.SLASH, }, displayName: _td("Open this settings tab"), }, - [NavigationAction.GoToHome]: { + [KeyBindingAction.GoToHome]: { default: { ctrlOrCmdKey: true, altKey: !isMac, @@ -369,55 +439,55 @@ const KEYBOARD_SHORTCUTS: IKeyboardShortcuts = { }, displayName: _td("Go to Home View"), }, - [NavigationAction.SelectNextUnreadRoom]: { + [KeyBindingAction.SelectNextUnreadRoom]: { default: { shiftKey: true, altKey: true, - key: Key.ARROW_UP, + key: Key.ARROW_DOWN, }, displayName: _td("Next unread room or DM"), }, - [NavigationAction.SelectPrevUnreadRoom]: { + [KeyBindingAction.SelectPrevUnreadRoom]: { default: { shiftKey: true, altKey: true, - key: Key.ARROW_DOWN, + key: Key.ARROW_UP, }, displayName: _td("Previous unread room or DM"), }, - [NavigationAction.SelectNextRoom]: { + [KeyBindingAction.SelectNextRoom]: { + default: { + altKey: true, + key: Key.ARROW_DOWN, + }, + displayName: _td("Next room or DM"), + }, + [KeyBindingAction.SelectPrevRoom]: { default: { altKey: true, key: Key.ARROW_UP, }, - displayName: _td("Next room or DM"), - }, - [NavigationAction.SelectPrevRoom]: { - default: { - altKey: true, - key: Key.ARROW_DOWN, - }, displayName: _td("Previous room or DM"), }, - [AutocompleteAction.Cancel]: { + [KeyBindingAction.CancelAutocomplete]: { default: { key: Key.ESCAPE, }, displayName: _td("Cancel autocomplete"), }, - [AutocompleteAction.NextSelection]: { - default: { - key: Key.ARROW_UP, - }, - displayName: _td("Next autocomplete suggestion"), - }, - [AutocompleteAction.PrevSelection]: { + [KeyBindingAction.NextSelectionInAutocomplete]: { default: { key: Key.ARROW_DOWN, }, + displayName: _td("Next autocomplete suggestion"), + }, + [KeyBindingAction.PrevSelectionInAutocomplete]: { + default: { + key: Key.ARROW_UP, + }, displayName: _td("Previous autocomplete suggestion"), }, - [NavigationAction.ToggleSpacePanel]: { + [KeyBindingAction.ToggleSpacePanel]: { default: { ctrlOrCmdKey: true, shiftKey: true, @@ -425,7 +495,7 @@ const KEYBOARD_SHORTCUTS: IKeyboardShortcuts = { }, displayName: _td("Toggle space panel"), }, - [LabsAction.ToggleHiddenEventVisibility]: { + [KeyBindingAction.ToggleHiddenEventVisibility]: { default: { ctrlOrCmdKey: true, shiftKey: true, @@ -433,61 +503,86 @@ const KEYBOARD_SHORTCUTS: IKeyboardShortcuts = { }, displayName: _td("Toggle hidden event visibility"), }, - [RoomAction.JumpToFirstMessage]: { + [KeyBindingAction.JumpToFirstMessage]: { default: { key: Key.HOME, ctrlKey: true, }, displayName: _td("Jump to first message"), }, - [RoomAction.JumpToOldestUnread]: { + [KeyBindingAction.JumpToOldestUnread]: { default: { key: Key.END, ctrlKey: true, }, displayName: _td("Jump to last message"), }, - [MessageComposerAction.EditUndo]: { + [KeyBindingAction.EditUndo]: { default: { key: Key.Z, ctrlOrCmdKey: true, }, displayName: _td("Undo edit"), }, - [AutocompleteAction.Complete]: { - default: { - key: Key.ENTER, - }, - displayName: _td("Complete"), - }, - [AutocompleteAction.ForceComplete]: { - default: { - key: Key.TAB, - }, - displayName: _td("Force complete"), - }, }; -export const getKeyboardShortcuts = (): IKeyboardShortcuts => { - const keyboardShortcuts = KEYBOARD_SHORTCUTS; +// XXX: These have to be manually mirrored in KeyBindingDefaults +const getNonCustomizableShortcuts = (): IKeyboardShortcuts => { const ctrlEnterToSend = SettingsStore.getValue('MessageComposerInput.ctrlEnterToSend'); - keyboardShortcuts[MessageComposerAction.Send] = { - default: { - key: Key.ENTER, - ctrlOrCmdKey: ctrlEnterToSend, + return { + [KeyBindingAction.SendMessage]: { + default: { + key: Key.ENTER, + ctrlOrCmdKey: ctrlEnterToSend, + }, + displayName: _td("Send message"), }, - displayName: _td("Send message"), + [KeyBindingAction.NewLine]: { + default: { + key: Key.ENTER, + shiftKey: !ctrlEnterToSend, + }, + displayName: _td("New line"), + }, + [KeyBindingAction.CompleteAutocomplete]: { + default: { + key: Key.ENTER, + }, + displayName: _td("Complete"), + }, + [KeyBindingAction.ForceCompleteAutocomplete]: { + default: { + key: Key.TAB, + }, + displayName: _td("Force complete"), + }, + [KeyBindingAction.SearchInRoom]: { + default: { + ctrlOrCmdKey: true, + key: Key.F, + }, + displayName: _td("Search (must be enabled)"), + }, + "KeyBinding.closeDialogOrContextMenu": { + default: { + key: Key.ESCAPE, + }, + displayName: _td("Close dialog or context menu"), + }, + "KeyBinding.activateSelectedButton": { + default: { + key: Key.ENTER, + }, + displayName: _td("Activate selected button"), + }, + }; +}; - }; - keyboardShortcuts[MessageComposerAction.NewLine] = { - default: { - key: Key.ENTER, - shiftKey: !ctrlEnterToSend, - }, - displayName: _td("New line"), - }; - keyboardShortcuts[MessageComposerAction.EditRedo] = { +export const getCustomizableShortcuts = (): IKeyboardShortcuts => { + const keyboardShortcuts = KEYBOARD_SHORTCUTS; + + keyboardShortcuts[KeyBindingAction.EditRedo] = { default: { key: isMac ? Key.Z : Key.Y, ctrlOrCmdKey: true, @@ -499,6 +594,19 @@ export const getKeyboardShortcuts = (): IKeyboardShortcuts => { return keyboardShortcuts; }; +export const getKeyboardShortcuts = (): IKeyboardShortcuts => { + const entries = [ + ...Object.entries(getNonCustomizableShortcuts()), + ...Object.entries(getCustomizableShortcuts()), + ]; + + const keyboardShortcuts: IKeyboardShortcuts = {}; + for (const [key, value] of entries) { + keyboardShortcuts[key] = value; + } + return keyboardShortcuts; +}; + export const registerShortcut = (shortcutName: string, categoryName: CategoryName, shortcut: ISetting): void => { KEYBOARD_SHORTCUTS[shortcutName] = shortcut; CATEGORIES[categoryName].settingNames.push(shortcutName); diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx index fb9fcd5d76..b9515fef46 100644 --- a/src/components/structures/LeftPanel.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -31,7 +31,7 @@ import LeftPanelWidget from "./LeftPanelWidget"; import { replaceableComponent } from "../../utils/replaceableComponent"; import SpaceStore from "../../stores/spaces/SpaceStore"; import { MetaSpace, SpaceKey, UPDATE_SELECTED_SPACE } from "../../stores/spaces"; -import { getKeyBindingsManager, RoomListAction } from "../../KeyBindingsManager"; +import { getKeyBindingsManager } from "../../KeyBindingsManager"; import UIStore from "../../stores/UIStore"; import { findSiblingElement, IState as IRovingTabIndexState } from "../../accessibility/RovingTabIndex"; import RoomListHeader from "../views/rooms/RoomListHeader"; @@ -44,6 +44,7 @@ import IndicatorScrollbar from "./IndicatorScrollbar"; import RoomBreadcrumbs from "../views/rooms/RoomBreadcrumbs"; import SettingsStore from "../../settings/SettingsStore"; import UserMenu from "./UserMenu"; +import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts"; interface IProps { isMinimized: boolean; @@ -296,7 +297,7 @@ export default class LeftPanel extends React.Component { const action = getKeyBindingsManager().getRoomListAction(ev); switch (action) { - case RoomListAction.NextRoom: + case KeyBindingAction.NextRoom: if (!state) { ev.stopPropagation(); ev.preventDefault(); @@ -304,7 +305,7 @@ export default class LeftPanel extends React.Component { } break; - case RoomListAction.PrevRoom: + case KeyBindingAction.PrevRoom: if (state && state.activeRef === findSiblingElement(state.refs, 0)) { ev.stopPropagation(); ev.preventDefault(); diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 8ccbd5e3c5..8e967169da 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -48,7 +48,7 @@ import { IOOBData, IThreepidInvite } from "../../stores/ThreepidInviteStore"; import Modal from "../../Modal"; import { ICollapseConfig } from "../../resizer/distributors/collapse"; import HostSignupContainer from '../views/host_signup/HostSignupContainer'; -import { getKeyBindingsManager, NavigationAction, RoomAction, LabsAction } from '../../KeyBindingsManager'; +import { getKeyBindingsManager } from '../../KeyBindingsManager'; import { IOpts } from "../../createRoom"; import SpacePanel from "../views/spaces/SpacePanel"; import { replaceableComponent } from "../../utils/replaceableComponent"; @@ -71,6 +71,7 @@ import { UserTab } from "../views/dialogs/UserSettingsDialog"; import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload"; import RightPanelStore from '../../stores/right-panel/RightPanelStore'; import { TimelineRenderingType } from "../../contexts/RoomContext"; +import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts"; // We need to fetch each pinned message individually (if we don't already have it) // so each pinned message may trigger a request. Limit the number per room for sanity. @@ -447,15 +448,15 @@ class LoggedInView extends React.Component { const roomAction = getKeyBindingsManager().getRoomAction(ev); switch (roomAction) { - case RoomAction.ScrollUp: - case RoomAction.RoomScrollDown: - case RoomAction.JumpToFirstMessage: - case RoomAction.JumpToLatestMessage: + case KeyBindingAction.ScrollUp: + case KeyBindingAction.ScrollDown: + case KeyBindingAction.JumpToFirstMessage: + case KeyBindingAction.JumpToLatestMessage: // pass the event down to the scroll panel this.onScrollKeyPressed(ev); handled = true; break; - case RoomAction.FocusSearch: + case KeyBindingAction.SearchInRoom: dis.dispatch({ action: 'focus_search', }); @@ -470,41 +471,41 @@ class LoggedInView extends React.Component { const navAction = getKeyBindingsManager().getNavigationAction(ev); switch (navAction) { - case NavigationAction.FocusRoomSearch: + case KeyBindingAction.FilterRooms: dis.dispatch({ action: 'focus_room_filter', }); handled = true; break; - case NavigationAction.ToggleUserMenu: + case KeyBindingAction.ToggleUserMenu: dis.fire(Action.ToggleUserMenu); handled = true; break; - case NavigationAction.OpenShortCutDialog: + case KeyBindingAction.ShowKeyboardSettings: dis.dispatch({ action: Action.ViewUserSettings, initialTabId: UserTab.Keyboard, }); handled = true; break; - case NavigationAction.GoToHome: + case KeyBindingAction.GoToHome: dis.dispatch({ action: 'view_home_page', }); Modal.closeCurrentModal("homeKeyboardShortcut"); handled = true; break; - case NavigationAction.ToggleSpacePanel: + case KeyBindingAction.ToggleSpacePanel: dis.fire(Action.ToggleSpacePanel); handled = true; break; - case NavigationAction.ToggleRoomSidePanel: + case KeyBindingAction.ToggleRoomSidePanel: if (this.props.page_type === "room_view" || this.props.page_type === "group_view") { RightPanelStore.instance.togglePanel(); handled = true; } break; - case NavigationAction.SelectPrevRoom: + case KeyBindingAction.SelectPrevRoom: dis.dispatch({ action: Action.ViewRoomDelta, delta: -1, @@ -512,7 +513,7 @@ class LoggedInView extends React.Component { }); handled = true; break; - case NavigationAction.SelectNextRoom: + case KeyBindingAction.SelectNextRoom: dis.dispatch({ action: Action.ViewRoomDelta, delta: 1, @@ -520,14 +521,14 @@ class LoggedInView extends React.Component { }); handled = true; break; - case NavigationAction.SelectPrevUnreadRoom: + case KeyBindingAction.SelectPrevUnreadRoom: dis.dispatch({ action: Action.ViewRoomDelta, delta: -1, unread: true, }); break; - case NavigationAction.SelectNextUnreadRoom: + case KeyBindingAction.SelectNextUnreadRoom: dis.dispatch({ action: Action.ViewRoomDelta, delta: 1, @@ -543,7 +544,7 @@ class LoggedInView extends React.Component { if (!handled) { const labsAction = getKeyBindingsManager().getLabsAction(ev); switch (labsAction) { - case LabsAction.ToggleHiddenEventVisibility: { + case KeyBindingAction.ToggleHiddenEventVisibility: { const hiddenEventVisibility = SettingsStore.getValueAt( SettingLevel.DEVICE, 'showHiddenEventsInTimeline', diff --git a/src/components/structures/RoomSearch.tsx b/src/components/structures/RoomSearch.tsx index 6d21f5a464..1151b915ad 100644 --- a/src/components/structures/RoomSearch.tsx +++ b/src/components/structures/RoomSearch.tsx @@ -25,7 +25,7 @@ import AccessibleButton from "../views/elements/AccessibleButton"; import { Action } from "../../dispatcher/actions"; import RoomListStore from "../../stores/room-list/RoomListStore"; import { NameFilterCondition } from "../../stores/room-list/filters/NameFilterCondition"; -import { getKeyBindingsManager, RoomListAction } from "../../KeyBindingsManager"; +import { getKeyBindingsManager } from "../../KeyBindingsManager"; import { replaceableComponent } from "../../utils/replaceableComponent"; import SpaceStore from "../../stores/spaces/SpaceStore"; import { UPDATE_SELECTED_SPACE } from "../../stores/spaces"; @@ -33,7 +33,7 @@ import { isMac, Key } from "../../Keyboard"; import SettingsStore from "../../settings/SettingsStore"; import Modal from "../../Modal"; import SpotlightDialog from "../views/dialogs/SpotlightDialog"; -import { ALTERNATE_KEY_NAME } from "../../accessibility/KeyboardShortcuts"; +import { ALTERNATE_KEY_NAME, KeyBindingAction } from "../../accessibility/KeyboardShortcuts"; interface IProps { isMinimized: boolean; @@ -141,11 +141,11 @@ export default class RoomSearch extends React.PureComponent { private onKeyDown = (ev: React.KeyboardEvent) => { const action = getKeyBindingsManager().getRoomListAction(ev); switch (action) { - case RoomListAction.ClearSearch: + case KeyBindingAction.ClearRoomFilter: this.clearInput(); defaultDispatcher.fire(Action.FocusSendMessageComposer); break; - case RoomListAction.SelectRoom: { + case KeyBindingAction.SelectRoomInRoomList: { const shouldClear = this.props.onSelectRoom(); if (shouldClear) { // wrap in set immediate to delay it so that we don't clear the filter & then change room diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 0386c3469e..d7eb1b3d3a 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -79,7 +79,7 @@ import Notifier from "../../Notifier"; import { showToast as showNotificationsToast } from "../../toasts/DesktopNotificationsToast"; import { RoomNotificationStateStore } from "../../stores/notifications/RoomNotificationStateStore"; import { Container, WidgetLayoutStore } from "../../stores/widgets/WidgetLayoutStore"; -import { getKeyBindingsManager, RoomAction } from '../../KeyBindingsManager'; +import { getKeyBindingsManager } from '../../KeyBindingsManager'; import { objectHasDiff } from "../../utils/objects"; import SpaceRoomView from "./SpaceRoomView"; import { IOpts } from "../../createRoom"; @@ -100,6 +100,7 @@ import { ComposerType } from "../../dispatcher/payloads/ComposerInsertPayload"; import AppsDrawer from '../views/rooms/AppsDrawer'; import { RightPanelPhases } from '../../stores/right-panel/RightPanelStorePhases'; import { ActionPayload } from "../../dispatcher/payloads"; +import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts"; const DEBUG = false; let debuglog = function(msg: string) {}; @@ -797,16 +798,16 @@ export class RoomView extends React.Component { const action = getKeyBindingsManager().getRoomAction(ev); switch (action) { - case RoomAction.DismissReadMarker: + case KeyBindingAction.DismissReadMarker: this.messagePanel.forgetReadMarker(); this.jumpToLiveTimeline(); handled = true; break; - case RoomAction.JumpToOldestUnread: + case KeyBindingAction.JumpToOldestUnread: this.jumpToReadMarker(); handled = true; break; - case RoomAction.UploadFile: + case KeyBindingAction.UploadFile: dis.dispatch({ action: "upload_file" }, true); handled = true; break; diff --git a/src/components/structures/ScrollPanel.tsx b/src/components/structures/ScrollPanel.tsx index d2b386f653..93a4a74cfe 100644 --- a/src/components/structures/ScrollPanel.tsx +++ b/src/components/structures/ScrollPanel.tsx @@ -20,8 +20,9 @@ import { logger } from "matrix-js-sdk/src/logger"; import Timer from '../../utils/Timer'; import AutoHideScrollbar from "./AutoHideScrollbar"; import { replaceableComponent } from "../../utils/replaceableComponent"; -import { getKeyBindingsManager, RoomAction } from "../../KeyBindingsManager"; +import { getKeyBindingsManager } from "../../KeyBindingsManager"; import ResizeNotifier from "../../utils/ResizeNotifier"; +import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts"; const DEBUG_SCROLL = false; @@ -592,19 +593,19 @@ export default class ScrollPanel extends React.Component { let isScrolling = false; const roomAction = getKeyBindingsManager().getRoomAction(ev); switch (roomAction) { - case RoomAction.ScrollUp: + case KeyBindingAction.ScrollUp: this.scrollRelative(-1); isScrolling = true; break; - case RoomAction.RoomScrollDown: + case KeyBindingAction.ScrollDown: this.scrollRelative(1); isScrolling = true; break; - case RoomAction.JumpToFirstMessage: + case KeyBindingAction.JumpToFirstMessage: this.scrollToTop(); isScrolling = true; break; - case RoomAction.JumpToLatestMessage: + case KeyBindingAction.JumpToLatestMessage: this.scrollToBottom(); isScrolling = true; break; diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index e923771808..44fa879b84 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -48,9 +48,9 @@ import { IDiff } from "../../../editor/diff"; import AutocompleteWrapperModel from "../../../editor/autocomplete"; import DocumentPosition from "../../../editor/position"; import { ICompletion } from "../../../autocomplete/Autocompleter"; -import { AutocompleteAction, getKeyBindingsManager, MessageComposerAction } from '../../../KeyBindingsManager'; +import { getKeyBindingsManager } from '../../../KeyBindingsManager'; import { replaceableComponent } from "../../../utils/replaceableComponent"; -import { ALTERNATE_KEY_NAME } from '../../../accessibility/KeyboardShortcuts'; +import { ALTERNATE_KEY_NAME, KeyBindingAction } from '../../../accessibility/KeyboardShortcuts'; import { _t } from "../../../languageHandler"; // matches emoticons which follow the start of a line or whitespace @@ -483,29 +483,29 @@ export default class BasicMessageEditor extends React.Component if (model.autoComplete?.hasCompletions()) { const autoComplete = model.autoComplete; switch (autocompleteAction) { - case AutocompleteAction.ForceComplete: - case AutocompleteAction.Complete: + case KeyBindingAction.ForceCompleteAutocomplete: + case KeyBindingAction.CompleteAutocomplete: this.historyManager.ensureLastChangesPushed(this.props.model); this.modifiedFlag = true; autoComplete.confirmCompletion(); handled = true; break; - case AutocompleteAction.PrevSelection: + case KeyBindingAction.PrevSelectionInAutocomplete: autoComplete.selectPreviousSelection(); handled = true; break; - case AutocompleteAction.NextSelection: + case KeyBindingAction.NextSelectionInAutocomplete: autoComplete.selectNextSelection(); handled = true; break; - case AutocompleteAction.Cancel: + case KeyBindingAction.CancelAutocomplete: autoComplete.onEscape(event); handled = true; break; default: return; // don't preventDefault on anything else } - } else if (autocompleteAction === AutocompleteAction.ForceComplete && !this.state.showVisualBell) { + } else if (autocompleteAction === KeyBindingAction.ForceCompleteAutocomplete && !this.state.showVisualBell) { // there is no current autocomplete window, try to open it this.tabCompleteName(); handled = true; @@ -521,19 +521,19 @@ export default class BasicMessageEditor extends React.Component const action = getKeyBindingsManager().getMessageComposerAction(event); switch (action) { - case MessageComposerAction.FormatBold: + case KeyBindingAction.FormatBold: this.onFormatAction(Formatting.Bold); handled = true; break; - case MessageComposerAction.FormatItalics: + case KeyBindingAction.FormatItalics: this.onFormatAction(Formatting.Italics); handled = true; break; - case MessageComposerAction.FormatQuote: + case KeyBindingAction.FormatQuote: this.onFormatAction(Formatting.Quote); handled = true; break; - case MessageComposerAction.EditRedo: + case KeyBindingAction.EditRedo: if (this.historyManager.canRedo()) { const { parts, caret } = this.historyManager.redo(); // pass matching inputType so historyManager doesn't push echo @@ -542,7 +542,7 @@ export default class BasicMessageEditor extends React.Component } handled = true; break; - case MessageComposerAction.EditUndo: + case KeyBindingAction.EditUndo: if (this.historyManager.canUndo()) { const { parts, caret } = this.historyManager.undo(this.props.model); // pass matching inputType so historyManager doesn't push echo @@ -551,18 +551,18 @@ export default class BasicMessageEditor extends React.Component } handled = true; break; - case MessageComposerAction.NewLine: + case KeyBindingAction.NewLine: this.insertText("\n"); handled = true; break; - case MessageComposerAction.MoveCursorToStart: + case KeyBindingAction.MoveCursorToStart: setSelection(this.editorRef.current, model, { index: 0, offset: 0, }); handled = true; break; - case MessageComposerAction.MoveCursorToEnd: + case KeyBindingAction.MoveCursorToEnd: setSelection(this.editorRef.current, model, { index: model.parts.length - 1, offset: model.parts[model.parts.length - 1].text.length, diff --git a/src/components/views/rooms/EditMessageComposer.tsx b/src/components/views/rooms/EditMessageComposer.tsx index f74608aca2..c7ee882526 100644 --- a/src/components/views/rooms/EditMessageComposer.tsx +++ b/src/components/views/rooms/EditMessageComposer.tsx @@ -34,7 +34,7 @@ import BasicMessageComposer, { REGEX_EMOTICON } from "./BasicMessageComposer"; import { CommandCategories } from '../../../SlashCommands'; import { Action } from "../../../dispatcher/actions"; import CountlyAnalytics from "../../../CountlyAnalytics"; -import { getKeyBindingsManager, MessageComposerAction } from '../../../KeyBindingsManager'; +import { getKeyBindingsManager } from '../../../KeyBindingsManager'; import { replaceableComponent } from "../../../utils/replaceableComponent"; import SendHistoryManager from '../../../SendHistoryManager'; import { ActionPayload } from "../../../dispatcher/payloads"; @@ -45,6 +45,7 @@ import { withMatrixClientHOC, MatrixClientProps } from '../../../contexts/Matrix import RoomContext from '../../../contexts/RoomContext'; import { ComposerType } from "../../../dispatcher/payloads/ComposerInsertPayload"; import { getSlashCommand, isSlashCommand, runSlashCommand, shouldSendAnyway } from "../../../editor/commands"; +import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts"; function getHtmlReplyFallback(mxEvent: MatrixEvent): string { const html = mxEvent.getContent().formatted_body; @@ -156,16 +157,16 @@ class EditMessageComposer extends React.Component { private onHeaderKeyDown = (ev: React.KeyboardEvent) => { const action = getKeyBindingsManager().getRoomListAction(ev); switch (action) { - case RoomListAction.CollapseSection: + case KeyBindingAction.CollapseRoomListSection: ev.stopPropagation(); if (this.state.isExpanded) { // Collapse the room sublist if it isn't already this.toggleCollapsed(); } break; - case RoomListAction.ExpandSection: { + case KeyBindingAction.ExpandRoomListSection: { ev.stopPropagation(); if (!this.state.isExpanded) { // Expand the room sublist if it isn't already diff --git a/src/components/views/rooms/SendMessageComposer.tsx b/src/components/views/rooms/SendMessageComposer.tsx index 6cbd86179b..406fed0971 100644 --- a/src/components/views/rooms/SendMessageComposer.tsx +++ b/src/components/views/rooms/SendMessageComposer.tsx @@ -46,7 +46,7 @@ import { containsEmoji } from "../../../effects/utils"; import { CHAT_EFFECTS } from '../../../effects'; import CountlyAnalytics from "../../../CountlyAnalytics"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; -import { getKeyBindingsManager, MessageComposerAction } from '../../../KeyBindingsManager'; +import { getKeyBindingsManager } from '../../../KeyBindingsManager'; import { replaceableComponent } from "../../../utils/replaceableComponent"; import SettingsStore from '../../../settings/SettingsStore'; import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"; @@ -56,6 +56,7 @@ import RoomContext, { TimelineRenderingType } from '../../../contexts/RoomContex import DocumentPosition from "../../../editor/position"; import { ComposerType } from "../../../dispatcher/payloads/ComposerInsertPayload"; import { getSlashCommand, isSlashCommand, runSlashCommand, shouldSendAnyway } from "../../../editor/commands"; +import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts"; interface IAddReplyOpts { permalinkCreator?: RoomPermalinkCreator; @@ -221,21 +222,21 @@ export class SendMessageComposer extends React.Component, "title" | "onClick"> { space?: Room; @@ -234,7 +235,7 @@ export class SpaceItem extends React.PureComponent { const action = getKeyBindingsManager().getRoomListAction(ev); const hasChildren = this.state.childSpaces?.length; switch (action) { - case RoomListAction.CollapseSection: + case KeyBindingAction.CollapseRoomListSection: if (hasChildren && !this.isCollapsed) { this.toggleCollapse(ev); } else { @@ -244,7 +245,7 @@ export class SpaceItem extends React.PureComponent { } break; - case RoomListAction.ExpandSection: + case KeyBindingAction.ExpandRoomListSection: if (hasChildren) { if (this.isCollapsed) { this.toggleCollapse(ev); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index c641173a09..f31199482f 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -3397,7 +3397,6 @@ "Dismiss read marker and jump to bottom": "Dismiss read marker and jump to bottom", "Jump to oldest unread message": "Jump to oldest unread message", "Upload a file": "Upload a file", - "Search (must be enabled)": "Search (must be enabled)", "Scroll up in the timeline": "Scroll up in the timeline", "Scroll down in the timeline": "Scroll down in the timeline", "Jump to room search": "Jump to room search", @@ -3408,8 +3407,6 @@ "Navigate up in the room list": "Navigate up in the room list", "Navigate down in the room list": "Navigate down in the room list", "Toggle the top left menu": "Toggle the top left menu", - "Close dialog or context menu": "Close dialog or context menu", - "Activate selected button": "Activate selected button", "Toggle right panel": "Toggle right panel", "Open this settings tab": "Open this settings tab", "Go to Home View": "Go to Home View", @@ -3425,7 +3422,10 @@ "Jump to first message": "Jump to first message", "Jump to last message": "Jump to last message", "Undo edit": "Undo edit", - "Force complete": "Force complete", "New line": "New line", + "Force complete": "Force complete", + "Search (must be enabled)": "Search (must be enabled)", + "Close dialog or context menu": "Close dialog or context menu", + "Activate selected button": "Activate selected button", "Redo edit": "Redo edit" } diff --git a/test/KeyBindingsManager-test.ts b/test/KeyBindingsManager-test.ts index eab1bea2b0..7cf9f2dd3d 100644 --- a/test/KeyBindingsManager-test.ts +++ b/test/KeyBindingsManager-test.ts @@ -123,7 +123,7 @@ describe('KeyBindingsManager', () => { it('should match ctrlOrMeta key combo', () => { const combo: KeyCombo = { key: 'k', - ctrlOrCmd: true, + ctrlOrCmdKey: true, }; // PC: expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true }), combo, false)).toBe(true); @@ -138,7 +138,7 @@ describe('KeyBindingsManager', () => { it('should match advanced ctrlOrMeta key combo', () => { const combo: KeyCombo = { key: 'k', - ctrlOrCmd: true, + ctrlOrCmdKey: true, altKey: true, }; // PC: