Work towards unifying KeyboardShortcuts and KeyBindingsDefaults #2 (#7674)

This commit is contained in:
Šimon Brandner 2022-01-31 16:55:45 +01:00 committed by GitHub
parent 7e5de9294c
commit a17d585a12
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 450 additions and 732 deletions

View file

@ -1,5 +1,6 @@
/* /*
Copyright 2021 Clemens Zeidler Copyright 2021 Clemens Zeidler
Copyright 2022 Šimon Brandner <simon.bra.ag@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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. limitations under the License.
*/ */
import {
AutocompleteAction,
IKeyBindingsProvider,
KeyBinding,
MessageComposerAction,
NavigationAction,
RoomAction,
RoomListAction,
LabsAction,
} from "./KeyBindingsManager";
import { isMac, Key } from "./Keyboard"; import { isMac, Key } from "./Keyboard";
import SettingsStore from "./settings/SettingsStore"; import SettingsStore from "./settings/SettingsStore";
import SdkConfig from "./SdkConfig"; 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<MessageComposerAction>[] => {
const bindings: KeyBinding<MessageComposerAction>[] = [
{
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')) { if (SettingsStore.getValue('MessageComposerInput.ctrlEnterToSend')) {
bindings.push({ bindings.push({
action: MessageComposerAction.Send, action: KeyBindingAction.SendMessage,
keyCombo: { keyCombo: {
key: Key.ENTER, key: Key.ENTER,
ctrlOrCmd: true, ctrlOrCmdKey: true,
}, },
}); });
bindings.push({ bindings.push({
action: MessageComposerAction.NewLine, action: KeyBindingAction.NewLine,
keyCombo: { keyCombo: {
key: Key.ENTER, key: Key.ENTER,
}, },
}); });
bindings.push({ bindings.push({
action: MessageComposerAction.NewLine, action: KeyBindingAction.NewLine,
keyCombo: { keyCombo: {
key: Key.ENTER, key: Key.ENTER,
shiftKey: true, shiftKey: true,
@ -149,13 +71,13 @@ const messageComposerBindings = (): KeyBinding<MessageComposerAction>[] => {
}); });
} else { } else {
bindings.push({ bindings.push({
action: MessageComposerAction.Send, action: KeyBindingAction.SendMessage,
keyCombo: { keyCombo: {
key: Key.ENTER, key: Key.ENTER,
}, },
}); });
bindings.push({ bindings.push({
action: MessageComposerAction.NewLine, action: KeyBindingAction.NewLine,
keyCombo: { keyCombo: {
key: Key.ENTER, key: Key.ENTER,
shiftKey: true, shiftKey: true,
@ -163,7 +85,7 @@ const messageComposerBindings = (): KeyBinding<MessageComposerAction>[] => {
}); });
if (isMac) { if (isMac) {
bindings.push({ bindings.push({
action: MessageComposerAction.NewLine, action: KeyBindingAction.NewLine,
keyCombo: { keyCombo: {
key: Key.ENTER, key: Key.ENTER,
altKey: true, altKey: true,
@ -171,156 +93,56 @@ const messageComposerBindings = (): KeyBinding<MessageComposerAction>[] => {
}); });
} }
} }
return bindings; return bindings;
}; };
const autocompleteBindings = (): KeyBinding<AutocompleteAction>[] => { const autocompleteBindings = (): KeyBinding[] => {
return [ const bindings = getBindingsByCategory(CategoryName.AUTOCOMPLETE);
{
action: AutocompleteAction.ForceComplete, bindings.push({
keyCombo: { action: KeyBindingAction.ForceCompleteAutocomplete,
key: Key.TAB, keyCombo: {
}, key: Key.TAB,
}, },
{ });
action: AutocompleteAction.ForceComplete, bindings.push({
keyCombo: { action: KeyBindingAction.ForceCompleteAutocomplete,
key: Key.TAB, keyCombo: {
ctrlKey: true, key: Key.TAB,
}, ctrlKey: true,
}, },
{ });
action: AutocompleteAction.Complete, bindings.push({
keyCombo: { action: KeyBindingAction.CompleteAutocomplete,
key: Key.ENTER, keyCombo: {
}, key: Key.ENTER,
}, },
{ });
action: AutocompleteAction.Complete, bindings.push({
keyCombo: { action: KeyBindingAction.CompleteAutocomplete,
key: Key.ENTER, keyCombo: {
ctrlKey: true, key: Key.ENTER,
}, ctrlKey: true,
}, },
{ });
action: AutocompleteAction.Cancel,
keyCombo: { return bindings;
key: Key.ESCAPE,
},
},
{
action: AutocompleteAction.PrevSelection,
keyCombo: {
key: Key.ARROW_UP,
},
},
{
action: AutocompleteAction.NextSelection,
keyCombo: {
key: Key.ARROW_DOWN,
},
},
];
}; };
const roomListBindings = (): KeyBinding<RoomListAction>[] => { const roomListBindings = (): KeyBinding[] => {
return [ return getBindingsByCategory(CategoryName.ROOM_LIST);
{
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 roomBindings = (): KeyBinding<RoomAction>[] => { const roomBindings = (): KeyBinding[] => {
const bindings: KeyBinding<RoomAction>[] = [ const bindings = getBindingsByCategory(CategoryName.ROOM);
{
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,
},
},
];
if (SettingsStore.getValue('ctrlFForSearch')) { if (SettingsStore.getValue('ctrlFForSearch')) {
bindings.push({ bindings.push({
action: RoomAction.FocusSearch, action: KeyBindingAction.SearchInRoom,
keyCombo: { keyCombo: {
key: Key.F, key: Key.F,
ctrlOrCmd: true, ctrlOrCmdKey: true,
}, },
}); });
} }
@ -328,113 +150,14 @@ const roomBindings = (): KeyBinding<RoomAction>[] => {
return bindings; return bindings;
}; };
const navigationBindings = (): KeyBinding<NavigationAction>[] => { const navigationBindings = (): KeyBinding[] => {
return [ return getBindingsByCategory(CategoryName.NAVIGATION);
{
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 labsBindings = (): KeyBinding<LabsAction>[] => { const labsBindings = (): KeyBinding[] => {
if (!SdkConfig.get()['showLabsSettings']) { if (!SdkConfig.get()['showLabsSettings']) return [];
return [];
}
return [ return getBindingsByCategory(CategoryName.LABS);
{
action: LabsAction.ToggleHiddenEventVisibility,
keyCombo: {
key: Key.H,
ctrlOrCmd: true,
shiftKey: true,
},
},
];
}; };
export const defaultBindingsProvider: IKeyBindingsProvider = { export const defaultBindingsProvider: IKeyBindingsProvider = {

View file

@ -1,5 +1,6 @@
/* /*
Copyright 2021 Clemens Zeidler Copyright 2021 Clemens Zeidler
Copyright 2022 Šimon Brandner <simon.bra.ag@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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. limitations under the License.
*/ */
import { KeyBindingAction } from "./accessibility/KeyboardShortcuts";
import { defaultBindingsProvider } from './KeyBindingsDefaults'; import { defaultBindingsProvider } from './KeyBindingsDefaults';
import { isMac } from './Keyboard'; 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. * Represent a key combination.
* *
@ -144,7 +28,7 @@ export type KeyCombo = {
key?: string; key?: string;
/** On PC: ctrl is pressed; on Mac: meta is pressed */ /** On PC: ctrl is pressed; on Mac: meta is pressed */
ctrlOrCmd?: boolean; ctrlOrCmdKey?: boolean;
altKey?: boolean; altKey?: boolean;
ctrlKey?: boolean; ctrlKey?: boolean;
@ -152,8 +36,8 @@ export type KeyCombo = {
shiftKey?: boolean; shiftKey?: boolean;
}; };
export type KeyBinding<T extends string> = { export type KeyBinding = {
action: T; action: KeyBindingAction;
keyCombo: KeyCombo; keyCombo: KeyCombo;
}; };
@ -186,7 +70,7 @@ export function isKeyComboMatch(ev: KeyboardEvent | React.KeyboardEvent, combo:
const evShift = ev.shiftKey ?? false; const evShift = ev.shiftKey ?? false;
const evMeta = ev.metaKey ?? false; const evMeta = ev.metaKey ?? false;
// When ctrlOrCmd is set, the keys need do evaluated differently on PC and Mac // When ctrlOrCmd is set, the keys need do evaluated differently on PC and Mac
if (combo.ctrlOrCmd) { if (combo.ctrlOrCmdKey) {
if (onMac) { if (onMac) {
if (!evMeta if (!evMeta
|| evCtrl !== comboCtrl || evCtrl !== comboCtrl
@ -215,15 +99,10 @@ export function isKeyComboMatch(ev: KeyboardEvent | React.KeyboardEvent, combo:
return true; return true;
} }
export type KeyBindingGetter<T extends string> = () => KeyBinding<T>[]; export type KeyBindingGetter = () => KeyBinding[];
export interface IKeyBindingsProvider { export interface IKeyBindingsProvider {
getMessageComposerBindings: KeyBindingGetter<MessageComposerAction>; [key: string]: KeyBindingGetter;
getAutocompleteBindings: KeyBindingGetter<AutocompleteAction>;
getRoomListBindings: KeyBindingGetter<RoomListAction>;
getRoomBindings: KeyBindingGetter<RoomAction>;
getNavigationBindings: KeyBindingGetter<NavigationAction>;
getLabsBindings: KeyBindingGetter<LabsAction>;
} }
export class KeyBindingsManager { export class KeyBindingsManager {
@ -242,10 +121,10 @@ export class KeyBindingsManager {
/** /**
* Finds a matching KeyAction for a given KeyboardEvent * Finds a matching KeyAction for a given KeyboardEvent
*/ */
private getAction<T extends string>( private getAction(
getters: KeyBindingGetter<T>[], getters: KeyBindingGetter[],
ev: KeyboardEvent | React.KeyboardEvent, ev: KeyboardEvent | React.KeyboardEvent,
): T | undefined { ): KeyBindingAction | undefined {
for (const getter of getters) { for (const getter of getters) {
const bindings = getter(); const bindings = getter();
const binding = bindings.find(it => isKeyComboMatch(ev, it.keyCombo, isMac)); const binding = bindings.find(it => isKeyComboMatch(ev, it.keyCombo, isMac));
@ -256,27 +135,27 @@ export class KeyBindingsManager {
return undefined; 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); 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); 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); 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); 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); 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); return this.getAction(this.bindingsProviders.map(it => it.getLabsBindings), ev);
} }
} }

View file

@ -19,15 +19,104 @@ import { _td } from "../languageHandler";
import { isMac, Key } from "../Keyboard"; import { isMac, Key } from "../Keyboard";
import { ISetting } from "../settings/Settings"; import { ISetting } from "../settings/Settings";
import SettingsStore from "../settings/SettingsStore"; import SettingsStore from "../settings/SettingsStore";
import {
AutocompleteAction, export enum KeyBindingAction {
KeyBindingAction, /** Send a message */
LabsAction, SendMessage = 'KeyBinding.sendMessageInComposer',
MessageComposerAction, /** Go backwards through the send history and use the message in composer view */
NavigationAction, SelectPrevSendHistory = 'KeyBinding.previousMessageInComposerHistory',
RoomAction, /** Go forwards through the send history */
RoomListAction, SelectNextSendHistory = 'KeyBinding.nextMessageInComposerHistory',
} from "../KeyBindingsManager"; /** 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 = { type IKeyboardShortcuts = {
// TODO: We should figure out what to do with the keyboard shortcuts that are not handled by KeybindingManager // 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, ICategory> = {
[CategoryName.COMPOSER]: { [CategoryName.COMPOSER]: {
categoryLabel: _td("Composer"), categoryLabel: _td("Composer"),
settingNames: [ settingNames: [
MessageComposerAction.Send, KeyBindingAction.SendMessage,
MessageComposerAction.FormatBold, KeyBindingAction.NewLine,
MessageComposerAction.FormatItalics, KeyBindingAction.FormatBold,
MessageComposerAction.FormatQuote, KeyBindingAction.FormatItalics,
MessageComposerAction.NewLine, KeyBindingAction.FormatQuote,
MessageComposerAction.CancelEditing, KeyBindingAction.EditUndo,
MessageComposerAction.EditNextMessage, KeyBindingAction.EditRedo,
MessageComposerAction.EditPrevMessage, KeyBindingAction.MoveCursorToStart,
MessageComposerAction.MoveCursorToStart, KeyBindingAction.MoveCursorToEnd,
MessageComposerAction.MoveCursorToEnd, KeyBindingAction.CancelReplyOrEdit,
MessageComposerAction.SelectNextSendHistory, KeyBindingAction.EditNextMessage,
MessageComposerAction.EditPrevMessage, KeyBindingAction.EditPrevMessage,
MessageComposerAction.EditUndo, KeyBindingAction.SelectNextSendHistory,
MessageComposerAction.EditRedo, KeyBindingAction.SelectPrevSendHistory,
], ],
}, [CategoryName.CALLS]: { }, [CategoryName.CALLS]: {
categoryLabel: _td("Calls"), categoryLabel: _td("Calls"),
@ -105,54 +194,54 @@ export const CATEGORIES: Record<CategoryName, ICategory> = {
}, [CategoryName.ROOM]: { }, [CategoryName.ROOM]: {
categoryLabel: _td("Room"), categoryLabel: _td("Room"),
settingNames: [ settingNames: [
RoomAction.DismissReadMarker, KeyBindingAction.SearchInRoom,
RoomAction.JumpToOldestUnread, KeyBindingAction.UploadFile,
RoomAction.UploadFile, KeyBindingAction.DismissReadMarker,
RoomAction.FocusSearch, KeyBindingAction.JumpToOldestUnread,
RoomAction.ScrollUp, KeyBindingAction.ScrollUp,
RoomAction.RoomScrollDown, KeyBindingAction.ScrollDown,
RoomAction.JumpToFirstMessage, KeyBindingAction.JumpToFirstMessage,
RoomAction.JumpToLatestMessage, KeyBindingAction.JumpToLatestMessage,
], ],
}, [CategoryName.ROOM_LIST]: { }, [CategoryName.ROOM_LIST]: {
categoryLabel: _td("Room List"), categoryLabel: _td("Room List"),
settingNames: [ settingNames: [
RoomListAction.SelectRoom, KeyBindingAction.SelectRoomInRoomList,
RoomListAction.CollapseSection, KeyBindingAction.ClearRoomFilter,
RoomListAction.ExpandSection, KeyBindingAction.CollapseRoomListSection,
RoomListAction.ClearSearch, KeyBindingAction.ExpandRoomListSection,
RoomListAction.NextRoom, KeyBindingAction.NextRoom,
RoomListAction.PrevRoom, KeyBindingAction.PrevRoom,
], ],
}, [CategoryName.NAVIGATION]: { }, [CategoryName.NAVIGATION]: {
categoryLabel: _td("Navigation"), categoryLabel: _td("Navigation"),
settingNames: [ settingNames: [
NavigationAction.ToggleUserMenu, KeyBindingAction.ToggleUserMenu,
"KeyBinding.closeDialogOrContextMenu", "KeyBinding.closeDialogOrContextMenu",
"KeyBinding.activateSelectedButton", "KeyBinding.activateSelectedButton",
NavigationAction.ToggleRoomSidePanel, KeyBindingAction.ToggleRoomSidePanel,
NavigationAction.OpenShortCutDialog, KeyBindingAction.ToggleSpacePanel,
NavigationAction.GoToHome, KeyBindingAction.ShowKeyboardSettings,
NavigationAction.SelectNextUnreadRoom, KeyBindingAction.GoToHome,
NavigationAction.SelectPrevUnreadRoom, KeyBindingAction.FilterRooms,
NavigationAction.SelectNextRoom, KeyBindingAction.SelectNextUnreadRoom,
NavigationAction.SelectPrevRoom, KeyBindingAction.SelectPrevUnreadRoom,
NavigationAction.ToggleSpacePanel, KeyBindingAction.SelectNextRoom,
NavigationAction.FocusRoomSearch, KeyBindingAction.SelectPrevRoom,
], ],
}, [CategoryName.AUTOCOMPLETE]: { }, [CategoryName.AUTOCOMPLETE]: {
categoryLabel: _td("Autocomplete"), categoryLabel: _td("Autocomplete"),
settingNames: [ settingNames: [
AutocompleteAction.Cancel, KeyBindingAction.CancelAutocomplete,
AutocompleteAction.NextSelection, KeyBindingAction.NextSelectionInAutocomplete,
AutocompleteAction.PrevSelection, KeyBindingAction.PrevSelectionInAutocomplete,
AutocompleteAction.Complete, KeyBindingAction.CompleteAutocomplete,
AutocompleteAction.ForceComplete, KeyBindingAction.ForceCompleteAutocomplete,
], ],
}, [CategoryName.LABS]: { }, [CategoryName.LABS]: {
categoryLabel: _td("Labs"), categoryLabel: _td("Labs"),
settingNames: [ settingNames: [
LabsAction.ToggleHiddenEventVisibility, KeyBindingAction.ToggleHiddenEventVisibility,
], ],
}, },
}; };
@ -161,73 +250,73 @@ export const CATEGORIES: Record<CategoryName, ICategory> = {
// to implement customizable keyboard shortcuts // to implement customizable keyboard shortcuts
// TODO: TravisR will fix this nightmare when the new version of the SettingsStore becomes a thing // TODO: TravisR will fix this nightmare when the new version of the SettingsStore becomes a thing
const KEYBOARD_SHORTCUTS: IKeyboardShortcuts = { const KEYBOARD_SHORTCUTS: IKeyboardShortcuts = {
[MessageComposerAction.FormatBold]: { [KeyBindingAction.FormatBold]: {
default: { default: {
ctrlOrCmdKey: true, ctrlOrCmdKey: true,
key: Key.B, key: Key.B,
}, },
displayName: _td("Toggle Bold"), displayName: _td("Toggle Bold"),
}, },
[MessageComposerAction.FormatItalics]: { [KeyBindingAction.FormatItalics]: {
default: { default: {
ctrlOrCmdKey: true, ctrlOrCmdKey: true,
key: Key.I, key: Key.I,
}, },
displayName: _td("Toggle Italics"), displayName: _td("Toggle Italics"),
}, },
[MessageComposerAction.FormatQuote]: { [KeyBindingAction.FormatQuote]: {
default: { default: {
ctrlOrCmdKey: true, ctrlOrCmdKey: true,
key: Key.GREATER_THAN, key: Key.GREATER_THAN,
}, },
displayName: _td("Toggle Quote"), displayName: _td("Toggle Quote"),
}, },
[MessageComposerAction.CancelEditing]: { [KeyBindingAction.CancelReplyOrEdit]: {
default: { default: {
key: Key.ESCAPE, key: Key.ESCAPE,
}, },
displayName: _td("Cancel replying to a message"), displayName: _td("Cancel replying to a message"),
}, },
[MessageComposerAction.EditNextMessage]: { [KeyBindingAction.EditNextMessage]: {
default: {
key: Key.ARROW_UP,
},
displayName: _td("Navigate to next message to edit"),
},
[MessageComposerAction.EditPrevMessage]: {
default: { default: {
key: Key.ARROW_DOWN, 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"), displayName: _td("Navigate to previous message to edit"),
}, },
[MessageComposerAction.MoveCursorToStart]: { [KeyBindingAction.MoveCursorToStart]: {
default: { default: {
ctrlOrCmdKey: true, ctrlOrCmdKey: true,
key: Key.HOME, key: Key.HOME,
}, },
displayName: _td("Jump to start of the composer"), displayName: _td("Jump to start of the composer"),
}, },
[MessageComposerAction.MoveCursorToEnd]: { [KeyBindingAction.MoveCursorToEnd]: {
default: { default: {
ctrlOrCmdKey: true, ctrlOrCmdKey: true,
key: Key.END, key: Key.END,
}, },
displayName: _td("Jump to end of the composer"), displayName: _td("Jump to end of the composer"),
}, },
[MessageComposerAction.SelectNextSendHistory]: { [KeyBindingAction.SelectNextSendHistory]: {
default: {
altKey: true,
ctrlKey: true,
key: Key.ARROW_UP,
},
displayName: _td("Navigate to next message in composer history"),
},
[MessageComposerAction.SelectPrevSendHistory]: {
default: { default: {
altKey: true, altKey: true,
ctrlKey: true, ctrlKey: true,
key: Key.ARROW_DOWN, 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"), displayName: _td("Navigate to previous message in composer history"),
}, },
"KeyBinding.toggleMicInCall": { "KeyBinding.toggleMicInCall": {
@ -244,20 +333,20 @@ const KEYBOARD_SHORTCUTS: IKeyboardShortcuts = {
}, },
displayName: _td("Toggle webcam on/off"), displayName: _td("Toggle webcam on/off"),
}, },
[RoomAction.DismissReadMarker]: { [KeyBindingAction.DismissReadMarker]: {
default: { default: {
key: Key.ESCAPE, key: Key.ESCAPE,
}, },
displayName: _td("Dismiss read marker and jump to bottom"), displayName: _td("Dismiss read marker and jump to bottom"),
}, },
[RoomAction.JumpToOldestUnread]: { [KeyBindingAction.JumpToOldestUnread]: {
default: { default: {
shiftKey: true, shiftKey: true,
key: Key.PAGE_UP, key: Key.PAGE_UP,
}, },
displayName: _td("Jump to oldest unread message"), displayName: _td("Jump to oldest unread message"),
}, },
[RoomAction.UploadFile]: { [KeyBindingAction.UploadFile]: {
default: { default: {
ctrlOrCmdKey: true, ctrlOrCmdKey: true,
shiftKey: true, shiftKey: true,
@ -265,102 +354,83 @@ const KEYBOARD_SHORTCUTS: IKeyboardShortcuts = {
}, },
displayName: _td("Upload a file"), displayName: _td("Upload a file"),
}, },
[RoomAction.FocusSearch]: { [KeyBindingAction.ScrollUp]: {
default: {
ctrlOrCmdKey: true,
key: Key.F,
},
displayName: _td("Search (must be enabled)"),
},
[RoomAction.ScrollUp]: {
default: { default: {
key: Key.PAGE_UP, key: Key.PAGE_UP,
}, },
displayName: _td("Scroll up in the timeline"), displayName: _td("Scroll up in the timeline"),
}, },
[RoomAction.RoomScrollDown]: { [KeyBindingAction.ScrollDown]: {
default: { default: {
key: Key.PAGE_DOWN, key: Key.PAGE_DOWN,
}, },
displayName: _td("Scroll down in the timeline"), displayName: _td("Scroll down in the timeline"),
}, },
[NavigationAction.FocusRoomSearch]: { [KeyBindingAction.FilterRooms]: {
default: { default: {
ctrlOrCmdKey: true, ctrlOrCmdKey: true,
key: Key.K, key: Key.K,
}, },
displayName: _td("Jump to room search"), displayName: _td("Jump to room search"),
}, },
[RoomListAction.SelectRoom]: { [KeyBindingAction.SelectRoomInRoomList]: {
default: { default: {
key: Key.ENTER, key: Key.ENTER,
}, },
displayName: _td("Select room from the room list"), displayName: _td("Select room from the room list"),
}, },
[RoomListAction.CollapseSection]: { [KeyBindingAction.CollapseRoomListSection]: {
default: { default: {
key: Key.ARROW_LEFT, key: Key.ARROW_LEFT,
}, },
displayName: _td("Collapse room list section"), displayName: _td("Collapse room list section"),
}, },
[RoomListAction.ExpandSection]: { [KeyBindingAction.ExpandRoomListSection]: {
default: { default: {
key: Key.ARROW_RIGHT, key: Key.ARROW_RIGHT,
}, },
displayName: _td("Expand room list section"), displayName: _td("Expand room list section"),
}, },
[RoomListAction.ClearSearch]: { [KeyBindingAction.ClearRoomFilter]: {
default: { default: {
key: Key.ESCAPE, key: Key.ESCAPE,
}, },
displayName: _td("Clear room list filter field"), displayName: _td("Clear room list filter field"),
}, },
[RoomListAction.NextRoom]: { [KeyBindingAction.NextRoom]: {
default: {
key: Key.ARROW_UP,
},
displayName: _td("Navigate up in the room list"),
},
[RoomListAction.PrevRoom]: {
default: { default: {
key: Key.ARROW_DOWN, 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"), displayName: _td("Navigate down in the room list"),
}, },
[NavigationAction.ToggleUserMenu]: { [KeyBindingAction.ToggleUserMenu]: {
default: { default: {
ctrlOrCmdKey: true, ctrlOrCmdKey: true,
key: Key.BACKTICK, key: Key.BACKTICK,
}, },
displayName: _td("Toggle the top left menu"), displayName: _td("Toggle the top left menu"),
}, },
"KeyBinding.closeDialogOrContextMenu": { [KeyBindingAction.ToggleRoomSidePanel]: {
default: {
key: Key.ESCAPE,
},
displayName: _td("Close dialog or context menu"),
},
"KeyBinding.activateSelectedButton": {
default: {
key: Key.ENTER,
},
displayName: _td("Activate selected button"),
},
[NavigationAction.ToggleRoomSidePanel]: {
default: { default: {
ctrlOrCmdKey: true, ctrlOrCmdKey: true,
key: Key.PERIOD, key: Key.PERIOD,
}, },
displayName: _td("Toggle right panel"), displayName: _td("Toggle right panel"),
}, },
[NavigationAction.OpenShortCutDialog]: { [KeyBindingAction.ShowKeyboardSettings]: {
default: { default: {
ctrlOrCmdKey: true, ctrlOrCmdKey: true,
key: Key.SLASH, key: Key.SLASH,
}, },
displayName: _td("Open this settings tab"), displayName: _td("Open this settings tab"),
}, },
[NavigationAction.GoToHome]: { [KeyBindingAction.GoToHome]: {
default: { default: {
ctrlOrCmdKey: true, ctrlOrCmdKey: true,
altKey: !isMac, altKey: !isMac,
@ -369,55 +439,55 @@ const KEYBOARD_SHORTCUTS: IKeyboardShortcuts = {
}, },
displayName: _td("Go to Home View"), displayName: _td("Go to Home View"),
}, },
[NavigationAction.SelectNextUnreadRoom]: { [KeyBindingAction.SelectNextUnreadRoom]: {
default: { default: {
shiftKey: true, shiftKey: true,
altKey: true, altKey: true,
key: Key.ARROW_UP, key: Key.ARROW_DOWN,
}, },
displayName: _td("Next unread room or DM"), displayName: _td("Next unread room or DM"),
}, },
[NavigationAction.SelectPrevUnreadRoom]: { [KeyBindingAction.SelectPrevUnreadRoom]: {
default: { default: {
shiftKey: true, shiftKey: true,
altKey: true, altKey: true,
key: Key.ARROW_DOWN, key: Key.ARROW_UP,
}, },
displayName: _td("Previous unread room or DM"), 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: { default: {
altKey: true, altKey: true,
key: Key.ARROW_UP, 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"), displayName: _td("Previous room or DM"),
}, },
[AutocompleteAction.Cancel]: { [KeyBindingAction.CancelAutocomplete]: {
default: { default: {
key: Key.ESCAPE, key: Key.ESCAPE,
}, },
displayName: _td("Cancel autocomplete"), displayName: _td("Cancel autocomplete"),
}, },
[AutocompleteAction.NextSelection]: { [KeyBindingAction.NextSelectionInAutocomplete]: {
default: {
key: Key.ARROW_UP,
},
displayName: _td("Next autocomplete suggestion"),
},
[AutocompleteAction.PrevSelection]: {
default: { default: {
key: Key.ARROW_DOWN, key: Key.ARROW_DOWN,
}, },
displayName: _td("Next autocomplete suggestion"),
},
[KeyBindingAction.PrevSelectionInAutocomplete]: {
default: {
key: Key.ARROW_UP,
},
displayName: _td("Previous autocomplete suggestion"), displayName: _td("Previous autocomplete suggestion"),
}, },
[NavigationAction.ToggleSpacePanel]: { [KeyBindingAction.ToggleSpacePanel]: {
default: { default: {
ctrlOrCmdKey: true, ctrlOrCmdKey: true,
shiftKey: true, shiftKey: true,
@ -425,7 +495,7 @@ const KEYBOARD_SHORTCUTS: IKeyboardShortcuts = {
}, },
displayName: _td("Toggle space panel"), displayName: _td("Toggle space panel"),
}, },
[LabsAction.ToggleHiddenEventVisibility]: { [KeyBindingAction.ToggleHiddenEventVisibility]: {
default: { default: {
ctrlOrCmdKey: true, ctrlOrCmdKey: true,
shiftKey: true, shiftKey: true,
@ -433,61 +503,86 @@ const KEYBOARD_SHORTCUTS: IKeyboardShortcuts = {
}, },
displayName: _td("Toggle hidden event visibility"), displayName: _td("Toggle hidden event visibility"),
}, },
[RoomAction.JumpToFirstMessage]: { [KeyBindingAction.JumpToFirstMessage]: {
default: { default: {
key: Key.HOME, key: Key.HOME,
ctrlKey: true, ctrlKey: true,
}, },
displayName: _td("Jump to first message"), displayName: _td("Jump to first message"),
}, },
[RoomAction.JumpToOldestUnread]: { [KeyBindingAction.JumpToOldestUnread]: {
default: { default: {
key: Key.END, key: Key.END,
ctrlKey: true, ctrlKey: true,
}, },
displayName: _td("Jump to last message"), displayName: _td("Jump to last message"),
}, },
[MessageComposerAction.EditUndo]: { [KeyBindingAction.EditUndo]: {
default: { default: {
key: Key.Z, key: Key.Z,
ctrlOrCmdKey: true, ctrlOrCmdKey: true,
}, },
displayName: _td("Undo edit"), 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 => { // XXX: These have to be manually mirrored in KeyBindingDefaults
const keyboardShortcuts = KEYBOARD_SHORTCUTS; const getNonCustomizableShortcuts = (): IKeyboardShortcuts => {
const ctrlEnterToSend = SettingsStore.getValue('MessageComposerInput.ctrlEnterToSend'); const ctrlEnterToSend = SettingsStore.getValue('MessageComposerInput.ctrlEnterToSend');
keyboardShortcuts[MessageComposerAction.Send] = { return {
default: { [KeyBindingAction.SendMessage]: {
key: Key.ENTER, default: {
ctrlOrCmdKey: ctrlEnterToSend, 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"),
},
};
};
}; export const getCustomizableShortcuts = (): IKeyboardShortcuts => {
keyboardShortcuts[MessageComposerAction.NewLine] = { const keyboardShortcuts = KEYBOARD_SHORTCUTS;
default: {
key: Key.ENTER, keyboardShortcuts[KeyBindingAction.EditRedo] = {
shiftKey: !ctrlEnterToSend,
},
displayName: _td("New line"),
};
keyboardShortcuts[MessageComposerAction.EditRedo] = {
default: { default: {
key: isMac ? Key.Z : Key.Y, key: isMac ? Key.Z : Key.Y,
ctrlOrCmdKey: true, ctrlOrCmdKey: true,
@ -499,6 +594,19 @@ export const getKeyboardShortcuts = (): IKeyboardShortcuts => {
return keyboardShortcuts; 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 => { export const registerShortcut = (shortcutName: string, categoryName: CategoryName, shortcut: ISetting): void => {
KEYBOARD_SHORTCUTS[shortcutName] = shortcut; KEYBOARD_SHORTCUTS[shortcutName] = shortcut;
CATEGORIES[categoryName].settingNames.push(shortcutName); CATEGORIES[categoryName].settingNames.push(shortcutName);

View file

@ -31,7 +31,7 @@ import LeftPanelWidget from "./LeftPanelWidget";
import { replaceableComponent } from "../../utils/replaceableComponent"; import { replaceableComponent } from "../../utils/replaceableComponent";
import SpaceStore from "../../stores/spaces/SpaceStore"; import SpaceStore from "../../stores/spaces/SpaceStore";
import { MetaSpace, SpaceKey, UPDATE_SELECTED_SPACE } from "../../stores/spaces"; import { MetaSpace, SpaceKey, UPDATE_SELECTED_SPACE } from "../../stores/spaces";
import { getKeyBindingsManager, RoomListAction } from "../../KeyBindingsManager"; import { getKeyBindingsManager } from "../../KeyBindingsManager";
import UIStore from "../../stores/UIStore"; import UIStore from "../../stores/UIStore";
import { findSiblingElement, IState as IRovingTabIndexState } from "../../accessibility/RovingTabIndex"; import { findSiblingElement, IState as IRovingTabIndexState } from "../../accessibility/RovingTabIndex";
import RoomListHeader from "../views/rooms/RoomListHeader"; import RoomListHeader from "../views/rooms/RoomListHeader";
@ -44,6 +44,7 @@ import IndicatorScrollbar from "./IndicatorScrollbar";
import RoomBreadcrumbs from "../views/rooms/RoomBreadcrumbs"; import RoomBreadcrumbs from "../views/rooms/RoomBreadcrumbs";
import SettingsStore from "../../settings/SettingsStore"; import SettingsStore from "../../settings/SettingsStore";
import UserMenu from "./UserMenu"; import UserMenu from "./UserMenu";
import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
interface IProps { interface IProps {
isMinimized: boolean; isMinimized: boolean;
@ -296,7 +297,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
const action = getKeyBindingsManager().getRoomListAction(ev); const action = getKeyBindingsManager().getRoomListAction(ev);
switch (action) { switch (action) {
case RoomListAction.NextRoom: case KeyBindingAction.NextRoom:
if (!state) { if (!state) {
ev.stopPropagation(); ev.stopPropagation();
ev.preventDefault(); ev.preventDefault();
@ -304,7 +305,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
} }
break; break;
case RoomListAction.PrevRoom: case KeyBindingAction.PrevRoom:
if (state && state.activeRef === findSiblingElement(state.refs, 0)) { if (state && state.activeRef === findSiblingElement(state.refs, 0)) {
ev.stopPropagation(); ev.stopPropagation();
ev.preventDefault(); ev.preventDefault();

View file

@ -48,7 +48,7 @@ import { IOOBData, IThreepidInvite } from "../../stores/ThreepidInviteStore";
import Modal from "../../Modal"; import Modal from "../../Modal";
import { ICollapseConfig } from "../../resizer/distributors/collapse"; import { ICollapseConfig } from "../../resizer/distributors/collapse";
import HostSignupContainer from '../views/host_signup/HostSignupContainer'; import HostSignupContainer from '../views/host_signup/HostSignupContainer';
import { getKeyBindingsManager, NavigationAction, RoomAction, LabsAction } from '../../KeyBindingsManager'; import { getKeyBindingsManager } from '../../KeyBindingsManager';
import { IOpts } from "../../createRoom"; import { IOpts } from "../../createRoom";
import SpacePanel from "../views/spaces/SpacePanel"; import SpacePanel from "../views/spaces/SpacePanel";
import { replaceableComponent } from "../../utils/replaceableComponent"; import { replaceableComponent } from "../../utils/replaceableComponent";
@ -71,6 +71,7 @@ import { UserTab } from "../views/dialogs/UserSettingsDialog";
import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload"; import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
import RightPanelStore from '../../stores/right-panel/RightPanelStore'; import RightPanelStore from '../../stores/right-panel/RightPanelStore';
import { TimelineRenderingType } from "../../contexts/RoomContext"; 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) // 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. // so each pinned message may trigger a request. Limit the number per room for sanity.
@ -447,15 +448,15 @@ class LoggedInView extends React.Component<IProps, IState> {
const roomAction = getKeyBindingsManager().getRoomAction(ev); const roomAction = getKeyBindingsManager().getRoomAction(ev);
switch (roomAction) { switch (roomAction) {
case RoomAction.ScrollUp: case KeyBindingAction.ScrollUp:
case RoomAction.RoomScrollDown: case KeyBindingAction.ScrollDown:
case RoomAction.JumpToFirstMessage: case KeyBindingAction.JumpToFirstMessage:
case RoomAction.JumpToLatestMessage: case KeyBindingAction.JumpToLatestMessage:
// pass the event down to the scroll panel // pass the event down to the scroll panel
this.onScrollKeyPressed(ev); this.onScrollKeyPressed(ev);
handled = true; handled = true;
break; break;
case RoomAction.FocusSearch: case KeyBindingAction.SearchInRoom:
dis.dispatch({ dis.dispatch({
action: 'focus_search', action: 'focus_search',
}); });
@ -470,41 +471,41 @@ class LoggedInView extends React.Component<IProps, IState> {
const navAction = getKeyBindingsManager().getNavigationAction(ev); const navAction = getKeyBindingsManager().getNavigationAction(ev);
switch (navAction) { switch (navAction) {
case NavigationAction.FocusRoomSearch: case KeyBindingAction.FilterRooms:
dis.dispatch({ dis.dispatch({
action: 'focus_room_filter', action: 'focus_room_filter',
}); });
handled = true; handled = true;
break; break;
case NavigationAction.ToggleUserMenu: case KeyBindingAction.ToggleUserMenu:
dis.fire(Action.ToggleUserMenu); dis.fire(Action.ToggleUserMenu);
handled = true; handled = true;
break; break;
case NavigationAction.OpenShortCutDialog: case KeyBindingAction.ShowKeyboardSettings:
dis.dispatch<OpenToTabPayload>({ dis.dispatch<OpenToTabPayload>({
action: Action.ViewUserSettings, action: Action.ViewUserSettings,
initialTabId: UserTab.Keyboard, initialTabId: UserTab.Keyboard,
}); });
handled = true; handled = true;
break; break;
case NavigationAction.GoToHome: case KeyBindingAction.GoToHome:
dis.dispatch({ dis.dispatch({
action: 'view_home_page', action: 'view_home_page',
}); });
Modal.closeCurrentModal("homeKeyboardShortcut"); Modal.closeCurrentModal("homeKeyboardShortcut");
handled = true; handled = true;
break; break;
case NavigationAction.ToggleSpacePanel: case KeyBindingAction.ToggleSpacePanel:
dis.fire(Action.ToggleSpacePanel); dis.fire(Action.ToggleSpacePanel);
handled = true; handled = true;
break; break;
case NavigationAction.ToggleRoomSidePanel: case KeyBindingAction.ToggleRoomSidePanel:
if (this.props.page_type === "room_view" || this.props.page_type === "group_view") { if (this.props.page_type === "room_view" || this.props.page_type === "group_view") {
RightPanelStore.instance.togglePanel(); RightPanelStore.instance.togglePanel();
handled = true; handled = true;
} }
break; break;
case NavigationAction.SelectPrevRoom: case KeyBindingAction.SelectPrevRoom:
dis.dispatch<ViewRoomDeltaPayload>({ dis.dispatch<ViewRoomDeltaPayload>({
action: Action.ViewRoomDelta, action: Action.ViewRoomDelta,
delta: -1, delta: -1,
@ -512,7 +513,7 @@ class LoggedInView extends React.Component<IProps, IState> {
}); });
handled = true; handled = true;
break; break;
case NavigationAction.SelectNextRoom: case KeyBindingAction.SelectNextRoom:
dis.dispatch<ViewRoomDeltaPayload>({ dis.dispatch<ViewRoomDeltaPayload>({
action: Action.ViewRoomDelta, action: Action.ViewRoomDelta,
delta: 1, delta: 1,
@ -520,14 +521,14 @@ class LoggedInView extends React.Component<IProps, IState> {
}); });
handled = true; handled = true;
break; break;
case NavigationAction.SelectPrevUnreadRoom: case KeyBindingAction.SelectPrevUnreadRoom:
dis.dispatch<ViewRoomDeltaPayload>({ dis.dispatch<ViewRoomDeltaPayload>({
action: Action.ViewRoomDelta, action: Action.ViewRoomDelta,
delta: -1, delta: -1,
unread: true, unread: true,
}); });
break; break;
case NavigationAction.SelectNextUnreadRoom: case KeyBindingAction.SelectNextUnreadRoom:
dis.dispatch<ViewRoomDeltaPayload>({ dis.dispatch<ViewRoomDeltaPayload>({
action: Action.ViewRoomDelta, action: Action.ViewRoomDelta,
delta: 1, delta: 1,
@ -543,7 +544,7 @@ class LoggedInView extends React.Component<IProps, IState> {
if (!handled) { if (!handled) {
const labsAction = getKeyBindingsManager().getLabsAction(ev); const labsAction = getKeyBindingsManager().getLabsAction(ev);
switch (labsAction) { switch (labsAction) {
case LabsAction.ToggleHiddenEventVisibility: { case KeyBindingAction.ToggleHiddenEventVisibility: {
const hiddenEventVisibility = SettingsStore.getValueAt( const hiddenEventVisibility = SettingsStore.getValueAt(
SettingLevel.DEVICE, SettingLevel.DEVICE,
'showHiddenEventsInTimeline', 'showHiddenEventsInTimeline',

View file

@ -25,7 +25,7 @@ import AccessibleButton from "../views/elements/AccessibleButton";
import { Action } from "../../dispatcher/actions"; import { Action } from "../../dispatcher/actions";
import RoomListStore from "../../stores/room-list/RoomListStore"; import RoomListStore from "../../stores/room-list/RoomListStore";
import { NameFilterCondition } from "../../stores/room-list/filters/NameFilterCondition"; import { NameFilterCondition } from "../../stores/room-list/filters/NameFilterCondition";
import { getKeyBindingsManager, RoomListAction } from "../../KeyBindingsManager"; import { getKeyBindingsManager } from "../../KeyBindingsManager";
import { replaceableComponent } from "../../utils/replaceableComponent"; import { replaceableComponent } from "../../utils/replaceableComponent";
import SpaceStore from "../../stores/spaces/SpaceStore"; import SpaceStore from "../../stores/spaces/SpaceStore";
import { UPDATE_SELECTED_SPACE } from "../../stores/spaces"; import { UPDATE_SELECTED_SPACE } from "../../stores/spaces";
@ -33,7 +33,7 @@ import { isMac, Key } from "../../Keyboard";
import SettingsStore from "../../settings/SettingsStore"; import SettingsStore from "../../settings/SettingsStore";
import Modal from "../../Modal"; import Modal from "../../Modal";
import SpotlightDialog from "../views/dialogs/SpotlightDialog"; import SpotlightDialog from "../views/dialogs/SpotlightDialog";
import { ALTERNATE_KEY_NAME } from "../../accessibility/KeyboardShortcuts"; import { ALTERNATE_KEY_NAME, KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
interface IProps { interface IProps {
isMinimized: boolean; isMinimized: boolean;
@ -141,11 +141,11 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
private onKeyDown = (ev: React.KeyboardEvent) => { private onKeyDown = (ev: React.KeyboardEvent) => {
const action = getKeyBindingsManager().getRoomListAction(ev); const action = getKeyBindingsManager().getRoomListAction(ev);
switch (action) { switch (action) {
case RoomListAction.ClearSearch: case KeyBindingAction.ClearRoomFilter:
this.clearInput(); this.clearInput();
defaultDispatcher.fire(Action.FocusSendMessageComposer); defaultDispatcher.fire(Action.FocusSendMessageComposer);
break; break;
case RoomListAction.SelectRoom: { case KeyBindingAction.SelectRoomInRoomList: {
const shouldClear = this.props.onSelectRoom(); const shouldClear = this.props.onSelectRoom();
if (shouldClear) { if (shouldClear) {
// wrap in set immediate to delay it so that we don't clear the filter & then change room // wrap in set immediate to delay it so that we don't clear the filter & then change room

View file

@ -79,7 +79,7 @@ import Notifier from "../../Notifier";
import { showToast as showNotificationsToast } from "../../toasts/DesktopNotificationsToast"; import { showToast as showNotificationsToast } from "../../toasts/DesktopNotificationsToast";
import { RoomNotificationStateStore } from "../../stores/notifications/RoomNotificationStateStore"; import { RoomNotificationStateStore } from "../../stores/notifications/RoomNotificationStateStore";
import { Container, WidgetLayoutStore } from "../../stores/widgets/WidgetLayoutStore"; import { Container, WidgetLayoutStore } from "../../stores/widgets/WidgetLayoutStore";
import { getKeyBindingsManager, RoomAction } from '../../KeyBindingsManager'; import { getKeyBindingsManager } from '../../KeyBindingsManager';
import { objectHasDiff } from "../../utils/objects"; import { objectHasDiff } from "../../utils/objects";
import SpaceRoomView from "./SpaceRoomView"; import SpaceRoomView from "./SpaceRoomView";
import { IOpts } from "../../createRoom"; import { IOpts } from "../../createRoom";
@ -100,6 +100,7 @@ import { ComposerType } from "../../dispatcher/payloads/ComposerInsertPayload";
import AppsDrawer from '../views/rooms/AppsDrawer'; import AppsDrawer from '../views/rooms/AppsDrawer';
import { RightPanelPhases } from '../../stores/right-panel/RightPanelStorePhases'; import { RightPanelPhases } from '../../stores/right-panel/RightPanelStorePhases';
import { ActionPayload } from "../../dispatcher/payloads"; import { ActionPayload } from "../../dispatcher/payloads";
import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
const DEBUG = false; const DEBUG = false;
let debuglog = function(msg: string) {}; let debuglog = function(msg: string) {};
@ -797,16 +798,16 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
const action = getKeyBindingsManager().getRoomAction(ev); const action = getKeyBindingsManager().getRoomAction(ev);
switch (action) { switch (action) {
case RoomAction.DismissReadMarker: case KeyBindingAction.DismissReadMarker:
this.messagePanel.forgetReadMarker(); this.messagePanel.forgetReadMarker();
this.jumpToLiveTimeline(); this.jumpToLiveTimeline();
handled = true; handled = true;
break; break;
case RoomAction.JumpToOldestUnread: case KeyBindingAction.JumpToOldestUnread:
this.jumpToReadMarker(); this.jumpToReadMarker();
handled = true; handled = true;
break; break;
case RoomAction.UploadFile: case KeyBindingAction.UploadFile:
dis.dispatch({ action: "upload_file" }, true); dis.dispatch({ action: "upload_file" }, true);
handled = true; handled = true;
break; break;

View file

@ -20,8 +20,9 @@ import { logger } from "matrix-js-sdk/src/logger";
import Timer from '../../utils/Timer'; import Timer from '../../utils/Timer';
import AutoHideScrollbar from "./AutoHideScrollbar"; import AutoHideScrollbar from "./AutoHideScrollbar";
import { replaceableComponent } from "../../utils/replaceableComponent"; import { replaceableComponent } from "../../utils/replaceableComponent";
import { getKeyBindingsManager, RoomAction } from "../../KeyBindingsManager"; import { getKeyBindingsManager } from "../../KeyBindingsManager";
import ResizeNotifier from "../../utils/ResizeNotifier"; import ResizeNotifier from "../../utils/ResizeNotifier";
import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
const DEBUG_SCROLL = false; const DEBUG_SCROLL = false;
@ -592,19 +593,19 @@ export default class ScrollPanel extends React.Component<IProps> {
let isScrolling = false; let isScrolling = false;
const roomAction = getKeyBindingsManager().getRoomAction(ev); const roomAction = getKeyBindingsManager().getRoomAction(ev);
switch (roomAction) { switch (roomAction) {
case RoomAction.ScrollUp: case KeyBindingAction.ScrollUp:
this.scrollRelative(-1); this.scrollRelative(-1);
isScrolling = true; isScrolling = true;
break; break;
case RoomAction.RoomScrollDown: case KeyBindingAction.ScrollDown:
this.scrollRelative(1); this.scrollRelative(1);
isScrolling = true; isScrolling = true;
break; break;
case RoomAction.JumpToFirstMessage: case KeyBindingAction.JumpToFirstMessage:
this.scrollToTop(); this.scrollToTop();
isScrolling = true; isScrolling = true;
break; break;
case RoomAction.JumpToLatestMessage: case KeyBindingAction.JumpToLatestMessage:
this.scrollToBottom(); this.scrollToBottom();
isScrolling = true; isScrolling = true;
break; break;

View file

@ -48,9 +48,9 @@ import { IDiff } from "../../../editor/diff";
import AutocompleteWrapperModel from "../../../editor/autocomplete"; import AutocompleteWrapperModel from "../../../editor/autocomplete";
import DocumentPosition from "../../../editor/position"; import DocumentPosition from "../../../editor/position";
import { ICompletion } from "../../../autocomplete/Autocompleter"; import { ICompletion } from "../../../autocomplete/Autocompleter";
import { AutocompleteAction, getKeyBindingsManager, MessageComposerAction } from '../../../KeyBindingsManager'; import { getKeyBindingsManager } from '../../../KeyBindingsManager';
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import { ALTERNATE_KEY_NAME } from '../../../accessibility/KeyboardShortcuts'; import { ALTERNATE_KEY_NAME, KeyBindingAction } from '../../../accessibility/KeyboardShortcuts';
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
// matches emoticons which follow the start of a line or whitespace // matches emoticons which follow the start of a line or whitespace
@ -483,29 +483,29 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
if (model.autoComplete?.hasCompletions()) { if (model.autoComplete?.hasCompletions()) {
const autoComplete = model.autoComplete; const autoComplete = model.autoComplete;
switch (autocompleteAction) { switch (autocompleteAction) {
case AutocompleteAction.ForceComplete: case KeyBindingAction.ForceCompleteAutocomplete:
case AutocompleteAction.Complete: case KeyBindingAction.CompleteAutocomplete:
this.historyManager.ensureLastChangesPushed(this.props.model); this.historyManager.ensureLastChangesPushed(this.props.model);
this.modifiedFlag = true; this.modifiedFlag = true;
autoComplete.confirmCompletion(); autoComplete.confirmCompletion();
handled = true; handled = true;
break; break;
case AutocompleteAction.PrevSelection: case KeyBindingAction.PrevSelectionInAutocomplete:
autoComplete.selectPreviousSelection(); autoComplete.selectPreviousSelection();
handled = true; handled = true;
break; break;
case AutocompleteAction.NextSelection: case KeyBindingAction.NextSelectionInAutocomplete:
autoComplete.selectNextSelection(); autoComplete.selectNextSelection();
handled = true; handled = true;
break; break;
case AutocompleteAction.Cancel: case KeyBindingAction.CancelAutocomplete:
autoComplete.onEscape(event); autoComplete.onEscape(event);
handled = true; handled = true;
break; break;
default: default:
return; // don't preventDefault on anything else 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 // there is no current autocomplete window, try to open it
this.tabCompleteName(); this.tabCompleteName();
handled = true; handled = true;
@ -521,19 +521,19 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
const action = getKeyBindingsManager().getMessageComposerAction(event); const action = getKeyBindingsManager().getMessageComposerAction(event);
switch (action) { switch (action) {
case MessageComposerAction.FormatBold: case KeyBindingAction.FormatBold:
this.onFormatAction(Formatting.Bold); this.onFormatAction(Formatting.Bold);
handled = true; handled = true;
break; break;
case MessageComposerAction.FormatItalics: case KeyBindingAction.FormatItalics:
this.onFormatAction(Formatting.Italics); this.onFormatAction(Formatting.Italics);
handled = true; handled = true;
break; break;
case MessageComposerAction.FormatQuote: case KeyBindingAction.FormatQuote:
this.onFormatAction(Formatting.Quote); this.onFormatAction(Formatting.Quote);
handled = true; handled = true;
break; break;
case MessageComposerAction.EditRedo: case KeyBindingAction.EditRedo:
if (this.historyManager.canRedo()) { if (this.historyManager.canRedo()) {
const { parts, caret } = this.historyManager.redo(); const { parts, caret } = this.historyManager.redo();
// pass matching inputType so historyManager doesn't push echo // pass matching inputType so historyManager doesn't push echo
@ -542,7 +542,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
} }
handled = true; handled = true;
break; break;
case MessageComposerAction.EditUndo: case KeyBindingAction.EditUndo:
if (this.historyManager.canUndo()) { if (this.historyManager.canUndo()) {
const { parts, caret } = this.historyManager.undo(this.props.model); const { parts, caret } = this.historyManager.undo(this.props.model);
// pass matching inputType so historyManager doesn't push echo // pass matching inputType so historyManager doesn't push echo
@ -551,18 +551,18 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
} }
handled = true; handled = true;
break; break;
case MessageComposerAction.NewLine: case KeyBindingAction.NewLine:
this.insertText("\n"); this.insertText("\n");
handled = true; handled = true;
break; break;
case MessageComposerAction.MoveCursorToStart: case KeyBindingAction.MoveCursorToStart:
setSelection(this.editorRef.current, model, { setSelection(this.editorRef.current, model, {
index: 0, index: 0,
offset: 0, offset: 0,
}); });
handled = true; handled = true;
break; break;
case MessageComposerAction.MoveCursorToEnd: case KeyBindingAction.MoveCursorToEnd:
setSelection(this.editorRef.current, model, { setSelection(this.editorRef.current, model, {
index: model.parts.length - 1, index: model.parts.length - 1,
offset: model.parts[model.parts.length - 1].text.length, offset: model.parts[model.parts.length - 1].text.length,

View file

@ -34,7 +34,7 @@ import BasicMessageComposer, { REGEX_EMOTICON } from "./BasicMessageComposer";
import { CommandCategories } from '../../../SlashCommands'; import { CommandCategories } from '../../../SlashCommands';
import { Action } from "../../../dispatcher/actions"; import { Action } from "../../../dispatcher/actions";
import CountlyAnalytics from "../../../CountlyAnalytics"; import CountlyAnalytics from "../../../CountlyAnalytics";
import { getKeyBindingsManager, MessageComposerAction } from '../../../KeyBindingsManager'; import { getKeyBindingsManager } from '../../../KeyBindingsManager';
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import SendHistoryManager from '../../../SendHistoryManager'; import SendHistoryManager from '../../../SendHistoryManager';
import { ActionPayload } from "../../../dispatcher/payloads"; import { ActionPayload } from "../../../dispatcher/payloads";
@ -45,6 +45,7 @@ import { withMatrixClientHOC, MatrixClientProps } from '../../../contexts/Matrix
import RoomContext from '../../../contexts/RoomContext'; import RoomContext from '../../../contexts/RoomContext';
import { ComposerType } from "../../../dispatcher/payloads/ComposerInsertPayload"; import { ComposerType } from "../../../dispatcher/payloads/ComposerInsertPayload";
import { getSlashCommand, isSlashCommand, runSlashCommand, shouldSendAnyway } from "../../../editor/commands"; import { getSlashCommand, isSlashCommand, runSlashCommand, shouldSendAnyway } from "../../../editor/commands";
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
function getHtmlReplyFallback(mxEvent: MatrixEvent): string { function getHtmlReplyFallback(mxEvent: MatrixEvent): string {
const html = mxEvent.getContent().formatted_body; const html = mxEvent.getContent().formatted_body;
@ -156,16 +157,16 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
} }
const action = getKeyBindingsManager().getMessageComposerAction(event); const action = getKeyBindingsManager().getMessageComposerAction(event);
switch (action) { switch (action) {
case MessageComposerAction.Send: case KeyBindingAction.SendMessage:
this.sendEdit(); this.sendEdit();
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();
break; break;
case MessageComposerAction.CancelEditing: case KeyBindingAction.CancelReplyOrEdit:
event.stopPropagation(); event.stopPropagation();
this.cancelEdit(); this.cancelEdit();
break; break;
case MessageComposerAction.EditPrevMessage: { case KeyBindingAction.EditPrevMessage: {
if (this.editorRef.current?.isModified() || !this.editorRef.current?.isCaretAtStart()) { if (this.editorRef.current?.isModified() || !this.editorRef.current?.isCaretAtStart()) {
return; return;
} }
@ -184,7 +185,7 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
} }
break; break;
} }
case MessageComposerAction.EditNextMessage: { case KeyBindingAction.EditNextMessage: {
if (this.editorRef.current?.isModified() || !this.editorRef.current?.isCaretAtEnd()) { if (this.editorRef.current?.isModified() || !this.editorRef.current?.isCaretAtEnd()) {
return; return;
} }

View file

@ -54,8 +54,9 @@ import { arrayFastClone, arrayHasOrderChange } from "../../../utils/arrays";
import { objectExcluding, objectHasDiff } from "../../../utils/objects"; import { objectExcluding, objectHasDiff } from "../../../utils/objects";
import ExtraTile from "./ExtraTile"; import ExtraTile from "./ExtraTile";
import { ListNotificationState } from "../../../stores/notifications/ListNotificationState"; import { ListNotificationState } from "../../../stores/notifications/ListNotificationState";
import { getKeyBindingsManager, RoomListAction } from "../../../KeyBindingsManager"; import { getKeyBindingsManager } from "../../../KeyBindingsManager";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
const SHOW_N_BUTTON_HEIGHT = 28; // As defined by CSS const SHOW_N_BUTTON_HEIGHT = 28; // As defined by CSS
const RESIZE_HANDLE_HEIGHT = 4; // As defined by CSS const RESIZE_HANDLE_HEIGHT = 4; // As defined by CSS
@ -474,14 +475,14 @@ export default class RoomSublist extends React.Component<IProps, IState> {
private onHeaderKeyDown = (ev: React.KeyboardEvent) => { private onHeaderKeyDown = (ev: React.KeyboardEvent) => {
const action = getKeyBindingsManager().getRoomListAction(ev); const action = getKeyBindingsManager().getRoomListAction(ev);
switch (action) { switch (action) {
case RoomListAction.CollapseSection: case KeyBindingAction.CollapseRoomListSection:
ev.stopPropagation(); ev.stopPropagation();
if (this.state.isExpanded) { if (this.state.isExpanded) {
// Collapse the room sublist if it isn't already // Collapse the room sublist if it isn't already
this.toggleCollapsed(); this.toggleCollapsed();
} }
break; break;
case RoomListAction.ExpandSection: { case KeyBindingAction.ExpandRoomListSection: {
ev.stopPropagation(); ev.stopPropagation();
if (!this.state.isExpanded) { if (!this.state.isExpanded) {
// Expand the room sublist if it isn't already // Expand the room sublist if it isn't already

View file

@ -46,7 +46,7 @@ import { containsEmoji } from "../../../effects/utils";
import { CHAT_EFFECTS } from '../../../effects'; import { CHAT_EFFECTS } from '../../../effects';
import CountlyAnalytics from "../../../CountlyAnalytics"; import CountlyAnalytics from "../../../CountlyAnalytics";
import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { getKeyBindingsManager, MessageComposerAction } from '../../../KeyBindingsManager'; import { getKeyBindingsManager } from '../../../KeyBindingsManager';
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import SettingsStore from '../../../settings/SettingsStore'; import SettingsStore from '../../../settings/SettingsStore';
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"; import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
@ -56,6 +56,7 @@ import RoomContext, { TimelineRenderingType } from '../../../contexts/RoomContex
import DocumentPosition from "../../../editor/position"; import DocumentPosition from "../../../editor/position";
import { ComposerType } from "../../../dispatcher/payloads/ComposerInsertPayload"; import { ComposerType } from "../../../dispatcher/payloads/ComposerInsertPayload";
import { getSlashCommand, isSlashCommand, runSlashCommand, shouldSendAnyway } from "../../../editor/commands"; import { getSlashCommand, isSlashCommand, runSlashCommand, shouldSendAnyway } from "../../../editor/commands";
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
interface IAddReplyOpts { interface IAddReplyOpts {
permalinkCreator?: RoomPermalinkCreator; permalinkCreator?: RoomPermalinkCreator;
@ -221,21 +222,21 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
const replyingToThread = this.props.relation?.key === RelationType.Thread; const replyingToThread = this.props.relation?.key === RelationType.Thread;
const action = getKeyBindingsManager().getMessageComposerAction(event); const action = getKeyBindingsManager().getMessageComposerAction(event);
switch (action) { switch (action) {
case MessageComposerAction.Send: case KeyBindingAction.SendMessage:
this.sendMessage(); this.sendMessage();
event.preventDefault(); event.preventDefault();
break; break;
case MessageComposerAction.SelectPrevSendHistory: case KeyBindingAction.SelectPrevSendHistory:
case MessageComposerAction.SelectNextSendHistory: { case KeyBindingAction.SelectNextSendHistory: {
// Try select composer history // Try select composer history
const selected = this.selectSendHistory(action === MessageComposerAction.SelectPrevSendHistory); const selected = this.selectSendHistory(action === KeyBindingAction.SelectPrevSendHistory);
if (selected) { if (selected) {
// We're selecting history, so prevent the key event from doing anything else // We're selecting history, so prevent the key event from doing anything else
event.preventDefault(); event.preventDefault();
} }
break; break;
} }
case MessageComposerAction.EditPrevMessage: case KeyBindingAction.EditPrevMessage:
// selection must be collapsed and caret at start // selection must be collapsed and caret at start
if (this.editorRef.current?.isSelectionCollapsed() && this.editorRef.current?.isCaretAtStart()) { if (this.editorRef.current?.isSelectionCollapsed() && this.editorRef.current?.isCaretAtStart()) {
const events = const events =
@ -256,7 +257,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
} }
} }
break; break;
case MessageComposerAction.CancelEditing: case KeyBindingAction.CancelReplyOrEdit:
dis.dispatch({ dis.dispatch({
action: 'reply_to_event', action: 'reply_to_event',
event: null, event: null,

View file

@ -37,11 +37,12 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext";
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton"; import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState"; import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
import { NotificationColor } from "../../../stores/notifications/NotificationColor"; import { NotificationColor } from "../../../stores/notifications/NotificationColor";
import { getKeyBindingsManager, RoomListAction } from "../../../KeyBindingsManager"; import { getKeyBindingsManager } from "../../../KeyBindingsManager";
import { NotificationState } from "../../../stores/notifications/NotificationState"; import { NotificationState } from "../../../stores/notifications/NotificationState";
import SpaceContextMenu from "../context_menus/SpaceContextMenu"; import SpaceContextMenu from "../context_menus/SpaceContextMenu";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { useRovingTabIndex } from "../../../accessibility/RovingTabIndex"; import { useRovingTabIndex } from "../../../accessibility/RovingTabIndex";
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
interface IButtonProps extends Omit<ComponentProps<typeof AccessibleTooltipButton>, "title" | "onClick"> { interface IButtonProps extends Omit<ComponentProps<typeof AccessibleTooltipButton>, "title" | "onClick"> {
space?: Room; space?: Room;
@ -234,7 +235,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
const action = getKeyBindingsManager().getRoomListAction(ev); const action = getKeyBindingsManager().getRoomListAction(ev);
const hasChildren = this.state.childSpaces?.length; const hasChildren = this.state.childSpaces?.length;
switch (action) { switch (action) {
case RoomListAction.CollapseSection: case KeyBindingAction.CollapseRoomListSection:
if (hasChildren && !this.isCollapsed) { if (hasChildren && !this.isCollapsed) {
this.toggleCollapse(ev); this.toggleCollapse(ev);
} else { } else {
@ -244,7 +245,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
} }
break; break;
case RoomListAction.ExpandSection: case KeyBindingAction.ExpandRoomListSection:
if (hasChildren) { if (hasChildren) {
if (this.isCollapsed) { if (this.isCollapsed) {
this.toggleCollapse(ev); this.toggleCollapse(ev);

View file

@ -3397,7 +3397,6 @@
"Dismiss read marker and jump to bottom": "Dismiss read marker and jump to bottom", "Dismiss read marker and jump to bottom": "Dismiss read marker and jump to bottom",
"Jump to oldest unread message": "Jump to oldest unread message", "Jump to oldest unread message": "Jump to oldest unread message",
"Upload a file": "Upload a file", "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 up in the timeline": "Scroll up in the timeline",
"Scroll down in the timeline": "Scroll down in the timeline", "Scroll down in the timeline": "Scroll down in the timeline",
"Jump to room search": "Jump to room search", "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 up in the room list": "Navigate up in the room list",
"Navigate down in the room list": "Navigate down 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", "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", "Toggle right panel": "Toggle right panel",
"Open this settings tab": "Open this settings tab", "Open this settings tab": "Open this settings tab",
"Go to Home View": "Go to Home View", "Go to Home View": "Go to Home View",
@ -3425,7 +3422,10 @@
"Jump to first message": "Jump to first message", "Jump to first message": "Jump to first message",
"Jump to last message": "Jump to last message", "Jump to last message": "Jump to last message",
"Undo edit": "Undo edit", "Undo edit": "Undo edit",
"Force complete": "Force complete",
"New line": "New line", "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" "Redo edit": "Redo edit"
} }

View file

@ -123,7 +123,7 @@ describe('KeyBindingsManager', () => {
it('should match ctrlOrMeta key combo', () => { it('should match ctrlOrMeta key combo', () => {
const combo: KeyCombo = { const combo: KeyCombo = {
key: 'k', key: 'k',
ctrlOrCmd: true, ctrlOrCmdKey: true,
}; };
// PC: // PC:
expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true }), combo, false)).toBe(true); expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true }), combo, false)).toBe(true);
@ -138,7 +138,7 @@ describe('KeyBindingsManager', () => {
it('should match advanced ctrlOrMeta key combo', () => { it('should match advanced ctrlOrMeta key combo', () => {
const combo: KeyCombo = { const combo: KeyCombo = {
key: 'k', key: 'k',
ctrlOrCmd: true, ctrlOrCmdKey: true,
altKey: true, altKey: true,
}; };
// PC: // PC: