Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.
Merged
6 changes: 6 additions & 0 deletions src/BasePlatform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,12 @@ export default abstract class BasePlatform {
return false;
}

public overrideBrowserShortcuts(): boolean {
return false;
}

public navigateForwardBack(back: boolean): void {}

getAvailableSpellCheckLanguages(): Promise<string[]> | null {
return null;
}
Expand Down
7 changes: 6 additions & 1 deletion src/KeyBindingsDefaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ const navigationBindings = (): KeyBinding[] => {
const bindings = getBindingsByCategory(CategoryName.NAVIGATION);

bindings.push({
action: "KeyBinding.closeDialogOrContextMenu" as KeyBindingAction,
action: KeyBindingAction.CloseDialogOrContextMenu,
keyCombo: {
key: Key.ESCAPE,
},
Expand All @@ -161,6 +161,10 @@ const navigationBindings = (): KeyBinding[] => {
return bindings;
};

const callBindings = (): KeyBinding[] => {
return getBindingsByCategory(CategoryName.CALLS);
};

const labsBindings = (): KeyBinding[] => {
if (!SdkConfig.get()['showLabsSettings']) return [];

Expand All @@ -173,5 +177,6 @@ export const defaultBindingsProvider: IKeyBindingsProvider = {
getRoomListBindings: roomListBindings,
getRoomBindings: roomBindings,
getNavigationBindings: navigationBindings,
getCallBindings: callBindings,
getLabsBindings: labsBindings,
};
4 changes: 4 additions & 0 deletions src/KeyBindingsManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,10 @@ export class KeyBindingsManager {
return this.getAction(this.bindingsProviders.map(it => it.getNavigationBindings), ev);
}

getCallAction(ev: KeyboardEvent | React.KeyboardEvent): KeyBindingAction | undefined {
return this.getAction(this.bindingsProviders.map(it => it.getCallBindings), ev);
}

getLabsAction(ev: KeyboardEvent | React.KeyboardEvent): KeyBindingAction | undefined {
return this.getAction(this.bindingsProviders.map(it => it.getLabsBindings), ev);
}
Expand Down
147 changes: 114 additions & 33 deletions src/accessibility/KeyboardShortcuts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { isMac, Key } from "../Keyboard";
import { IBaseSetting } from "../settings/Settings";
import SettingsStore from "../settings/SettingsStore";
import IncompatibleController from "../settings/controllers/IncompatibleController";
import PlatformPeg from "../PlatformPeg";

export enum KeyBindingAction {
/** Send a message */
Expand Down Expand Up @@ -115,6 +116,25 @@ export enum KeyBindingAction {
/** Select next room with unread messages */
SelectNextUnreadRoom = 'KeyBinding.nextUnreadRoom',

/** Switches to a space by number */
SwitchToSpaceByNumber = "KeyBinding.switchToSpaceByNumber",
/** Opens user settings */
OpenUserSettings = "KeyBinding.openUserSettings",
/** Navigates backward */
PreviousVisitedRoomOrCommunity = "KeyBinding.previousVisitedRoomOrCommunity",
/** Navigates forward */
NextVisitedRoomOrCommunity = "KeyBinding.nextVisitedRoomOrCommunity",

/** Toggles microphone while on a call */
ToggleMicInCall = "KeyBinding.toggleMicInCall",
/** Toggles webcam while on a call */
ToggleWebcamInCall = "KeyBinding.toggleWebcamInCall",

/** Closes a dialog or a context menu */
CloseDialogOrContextMenu = "KeyBinding.closeDialogOrContextMenu",
/** Clicks the selected button */
ActivateSelectedButton = "KeyBinding.activateSelectedButton",

/** Toggle visibility of hidden events */
ToggleHiddenEventVisibility = 'KeyBinding.toggleHiddenEventVisibility',
}
Expand All @@ -132,13 +152,13 @@ type KeyboardShortcutSetting = IBaseSetting<KeyBindingConfig>;

type IKeyboardShortcuts = {
// TODO: We should figure out what to do with the keyboard shortcuts that are not handled by KeybindingManager
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this TODO now done?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, not really, hopefully the next PR will cover that

[k in (KeyBindingAction | string)]: KeyboardShortcutSetting;
[k in (KeyBindingAction)]?: KeyboardShortcutSetting;
};

export interface ICategory {
categoryLabel: string;
// TODO: We should figure out what to do with the keyboard shortcuts that are not handled by KeybindingManager
settingNames: (KeyBindingAction | string)[];
settingNames: (KeyBindingAction)[];
}

export enum CategoryName {
Expand Down Expand Up @@ -200,8 +220,8 @@ export const CATEGORIES: Record<CategoryName, ICategory> = {
}, [CategoryName.CALLS]: {
categoryLabel: _td("Calls"),
settingNames: [
"KeyBinding.toggleMicInCall",
"KeyBinding.toggleWebcamInCall",
KeyBindingAction.ToggleMicInCall,
KeyBindingAction.ToggleWebcamInCall,
],
}, [CategoryName.ROOM]: {
categoryLabel: _td("Room"),
Expand Down Expand Up @@ -229,8 +249,8 @@ export const CATEGORIES: Record<CategoryName, ICategory> = {
categoryLabel: _td("Navigation"),
settingNames: [
KeyBindingAction.ToggleUserMenu,
"KeyBinding.closeDialogOrContextMenu",
"KeyBinding.activateSelectedButton",
KeyBindingAction.CloseDialogOrContextMenu,
KeyBindingAction.ActivateSelectedButton,
KeyBindingAction.ToggleRoomSidePanel,
KeyBindingAction.ToggleSpacePanel,
KeyBindingAction.ShowKeyboardSettings,
Expand All @@ -240,6 +260,10 @@ export const CATEGORIES: Record<CategoryName, ICategory> = {
KeyBindingAction.SelectPrevUnreadRoom,
KeyBindingAction.SelectNextRoom,
KeyBindingAction.SelectPrevRoom,
KeyBindingAction.OpenUserSettings,
KeyBindingAction.SwitchToSpaceByNumber,
KeyBindingAction.PreviousVisitedRoomOrCommunity,
KeyBindingAction.NextVisitedRoomOrCommunity,
],
}, [CategoryName.AUTOCOMPLETE]: {
categoryLabel: _td("Autocomplete"),
Expand All @@ -258,6 +282,17 @@ export const CATEGORIES: Record<CategoryName, ICategory> = {
},
};

const DESKTOP_SHORTCUTS = [
KeyBindingAction.OpenUserSettings,
KeyBindingAction.SwitchToSpaceByNumber,
KeyBindingAction.PreviousVisitedRoomOrCommunity,
KeyBindingAction.NextVisitedRoomOrCommunity,
];

const MAC_ONLY_SHORTCUTS = [
KeyBindingAction.OpenUserSettings,
];

// 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
Expand Down Expand Up @@ -332,14 +367,14 @@ export const KEYBOARD_SHORTCUTS: IKeyboardShortcuts = {
},
displayName: _td("Navigate to previous message in composer history"),
},
"KeyBinding.toggleMicInCall": {
[KeyBindingAction.ToggleMicInCall]: {
default: {
ctrlOrCmdKey: true,
key: Key.D,
},
displayName: _td("Toggle microphone mute"),
},
"KeyBinding.toggleWebcamInCall": {
[KeyBindingAction.ToggleWebcamInCall]: {
default: {
ctrlOrCmdKey: true,
key: Key.E,
Expand Down Expand Up @@ -538,13 +573,51 @@ export const KEYBOARD_SHORTCUTS: IKeyboardShortcuts = {
},
displayName: _td("Undo edit"),
},
[KeyBindingAction.EditRedo]: {
default: {
key: isMac ? Key.Z : Key.Y,
ctrlOrCmdKey: true,
shiftKey: isMac,
},
displayName: _td("Redo edit"),
},
[KeyBindingAction.PreviousVisitedRoomOrCommunity]: {
default: {
metaKey: isMac,
altKey: !isMac,
key: isMac ? Key.SQUARE_BRACKET_LEFT : Key.ARROW_LEFT,
},
displayName: _td("Previous recently visited room or community"),
},
[KeyBindingAction.NextVisitedRoomOrCommunity]: {
default: {
metaKey: isMac,
altKey: !isMac,
key: isMac ? Key.SQUARE_BRACKET_RIGHT : Key.ARROW_RIGHT,
},
displayName: _td("Next recently visited room or community"),
},
[KeyBindingAction.SwitchToSpaceByNumber]: {
default: {
ctrlOrCmdKey: true,
key: DIGITS,
},
displayName: _td("Switch to space by number"),
},
[KeyBindingAction.OpenUserSettings]: {
default: {
metaKey: true,
key: Key.COMMA,
},
displayName: _td("Open user settings"),
},
};

// XXX: These have to be manually mirrored in KeyBindingDefaults
const getNonCustomizableShortcuts = (): IKeyboardShortcuts => {
const ctrlEnterToSend = SettingsStore.getValue('MessageComposerInput.ctrlEnterToSend');

return {
const keyboardShortcuts: IKeyboardShortcuts = {
[KeyBindingAction.SendMessage]: {
default: {
key: Key.ENTER,
Expand Down Expand Up @@ -578,39 +651,46 @@ const getNonCustomizableShortcuts = (): IKeyboardShortcuts => {
},
displayName: _td("Search (must be enabled)"),
},
"KeyBinding.closeDialogOrContextMenu": {
[KeyBindingAction.CloseDialogOrContextMenu]: {
default: {
key: Key.ESCAPE,
},
displayName: _td("Close dialog or context menu"),
},
"KeyBinding.activateSelectedButton": {
[KeyBindingAction.ActivateSelectedButton]: {
default: {
key: Key.ENTER,
},
displayName: _td("Activate selected button"),
},
};

if (PlatformPeg.get().overrideBrowserShortcuts()) {
keyboardShortcuts[KeyBindingAction.SwitchToSpaceByNumber] = {
default: {
ctrlOrCmdKey: true,
key: DIGITS,
},
displayName: _td("Switch to space by number"),
};
}

return keyboardShortcuts;
};

export const getCustomizableShortcuts = (): IKeyboardShortcuts => {
const keyboardShortcuts = Object.assign({}, KEYBOARD_SHORTCUTS);
const overrideBrowserShortcuts = PlatformPeg.get().overrideBrowserShortcuts();

keyboardShortcuts[KeyBindingAction.EditRedo] = {
default: {
key: isMac ? Key.Z : Key.Y,
ctrlOrCmdKey: true,
shiftKey: isMac,
},
displayName: _td("Redo edit"),
};
return Object.keys(KEYBOARD_SHORTCUTS).filter((k: KeyBindingAction) => {
if (KEYBOARD_SHORTCUTS[k]?.controller?.settingDisabled) return false;
if (MAC_ONLY_SHORTCUTS.includes(k) && !isMac) return false;
if (DESKTOP_SHORTCUTS.includes(k) && !overrideBrowserShortcuts) return false;

return Object.keys(keyboardShortcuts).filter(k => {
return !keyboardShortcuts[k].controller?.settingDisabled;
return true;
}).reduce((o, key) => {
o[key] = keyboardShortcuts[key];
o[key] = KEYBOARD_SHORTCUTS[key];
return o;
}, {});
}, {} as IKeyboardShortcuts);
};

export const getKeyboardShortcuts = (): IKeyboardShortcuts => {
Expand All @@ -622,14 +702,15 @@ export const getKeyboardShortcuts = (): IKeyboardShortcuts => {
return entries.reduce((acc, [key, value]) => {
acc[key] = value;
return acc;
}, {});
}, {} as IKeyboardShortcuts);
};

export const registerShortcut = (
shortcutName: string,
categoryName: CategoryName,
shortcut: KeyboardShortcutSetting,
): void => {
KEYBOARD_SHORTCUTS[shortcutName] = shortcut;
CATEGORIES[categoryName].settingNames.push(shortcutName);
};
// For tests
export function mock({ keyboardShortcuts, macOnlyShortcuts, desktopShortcuts }): void {
Object.keys(KEYBOARD_SHORTCUTS).forEach((k) => delete KEYBOARD_SHORTCUTS[k]);
if (keyboardShortcuts) Object.assign(KEYBOARD_SHORTCUTS, keyboardShortcuts);
MAC_ONLY_SHORTCUTS.splice(0, MAC_ONLY_SHORTCUTS.length);
if (macOnlyShortcuts) macOnlyShortcuts.forEach((e) => MAC_ONLY_SHORTCUTS.push(e));
DESKTOP_SHORTCUTS.splice(0, DESKTOP_SHORTCUTS.length);
if (desktopShortcuts) desktopShortcuts.forEach((e) => DESKTOP_SHORTCUTS.push(e));
}
32 changes: 25 additions & 7 deletions src/components/structures/LoggedInView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { ISyncStateData, SyncState } from 'matrix-js-sdk/src/sync';
import { IUsageLimit } from 'matrix-js-sdk/src/@types/partials';
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";

import { Key } from '../../Keyboard';
import { isOnlyCtrlOrCmdKeyEvent, Key } from '../../Keyboard';
import PageTypes from '../../PageTypes';
import MediaDeviceHandler from '../../MediaDeviceHandler';
import { fixupColorFonts } from '../../utils/FontManager';
Expand Down Expand Up @@ -73,6 +73,7 @@ import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
import RightPanelStore from '../../stores/right-panel/RightPanelStore';
import { TimelineRenderingType } from "../../contexts/RoomContext";
import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
import { SwitchSpacePayload } from "../../dispatcher/payloads/SwitchSpacePayload";

// 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.
Expand Down Expand Up @@ -535,9 +536,14 @@ class LoggedInView extends React.Component<IProps, IState> {
unread: true,
});
break;
default:
// if we do not have a handler for it, pass it to the platform which might
handled = PlatformPeg.get().onKeyDown(ev);
case KeyBindingAction.PreviousVisitedRoomOrCommunity:
PlatformPeg.get().navigateForwardBack(true);
handled = true;
break;
case KeyBindingAction.NextVisitedRoomOrCommunity:
PlatformPeg.get().navigateForwardBack(false);
handled = true;
break;
}

// Handle labs actions here, as they apply within the same scope
Expand All @@ -560,12 +566,24 @@ class LoggedInView extends React.Component<IProps, IState> {
handled = true;
break;
}
default:
// if we do not have a handler for it, pass it to the platform which might
handled = PlatformPeg.get().onKeyDown(ev);
}
}

if (
!handled &&
PlatformPeg.get().overrideBrowserShortcuts() &&
SpaceStore.spacesEnabled &&
ev.code.startsWith("Digit") &&
ev.code !== "Digit0" && // this is the shortcut for reset zoom, don't override it
isOnlyCtrlOrCmdKeyEvent(ev)
) {
dis.dispatch<SwitchSpacePayload>({
action: Action.SwitchSpace,
num: ev.code.slice(5), // Cut off the first 5 characters - "Digit"
});
handled = true;
}

if (handled) {
ev.stopPropagation();
ev.preventDefault();
Expand Down
Loading