diff --git a/res/css/views/settings/tabs/user/_KeyboardUserSettingsTab.scss b/res/css/views/settings/tabs/user/_KeyboardUserSettingsTab.scss index 6f5a0855ebe..1ace6ec1514 100644 --- a/res/css/views/settings/tabs/user/_KeyboardUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_KeyboardUserSettingsTab.scss @@ -16,30 +16,10 @@ limitations under the License. */ .mx_KeyboardUserSettingsTab .mx_SettingsTab_section { - display: flex; - flex-wrap: wrap; - -webkit-box-orient: vertical; - -webkit-box-direction: normal; - flex-direction: column; - margin-bottom: -50px; - max-height: 1100px; // XXX: this may need adjusting when adding new shortcuts - - .mx_KeyboardShortcutsDialog_category { - width: 33.3333%; // 3 columns - margin: 0 0 40px; - - & > div { - padding-left: 5px; - } - } - - h3 { - margin: 0 0 10px; - } - - h5 { - margin: 15px 0 5px; - font-weight: normal; + .mx_KeyboardShortcut_shortcutRow { + display: flex; + justify-content: space-between; + align-items: center; } kbd { @@ -59,8 +39,4 @@ limitations under the License. margin-left: 5px; } } - - .mx_KeyboardShortcutsDialog_inline div { - display: inline; - } } diff --git a/src/accessibility/KeyboardShortcuts.ts b/src/accessibility/KeyboardShortcuts.ts index 868277ccc7b..4659a4258c2 100644 --- a/src/accessibility/KeyboardShortcuts.ts +++ b/src/accessibility/KeyboardShortcuts.ts @@ -1,5 +1,6 @@ /* Copyright 2020 The Matrix.org Foundation C.I.C. +Copyright 2021 - 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. @@ -16,8 +17,14 @@ limitations under the License. import { _td } from "../languageHandler"; import { isMac, Key } from "../Keyboard"; +import { ISetting } from "../settings/Settings"; -export enum Categories { +export interface ICategory { + categoryLabel: string; + settingNames: string[]; +} + +export enum CategoryName { NAVIGATION = "Navigation", CALLS = "Calls", COMPOSER = "Composer", @@ -26,258 +33,378 @@ export enum Categories { AUTOCOMPLETE = "Autocomplete", } -export enum Modifiers { - ALT = "Alt", // Option on Mac and displayed as an Icon - ALT_GR = "Alt Gr", - SHIFT = "Shift", - SUPER = "Super", // should this be "Windows"? - // Instead of using below, consider CMD_OR_CTRL - COMMAND = "Command", // This gets displayed as an Icon - CONTROL = "Ctrl", -} - -// Meta-modifier: isMac ? CMD : CONTROL -export const CMD_OR_CTRL = isMac ? Modifiers.COMMAND : Modifiers.CONTROL; // Meta-key representing the digits [0-9] often found at the top of standard keyboard layouts export const DIGITS = "digits"; -interface IKeybind { - modifiers?: Modifiers[]; - key: string; // TS: fix this once Key is an enum -} - -export interface IShortcut { - keybinds: IKeybind[]; - description: string; +export const ALTERNATE_KEY_NAME: Record = { + [Key.PAGE_UP]: _td("Page Up"), + [Key.PAGE_DOWN]: _td("Page Down"), + [Key.ESCAPE]: _td("Esc"), + [Key.ENTER]: _td("Enter"), + [Key.SPACE]: _td("Space"), + [Key.HOME]: _td("Home"), + [Key.END]: _td("End"), + [Key.ALT]: _td("Alt"), + [Key.CONTROL]: _td("Ctrl"), + [Key.SHIFT]: _td("Shift"), + [DIGITS]: _td("[number]"), +}; +export const KEY_ICON: Record = { + [Key.ARROW_UP]: "↑", + [Key.ARROW_DOWN]: "↓", + [Key.ARROW_LEFT]: "←", + [Key.ARROW_RIGHT]: "→", +}; +if (isMac) { + KEY_ICON[Key.META] = "⌘"; + KEY_ICON[Key.SHIFT] = "⌥"; } -export const shortcuts: Record = { - [Categories.COMPOSER]: [ - { - keybinds: [{ - modifiers: [CMD_OR_CTRL], - key: Key.B, - }], - description: _td("Toggle Bold"), - }, { - keybinds: [{ - modifiers: [CMD_OR_CTRL], - key: Key.I, - }], - description: _td("Toggle Italics"), - }, { - keybinds: [{ - modifiers: [CMD_OR_CTRL], - key: Key.GREATER_THAN, - }], - description: _td("Toggle Quote"), - }, { - keybinds: [{ - modifiers: [Modifiers.SHIFT], - key: Key.ENTER, - }], - description: _td("New line"), - }, { - keybinds: [{ - key: Key.ARROW_UP, - }, { - key: Key.ARROW_DOWN, - }], - description: _td("Navigate recent messages to edit"), - }, { - keybinds: [{ - modifiers: [CMD_OR_CTRL], - key: Key.HOME, - }, { - modifiers: [CMD_OR_CTRL], - key: Key.END, - }], - description: _td("Jump to start/end of the composer"), - }, { - keybinds: [{ - modifiers: [Modifiers.CONTROL, Modifiers.ALT], - key: Key.ARROW_UP, - }, { - modifiers: [Modifiers.CONTROL, Modifiers.ALT], - key: Key.ARROW_DOWN, - }], - description: _td("Navigate composer history"), - }, { - keybinds: [{ - key: Key.ESCAPE, - }], - description: _td("Cancel replying to a message"), - }, - ], - - [Categories.CALLS]: [ - { - keybinds: [{ - modifiers: [CMD_OR_CTRL], - key: Key.D, - }], - description: _td("Toggle microphone mute"), - }, { - keybinds: [{ - modifiers: [CMD_OR_CTRL], - key: Key.E, - }], - description: _td("Toggle video on/off"), - }, - ], - - [Categories.ROOM]: [ - { - keybinds: [{ - key: Key.PAGE_UP, - }, { - key: Key.PAGE_DOWN, - }], - description: _td("Scroll up/down in the timeline"), - }, { - keybinds: [{ - key: Key.ESCAPE, - }], - description: _td("Dismiss read marker and jump to bottom"), - }, { - keybinds: [{ - modifiers: [Modifiers.SHIFT], - key: Key.PAGE_UP, - }], - description: _td("Jump to oldest unread message"), - }, { - keybinds: [{ - modifiers: [CMD_OR_CTRL, Modifiers.SHIFT], - key: Key.U, - }], - description: _td("Upload a file"), - }, { - keybinds: [{ - modifiers: [CMD_OR_CTRL], - key: Key.F, - }], - description: _td("Search (must be enabled)"), - }, - ], - - [Categories.ROOM_LIST]: [ - { - keybinds: [{ - modifiers: [CMD_OR_CTRL], - key: Key.K, - }], - description: _td("Jump to room search"), - }, { - keybinds: [{ - key: Key.ARROW_UP, - }, { - key: Key.ARROW_DOWN, - }], - description: _td("Navigate up/down in the room list"), - }, { - keybinds: [{ - key: Key.ENTER, - }], - description: _td("Select room from the room list"), - }, { - keybinds: [{ - key: Key.ARROW_LEFT, - }], - description: _td("Collapse room list section"), - }, { - keybinds: [{ - key: Key.ARROW_RIGHT, - }], - description: _td("Expand room list section"), - }, { - keybinds: [{ - key: Key.ESCAPE, - }], - description: _td("Clear room list filter field"), - }, - ], - - [Categories.NAVIGATION]: [ - { - keybinds: [{ - modifiers: [Modifiers.ALT, Modifiers.SHIFT], - key: Key.ARROW_UP, - }, { - modifiers: [Modifiers.ALT, Modifiers.SHIFT], - key: Key.ARROW_DOWN, - }], - description: _td("Previous/next unread room or DM"), - }, { - keybinds: [{ - modifiers: [Modifiers.ALT], - key: Key.ARROW_UP, - }, { - modifiers: [Modifiers.ALT], - key: Key.ARROW_DOWN, - }], - description: _td("Previous/next room or DM"), - }, { - keybinds: [{ - modifiers: [CMD_OR_CTRL], - key: Key.BACKTICK, - }], - description: _td("Toggle the top left menu"), - }, { - keybinds: [{ - key: Key.ESCAPE, - }], - description: _td("Close dialog or context menu"), - }, { - keybinds: [{ - key: Key.ENTER, - }, { - key: Key.SPACE, - }], - description: _td("Activate selected button"), - }, { - keybinds: [{ - modifiers: [CMD_OR_CTRL, Modifiers.SHIFT], - key: Key.D, - }], - description: _td("Toggle space panel"), - }, { - keybinds: [{ - modifiers: [CMD_OR_CTRL], - key: Key.PERIOD, - }], - description: _td("Toggle right panel"), - }, { - keybinds: [{ - modifiers: [CMD_OR_CTRL], - key: Key.SLASH, - }], - description: _td("Open this settings tab"), - }, { - keybinds: [{ - modifiers: [Modifiers.CONTROL, isMac ? Modifiers.SHIFT : Modifiers.ALT], - key: Key.H, - }], - description: _td("Go to Home View"), - }, - ], +export const CATEGORIES: Record = { + [CategoryName.COMPOSER]: { + categoryLabel: _td("Composer"), + settingNames: [ + "KeyBinding.toggleBoldInComposer", + "KeyBinding.toggleItalicsInComposer", + "KeyBinding.toggleQuoteInComposer", + "KeyBinding.newLineInComposer", + "KeyBinding.cancelReplyInComposer", + "KeyBinding.editNextMessage", + "KeyBinding.editPreviousMessage", + "KeyBinding.jumpToStartInComposer", + "KeyBinding.jumpToEndInComposer", + "KeyBinding.nextMessageInComposerHistory", + "KeyBinding.previousMessageInComposerHistory", + ], + }, [CategoryName.CALLS]: { + categoryLabel: _td("Calls"), + settingNames: [ + "KeyBinding.toggleMicInCall", + "KeyBinding.toggleWebcamInCall", + ], + }, [CategoryName.ROOM]: { + categoryLabel: _td("Room"), + settingNames: [ + "KeyBinding.dismissReadMarkerAndJumpToBottom", + "KeyBinding.jumpToOldestUnreadMessage", + "KeyBinding.uploadFileToRoom", + "KeyBinding.searchInRoom", + "KeyBinding.scrollUpInTimeline", + "KeyBinding.scrollDownInTimeline", + ], + }, [CategoryName.ROOM_LIST]: { + categoryLabel: _td("Room List"), + settingNames: [ + "KeyBinding.filterRooms", + "KeyBinding.selectRoomInRoomList", + "KeyBinding.collapseSectionInRoomList", + "KeyBinding.expandSectionInRoomList", + "KeyBinding.clearRoomFilter", + "KeyBinding.upperRoom", + "KeyBinding.downerRoom", + ], + }, [CategoryName.NAVIGATION]: { + categoryLabel: _td("Navigation"), + settingNames: [ + "KeyBinding.toggleTopLeftMenu", + "KeyBinding.closeDialogOrContextMenu", + "KeyBinding.activateSelectedButton", + "KeyBinding.toggleRightPanel", + "KeyBinding.showKeyBindingsSettings", + "KeyBinding.goToHomeView", + "KeyBinding.nextUnreadRoom", + "KeyBinding.previousUnreadRoom", + "KeyBinding.nextRoom", + "KeyBinding.previousRoom", + "KeyBinding.toggleSpacePanel", + ], + }, [CategoryName.AUTOCOMPLETE]: { + categoryLabel: _td("Autocomplete"), + settingNames: [ + "KeyBinding.cancelAutoComplete", + "KeyBinding.nextOptionInAutoComplete", + "KeyBinding.previousOptionInAutoComplete", + ], + }, +}; - [Categories.AUTOCOMPLETE]: [ - { - keybinds: [{ - key: Key.ARROW_UP, - }, { - key: Key.ARROW_DOWN, - }], - description: _td("Move autocomplete selection up/down"), - }, { - keybinds: [{ - key: Key.ESCAPE, - }], - description: _td("Cancel autocomplete"), - }, - ], +// This is very intentionally modelled after SETTINGS as it will make it easier +// to implement customizable keyboard shortcuts +// TODO: TravisR will fix this nightmare when the new version of the SettingsStore becomes a thing +export const KEYBOARD_SHORTCUTS: { [setting: string]: ISetting } = { + "KeyBinding.toggleBoldInComposer": { + default: { + ctrlOrCmdKey: true, + key: Key.B, + }, + displayName: _td("Toggle Bold"), + }, + "KeyBinding.toggleItalicsInComposer": { + default: { + ctrlOrCmdKey: true, + key: Key.I, + }, + displayName: _td("Toggle Italics"), + }, + "KeyBinding.toggleQuoteInComposer": { + default: { + ctrlOrCmdKey: true, + key: Key.GREATER_THAN, + }, + displayName: _td("Toggle Quote"), + }, + "KeyBinding.newLineInComposer": { + default: { + shiftKey: true, + key: Key.ENTER, + }, + displayName: _td("New line"), + }, + "KeyBinding.cancelReplyInComposer": { + default: { + key: Key.ESCAPE, + }, + displayName: _td("Cancel replying to a message"), + }, + "KeyBinding.editNextMessage": { + default: { + key: Key.ARROW_UP, + }, + displayName: _td("Navigate to next message to edit"), + }, + "KeyBinding.editPreviousMessage": { + default: { + key: Key.ARROW_DOWN, + }, + displayName: _td("Navigate to previous message to edit"), + }, + "KeyBinding.jumpToStartInComposer": { + default: { + ctrlOrCmdKey: true, + key: Key.HOME, + }, + displayName: _td("Jump to start of the composer"), + }, + "KeyBinding.jumpToEndInComposer": { + default: { + ctrlOrCmdKey: true, + key: Key.END, + }, + displayName: _td("Jump to end of the composer"), + }, + "KeyBinding.nextMessageInComposerHistory": { + default: { + altKey: true, + ctrlKey: true, + key: Key.ARROW_UP, + }, + displayName: _td("Navigate to next message in composer history"), + }, + "KeyBinding.previousMessageInComposerHistory": { + default: { + altKey: true, + ctrlKey: true, + key: Key.ARROW_DOWN, + }, + displayName: _td("Navigate to previous message in composer history"), + }, + "KeyBinding.toggleMicInCall": { + default: { + ctrlOrCmdKey: true, + key: Key.D, + }, + displayName: _td("Toggle microphone mute"), + }, + "KeyBinding.toggleWebcamInCall": { + default: { + ctrlOrCmdKey: true, + key: Key.E, + }, + displayName: _td("Toggle webcam on/off"), + }, + "KeyBinding.dismissReadMarkerAndJumpToBottom": { + default: { + key: Key.ESCAPE, + }, + displayName: _td("Dismiss read marker and jump to bottom"), + }, + "KeyBinding.jumpToOldestUnreadMessage": { + default: { + shiftKey: true, + key: Key.PAGE_UP, + }, + displayName: _td("Jump to oldest unread message"), + }, + "KeyBinding.uploadFileToRoom": { + default: { + ctrlOrCmdKey: true, + shiftKey: true, + key: Key.U, + }, + displayName: _td("Upload a file"), + }, + "KeyBinding.searchInRoom": { + default: { + ctrlOrCmdKey: true, + key: Key.F, + }, + displayName: _td("Search (must be enabled)"), + }, + "KeyBinding.scrollUpInTimeline": { + default: { + key: Key.PAGE_UP, + }, + displayName: _td("Scroll up in the timeline"), + }, + "KeyBinding.scrollDownInTimeline": { + default: { + key: Key.PAGE_DOWN, + }, + displayName: _td("Scroll down in the timeline"), + }, + "KeyBinding.filterRooms": { + default: { + ctrlOrCmdKey: true, + key: Key.K, + }, + displayName: _td("Jump to room search"), + }, + "KeyBinding.selectRoomInRoomList": { + default: { + key: Key.ENTER, + }, + displayName: _td("Select room from the room list"), + }, + "KeyBinding.collapseSectionInRoomList": { + default: { + key: Key.ARROW_LEFT, + }, + displayName: _td("Collapse room list section"), + }, + "KeyBinding.expandSectionInRoomList": { + default: { + key: Key.ARROW_RIGHT, + }, + displayName: _td("Expand room list section"), + }, + "KeyBinding.clearRoomFilter": { + default: { + key: Key.ESCAPE, + }, + displayName: _td("Clear room list filter field"), + }, + "KeyBinding.upperRoom": { + default: { + key: Key.ARROW_UP, + }, + displayName: _td("Navigate up in the room list"), + }, + "KeyBinding.downerRoom": { + default: { + key: Key.ARROW_DOWN, + }, + displayName: _td("Navigate down in the room list"), + }, + "KeyBinding.toggleTopLeftMenu": { + 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"), + }, + "KeyBinding.toggleRightPanel": { + default: { + ctrlOrCmdKey: true, + key: Key.PERIOD, + }, + displayName: _td("Toggle right panel"), + }, + "KeyBinding.showKeyBindingsSettings": { + default: { + ctrlOrCmdKey: true, + key: Key.SLASH, + }, + displayName: _td("Open this settings tab"), + }, + "KeyBinding.goToHomeView": { + default: { + ctrlOrCmdKey: true, + altKey: true, + key: Key.H, + }, + displayName: _td("Go to Home View"), + }, + "KeyBinding.nextUnreadRoom": { + default: { + shiftKey: true, + altKey: true, + key: Key.ARROW_UP, + }, + displayName: _td("Next unread room or DM"), + }, + "KeyBinding.previousUnreadRoom": { + default: { + shiftKey: true, + altKey: true, + key: Key.ARROW_DOWN, + }, + displayName: _td("Previous unread room or DM"), + }, + "KeyBinding.nextRoom": { + default: { + altKey: true, + key: Key.ARROW_UP, + }, + displayName: _td("Next room or DM"), + }, + "KeyBinding.previousRoom": { + default: { + altKey: true, + key: Key.ARROW_DOWN, + }, + displayName: _td("Previous room or DM"), + }, + "KeyBinding.cancelAutoComplete": { + default: { + key: Key.ESCAPE, + }, + displayName: _td("Cancel autocomplete"), + }, + "KeyBinding.nextOptionInAutoComplete": { + default: { + key: Key.ARROW_UP, + }, + displayName: _td("Next autocomplete suggestion"), + }, + "KeyBinding.previousOptionInAutoComplete": { + default: { + key: Key.ARROW_DOWN, + }, + displayName: _td("Previous autocomplete suggestion"), + }, + "KeyBinding.toggleSpacePanel": { + default: { + ctrlOrCmdKey: true, + shiftKey: true, + key: Key.D, + }, + displayName: _td("Toggle space panel"), + }, }; -export const registerShortcut = (category: Categories, defn: IShortcut) => { - shortcuts[category].push(defn); +export const registerShortcut = (shortcutName: string, categoryName: CategoryName, shortcut: ISetting): void => { + KEYBOARD_SHORTCUTS[shortcutName] = shortcut; + CATEGORIES[categoryName].settingNames.push(shortcutName); }; diff --git a/src/components/views/settings/tabs/user/KeyboardUserSettingsTab.tsx b/src/components/views/settings/tabs/user/KeyboardUserSettingsTab.tsx index 11ef078424b..8759a04a5cb 100644 --- a/src/components/views/settings/tabs/user/KeyboardUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/KeyboardUserSettingsTab.tsx @@ -1,6 +1,6 @@ /* Copyright 2020 The Matrix.org Foundation C.I.C. -Copyright 2021 Šimon Brandner +Copyright 2021 - 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. @@ -15,102 +15,94 @@ See the License for the specific language governing permissions and limitations under the License. */ -import classNames from "classnames"; import React from "react"; -import { Categories, DIGITS, IShortcut, Modifiers, shortcuts } from "../../../../../accessibility/KeyboardShortcuts"; +import { + KEYBOARD_SHORTCUTS, + ALTERNATE_KEY_NAME, + KEY_ICON, + ICategory, + CATEGORIES, + CategoryName, +} from "../../../../../accessibility/KeyboardShortcuts"; import { isMac, Key } from "../../../../../Keyboard"; -import { _t, _td } from "../../../../../languageHandler"; - -// TS: once languageHandler is TS we can probably inline this into the enum -_td("Alt"); -_td("Alt Gr"); -_td("Shift"); -_td("Super"); -_td("Ctrl"); -_td("Navigation"); -_td("Calls"); -_td("Composer"); -_td("Room List"); -_td("Autocomplete"); - -const categoryOrder = [ - Categories.COMPOSER, - Categories.AUTOCOMPLETE, - Categories.ROOM, - Categories.ROOM_LIST, - Categories.NAVIGATION, - Categories.CALLS, -]; - -const modifierIcon: Record = { - [Modifiers.COMMAND]: "⌘", +import { _t } from "../../../../../languageHandler"; + +interface IKeyboardKeyProps { + name: string; + last?: boolean; +} + +export const KeyboardKey: React.FC = ({ name, last }) => { + const icon = KEY_ICON[name]; + const alternateName = ALTERNATE_KEY_NAME[name]; + + return + { icon || (alternateName && _t(alternateName)) || name } + { !last && "+" } + ; }; -if (isMac) { - modifierIcon[Modifiers.ALT] = "⌥"; +interface IKeyboardShortcutProps { + name: string; } -const alternateKeyName: Record = { - [Key.PAGE_UP]: _td("Page Up"), - [Key.PAGE_DOWN]: _td("Page Down"), - [Key.ESCAPE]: _td("Esc"), - [Key.ENTER]: _td("Enter"), - [Key.SPACE]: _td("Space"), - [Key.HOME]: _td("Home"), - [Key.END]: _td("End"), - [DIGITS]: _td("[number]"), +export const KeyboardShortcut: React.FC = ({ name }) => { + const value = KEYBOARD_SHORTCUTS[name]?.default; + if (!value) return null; + + const modifiersElement = []; + if (value.ctrlOrCmdKey) { + modifiersElement.push(); + } else if (value.ctrlKey) { + modifiersElement.push(); + } else if (value.metaKey) { + modifiersElement.push(); + } + if (value.altKey) { + modifiersElement.push(); + } + if (value.shiftKey) { + modifiersElement.push(); + } + + return
+ { modifiersElement } + +
; }; -const keyIcon: Record = { - [Key.ARROW_UP]: "↑", - [Key.ARROW_DOWN]: "↓", - [Key.ARROW_LEFT]: "←", - [Key.ARROW_RIGHT]: "→", + +interface IKeyboardShortcutRowProps { + name: string; +} + +const KeyboardShortcutRow: React.FC = ({ name }) => { + return
+ { KEYBOARD_SHORTCUTS[name].displayName } + +
; }; -interface IShortcutProps { - shortcut: IShortcut; +interface IKeyboardShortcutSectionProps { + categoryName: CategoryName; + category: ICategory; } -const Shortcut: React.FC = ({ shortcut }) => { - const classes = classNames({ - "mx_KeyboardShortcutsDialog_inline": shortcut.keybinds.every(k => !k.modifiers || k.modifiers.length === 0), - }); - - return
-
{ _t(shortcut.description) }
- { shortcut.keybinds.map(s => { - let text = s.key; - if (alternateKeyName[s.key]) { - text = _t(alternateKeyName[s.key]); - } else if (keyIcon[s.key]) { - text = keyIcon[s.key]; - } - - return
- { s.modifiers && s.modifiers.map(m => { - return - { modifierIcon[m] || _t(m) }+ - ; - }) } - { text } -
; - }) } +const KeyboardShortcutSection: React.FC = ({ categoryName, category }) => { + return
+
{ _t(category.categoryLabel) }
+
{ category.settingNames.map((shortcutName) => { + return ; + }) }
; }; const KeyboardUserSettingsTab: React.FC = () => { return
{ _t("Keyboard") }
-
- { categoryOrder.map(category => { - const list = shortcuts[category]; - return
-

{ _t(category) }

-
{ list.map(shortcut => ) }
-
; - }) } -
+ { Object.entries(CATEGORIES).map(([categoryName, category]: [CategoryName, ICategory]) => { + return ; + }) }
; }; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 86f2e90efb2..33b60ed1914 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1432,23 +1432,6 @@ "Access Token": "Access Token", "Your access token gives full access to your account. Do not share it with anyone.": "Your access token gives full access to your account. Do not share it with anyone.", "Clear cache and reload": "Clear cache and reload", - "Alt": "Alt", - "Alt Gr": "Alt Gr", - "Shift": "Shift", - "Super": "Super", - "Ctrl": "Ctrl", - "Navigation": "Navigation", - "Calls": "Calls", - "Composer": "Composer", - "Room List": "Room List", - "Autocomplete": "Autocomplete", - "Page Up": "Page Up", - "Page Down": "Page Down", - "Esc": "Esc", - "Enter": "Enter", - "Space": "Space", - "End": "End", - "[number]": "[number]", "Keyboard": "Keyboard", "Labs": "Labs", "Feeling experimental? Labs are the best way to get things early, test out new features and help shape them before they actually launch. Learn more.": "Feeling experimental? Labs are the best way to get things early, test out new features and help shape them before they actually launch. Learn more.", @@ -1500,6 +1483,7 @@ "Keyboard shortcuts": "Keyboard shortcuts", "To view all keyboard shortcuts, click here.": "To view all keyboard shortcuts, click here.", "Displaying time": "Displaying time", + "Composer": "Composer", "Code blocks": "Code blocks", "Images, GIFs and videos": "Images, GIFs and videos", "Timeline": "Timeline", @@ -2892,6 +2876,7 @@ "Mentions only": "Mentions only", "See room timeline (devtools)": "See room timeline (devtools)", "Room": "Room", + "Space": "Space", "Space home": "Space home", "Manage & explore rooms": "Manage & explore rooms", "Move up": "Move up", @@ -3377,36 +3362,57 @@ "Failed to set direct chat tag": "Failed to set direct chat tag", "Failed to remove tag %(tagName)s from room": "Failed to remove tag %(tagName)s from room", "Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room", + "Page Up": "Page Up", + "Page Down": "Page Down", + "Esc": "Esc", + "Enter": "Enter", + "End": "End", + "Alt": "Alt", + "Ctrl": "Ctrl", + "Shift": "Shift", + "[number]": "[number]", + "Calls": "Calls", + "Room List": "Room List", + "Navigation": "Navigation", + "Autocomplete": "Autocomplete", "Toggle Bold": "Toggle Bold", "Toggle Italics": "Toggle Italics", "Toggle Quote": "Toggle Quote", "New line": "New line", - "Navigate recent messages to edit": "Navigate recent messages to edit", - "Jump to start/end of the composer": "Jump to start/end of the composer", - "Navigate composer history": "Navigate composer history", "Cancel replying to a message": "Cancel replying to a message", + "Navigate to next message to edit": "Navigate to next message to edit", + "Navigate to previous message to edit": "Navigate to previous message to edit", + "Jump to start of the composer": "Jump to start of the composer", + "Jump to end of the composer": "Jump to end of the composer", + "Navigate to next message in composer history": "Navigate to next message in composer history", + "Navigate to previous message in composer history": "Navigate to previous message in composer history", "Toggle microphone mute": "Toggle microphone mute", - "Toggle video on/off": "Toggle video on/off", - "Scroll up/down in the timeline": "Scroll up/down in the timeline", + "Toggle webcam on/off": "Toggle webcam on/off", "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", - "Navigate up/down in the room list": "Navigate up/down in the room list", "Select room from the room list": "Select room from the room list", "Collapse room list section": "Collapse room list section", "Expand room list section": "Expand room list section", "Clear room list filter field": "Clear room list filter field", - "Previous/next unread room or DM": "Previous/next unread room or DM", - "Previous/next room or DM": "Previous/next room or DM", + "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 space panel": "Toggle space panel", "Toggle right panel": "Toggle right panel", "Open this settings tab": "Open this settings tab", "Go to Home View": "Go to Home View", - "Move autocomplete selection up/down": "Move autocomplete selection up/down", - "Cancel autocomplete": "Cancel autocomplete" + "Next unread room or DM": "Next unread room or DM", + "Previous unread room or DM": "Previous unread room or DM", + "Next room or DM": "Next room or DM", + "Previous room or DM": "Previous room or DM", + "Cancel autocomplete": "Cancel autocomplete", + "Next autocomplete suggestion": "Next autocomplete suggestion", + "Previous autocomplete suggestion": "Previous autocomplete suggestion", + "Toggle space panel": "Toggle space panel" } diff --git a/test/accessibility/KeyboardShortcuts-test.ts b/test/accessibility/KeyboardShortcuts-test.ts new file mode 100644 index 00000000000..5fb559e1ab8 --- /dev/null +++ b/test/accessibility/KeyboardShortcuts-test.ts @@ -0,0 +1,45 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { + CATEGORIES, + CategoryName, + KEYBOARD_SHORTCUTS, + registerShortcut, +} from "../../src/accessibility/KeyboardShortcuts"; +import { Key } from "../../src/Keyboard"; +import { ISetting } from "../../src/settings/Settings"; + +describe("KeyboardShortcuts", () => { + describe("registerShortcut()", () => { + it("correctly registers shortcut", () => { + const shortcutName = "Keybinding.definitelyARealShortcut"; + const shortcutCategory = CategoryName.NAVIGATION; + const shortcut: ISetting = { + displayName: "A real shortcut", + default: { + ctrlKey: true, + key: Key.A, + }, + }; + + registerShortcut(shortcutName, shortcutCategory, shortcut); + + expect(KEYBOARD_SHORTCUTS[shortcutName]).toBe(shortcut); + expect(CATEGORIES[shortcutCategory].settingNames.includes(shortcutName)).toBeTruthy(); + }); + }); +}); diff --git a/test/components/views/settings/tabs/user/KeyboardUserSettingsTab-test.tsx b/test/components/views/settings/tabs/user/KeyboardUserSettingsTab-test.tsx new file mode 100644 index 00000000000..2297ada27a3 --- /dev/null +++ b/test/components/views/settings/tabs/user/KeyboardUserSettingsTab-test.tsx @@ -0,0 +1,134 @@ + +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from "react"; +import { mount, ReactWrapper } from "enzyme"; + +import { Key } from "../../../../../../src/Keyboard"; + +const PATH_TO_KEYBOARD_SHORTCUTS = "../../../../../../src/accessibility/KeyboardShortcuts"; +const PATH_TO_COMPONENT = "../../../../../../src/components/views/settings/tabs/user/KeyboardUserSettingsTab"; + +const mockKeyboardShortcuts = (override) => { + jest.doMock(PATH_TO_KEYBOARD_SHORTCUTS, () => { + const original = jest.requireActual(PATH_TO_KEYBOARD_SHORTCUTS); + return { + ...original, + ...override, + }; + }); +}; + +const renderKeyboardUserSettingsTab = async (component, props?): Promise => { + const Component = (await import(PATH_TO_COMPONENT))[component]; + return mount(); +}; + +describe("KeyboardUserSettingsTab", () => { + beforeEach(() => { + jest.resetModules(); + }); + + it("renders key icon", async () => { + const body = await renderKeyboardUserSettingsTab("KeyboardKey", { name: Key.ARROW_DOWN }); + expect(body).toMatchSnapshot(); + }); + + it("renders alternative key name", async () => { + const body = await renderKeyboardUserSettingsTab("KeyboardKey", { name: Key.PAGE_DOWN }); + expect(body).toMatchSnapshot(); + }); + + it("doesn't render + if last", async () => { + const body = await renderKeyboardUserSettingsTab("KeyboardKey", { name: Key.A, last: true }); + expect(body).toMatchSnapshot(); + }); + + it("doesn't render same modifier twice", async () => { + mockKeyboardShortcuts({ + "KEYBOARD_SHORTCUTS": { + "keybind1": { + default: { + key: Key.A, + ctrlOrCmdKey: true, + metaKey: true, + }, + displayName: "Cancel replying to a message", + }, + }, + }); + const body1 = await renderKeyboardUserSettingsTab("KeyboardShortcut", { name: "keybind1" }); + expect(body1).toMatchSnapshot(); + jest.resetModules(); + + mockKeyboardShortcuts({ + "KEYBOARD_SHORTCUTS": { + "keybind1": { + default: { + key: Key.A, + ctrlOrCmdKey: true, + ctrlKey: true, + }, + displayName: "Cancel replying to a message", + }, + }, + }); + const body2 = await renderKeyboardUserSettingsTab("KeyboardShortcut", { name: "keybind1" }); + expect(body2).toMatchSnapshot(); + jest.resetModules(); + }); + + it("renders list of keyboard shortcuts", async () => { + mockKeyboardShortcuts({ + "KEYBOARD_SHORTCUTS": { + "keybind1": { + default: { + key: Key.A, + ctrlKey: true, + }, + displayName: "Cancel replying to a message", + }, + "keybind2": { + default: { + key: Key.B, + ctrlKey: true, + }, + displayName: "Toggle Bold", + }, + "keybind3": { + default: { + key: Key.ENTER, + }, + displayName: "Select room from the room list", + }, + }, + "CATEGORIES": { + "Composer": { + settingNames: ["keybind1", "keybind2"], + categoryLabel: "Composer", + }, + "Navigation": { + settingNames: ["keybind3"], + categoryLabel: "Navigation", + }, + }, + }); + + const body = await renderKeyboardUserSettingsTab("default"); + expect(body).toMatchSnapshot(); + }); +}); diff --git a/test/components/views/settings/tabs/user/__snapshots__/KeyboardUserSettingsTab-test.tsx.snap b/test/components/views/settings/tabs/user/__snapshots__/KeyboardUserSettingsTab-test.tsx.snap new file mode 100644 index 00000000000..ab0a3b8eb38 --- /dev/null +++ b/test/components/views/settings/tabs/user/__snapshots__/KeyboardUserSettingsTab-test.tsx.snap @@ -0,0 +1,269 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`KeyboardUserSettingsTab doesn't render + if last 1`] = ` + + + + a + + + +`; + +exports[`KeyboardUserSettingsTab doesn't render same modifier twice 1`] = ` + +
+ + + + missing translation: en|Ctrl + + + + + + + + + a + + + +
+
+`; + +exports[`KeyboardUserSettingsTab doesn't render same modifier twice 2`] = ` + +
+ + + + missing translation: en|Ctrl + + + + + + + + + a + + + +
+
+`; + +exports[`KeyboardUserSettingsTab renders alternative key name 1`] = ` + + + + missing translation: en|Page Down + + + + + +`; + +exports[`KeyboardUserSettingsTab renders key icon 1`] = ` + + + + ↓ + + + + + +`; + +exports[`KeyboardUserSettingsTab renders list of keyboard shortcuts 1`] = ` + +
+
+ missing translation: en|Keyboard +
+ +
+
+ missing translation: en|Composer +
+
+ + +
+ Cancel replying to a message + +
+ + + + missing translation: en|Ctrl + + + + + + + + + a + + + +
+
+
+
+ +
+ Toggle Bold + +
+ + + + missing translation: en|Ctrl + + + + + + + + + b + + + +
+
+
+
+ +
+
+
+ +
+
+ missing translation: en|Navigation +
+
+ + +
+ Select room from the room list + +
+ + + + missing translation: en|Enter + + + +
+
+
+
+ +
+
+
+
+
+`;