Merge pull request #5815 from czeidler/keybindings-follow-up

Keybindings follow up
pull/21833/head
J. Ryan Stinnett 2021-03-31 17:54:05 +01:00 committed by GitHub
commit 7c4437ac9b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 58 additions and 57 deletions

View File

@ -161,27 +161,27 @@ const messageComposerBindings = (): KeyBinding<MessageComposerAction>[] => {
const autocompleteBindings = (): KeyBinding<AutocompleteAction>[] => { const autocompleteBindings = (): KeyBinding<AutocompleteAction>[] => {
return [ return [
{ {
action: AutocompleteAction.ApplySelection, action: AutocompleteAction.CompleteOrNextSelection,
keyCombo: { keyCombo: {
key: Key.TAB, key: Key.TAB,
}, },
}, },
{ {
action: AutocompleteAction.ApplySelection, action: AutocompleteAction.CompleteOrNextSelection,
keyCombo: { keyCombo: {
key: Key.TAB, key: Key.TAB,
ctrlKey: true, ctrlKey: true,
}, },
}, },
{ {
action: AutocompleteAction.ApplySelection, action: AutocompleteAction.CompleteOrPrevSelection,
keyCombo: { keyCombo: {
key: Key.TAB, key: Key.TAB,
shiftKey: true, shiftKey: true,
}, },
}, },
{ {
action: AutocompleteAction.ApplySelection, action: AutocompleteAction.CompleteOrPrevSelection,
keyCombo: { keyCombo: {
key: Key.TAB, key: Key.TAB,
ctrlKey: true, ctrlKey: true,

View File

@ -52,14 +52,19 @@ export enum MessageComposerAction {
/** Actions for text editing autocompletion */ /** Actions for text editing autocompletion */
export enum AutocompleteAction { export enum AutocompleteAction {
/** Apply the current autocomplete selection */ /**
ApplySelection = 'ApplySelection', * Select previous selection or, if the autocompletion window is not shown, open the window and select the first
/** Cancel autocompletion */ * selection.
Cancel = 'Cancel', */
CompleteOrPrevSelection = 'ApplySelection',
/** Select next selection or, if the autocompletion window is not shown, open it and select the first selection */
CompleteOrNextSelection = 'CompleteOrNextSelection',
/** Move to the previous autocomplete selection */ /** Move to the previous autocomplete selection */
PrevSelection = 'PrevSelection', PrevSelection = 'PrevSelection',
/** Move to the next autocomplete selection */ /** Move to the next autocomplete selection */
NextSelection = 'NextSelection', NextSelection = 'NextSelection',
/** Close the autocompletion window */
Cancel = 'Cancel',
} }
/** Actions for the room list sidebar */ /** Actions for the room list sidebar */

View File

@ -34,7 +34,6 @@ import { UPDATE_EVENT } from "../../stores/AsyncStore";
import ResizeNotifier from "../../utils/ResizeNotifier"; import ResizeNotifier from "../../utils/ResizeNotifier";
import SettingsStore from "../../settings/SettingsStore"; import SettingsStore from "../../settings/SettingsStore";
import RoomListStore, { LISTS_UPDATE_EVENT } from "../../stores/room-list/RoomListStore"; import RoomListStore, { LISTS_UPDATE_EVENT } from "../../stores/room-list/RoomListStore";
import {Key} from "../../Keyboard";
import IndicatorScrollbar from "../structures/IndicatorScrollbar"; import IndicatorScrollbar from "../structures/IndicatorScrollbar";
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton"; import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
import { OwnProfileStore } from "../../stores/OwnProfileStore"; import { OwnProfileStore } from "../../stores/OwnProfileStore";
@ -43,6 +42,7 @@ import LeftPanelWidget from "./LeftPanelWidget";
import {replaceableComponent} from "../../utils/replaceableComponent"; import {replaceableComponent} from "../../utils/replaceableComponent";
import {mediaFromMxc} from "../../customisations/Media"; import {mediaFromMxc} from "../../customisations/Media";
import SpaceStore, {UPDATE_SELECTED_SPACE} from "../../stores/SpaceStore"; import SpaceStore, {UPDATE_SELECTED_SPACE} from "../../stores/SpaceStore";
import { getKeyBindingsManager, RoomListAction } from "../../KeyBindingsManager";
interface IProps { interface IProps {
isMinimized: boolean; isMinimized: boolean;
@ -297,17 +297,18 @@ export default class LeftPanel extends React.Component<IProps, IState> {
private onKeyDown = (ev: React.KeyboardEvent) => { private onKeyDown = (ev: React.KeyboardEvent) => {
if (!this.focusedElement) return; if (!this.focusedElement) return;
switch (ev.key) { const action = getKeyBindingsManager().getRoomListAction(ev);
case Key.ARROW_UP: switch (action) {
case Key.ARROW_DOWN: case RoomListAction.NextRoom:
case RoomListAction.PrevRoom:
ev.stopPropagation(); ev.stopPropagation();
ev.preventDefault(); ev.preventDefault();
this.onMoveFocus(ev.key === Key.ARROW_UP); this.onMoveFocus(action === RoomListAction.PrevRoom);
break; break;
} }
}; };
private onEnter = () => { private selectRoom = () => {
const firstRoom = this.listContainerRef.current.querySelector<HTMLDivElement>(".mx_RoomTile"); const firstRoom = this.listContainerRef.current.querySelector<HTMLDivElement>(".mx_RoomTile");
if (firstRoom) { if (firstRoom) {
firstRoom.click(); firstRoom.click();
@ -388,8 +389,8 @@ export default class LeftPanel extends React.Component<IProps, IState> {
> >
<RoomSearch <RoomSearch
isMinimized={this.props.isMinimized} isMinimized={this.props.isMinimized}
onVerticalArrow={this.onKeyDown} onKeyDown={this.onKeyDown}
onEnter={this.onEnter} onSelectRoom={this.selectRoom}
/> />
<AccessibleTooltipButton <AccessibleTooltipButton
className={classNames("mx_LeftPanel_exploreButton", { className={classNames("mx_LeftPanel_exploreButton", {

View File

@ -444,6 +444,7 @@ class LoggedInView extends React.Component<IProps, IState> {
case RoomAction.RoomScrollDown: case RoomAction.RoomScrollDown:
case RoomAction.JumpToFirstMessage: case RoomAction.JumpToFirstMessage:
case RoomAction.JumpToLatestMessage: case RoomAction.JumpToLatestMessage:
// pass the event down to the scroll panel
this._onScrollKeyPressed(ev); this._onScrollKeyPressed(ev);
handled = true; handled = true;
break; break;

View File

@ -30,8 +30,11 @@ import SpaceStore, {UPDATE_SELECTED_SPACE} from "../../stores/SpaceStore";
interface IProps { interface IProps {
isMinimized: boolean; isMinimized: boolean;
onVerticalArrow(ev: React.KeyboardEvent): void; onKeyDown(ev: React.KeyboardEvent): void;
onEnter(ev: React.KeyboardEvent): boolean; /**
* @returns true if a room has been selected and the search field should be cleared
*/
onSelectRoom(): boolean;
} }
interface IState { interface IState {
@ -120,10 +123,11 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
break; break;
case RoomListAction.NextRoom: case RoomListAction.NextRoom:
case RoomListAction.PrevRoom: case RoomListAction.PrevRoom:
this.props.onVerticalArrow(ev); // we don't handle these actions here put pass the event on to the interested party (LeftPanel)
this.props.onKeyDown(ev);
break; break;
case RoomListAction.SelectRoom: { case RoomListAction.SelectRoom: {
const shouldClear = this.props.onEnter(ev); 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
setImmediate(() => { setImmediate(() => {

View File

@ -16,10 +16,10 @@ limitations under the License.
import React, {createRef} from "react"; import React, {createRef} from "react";
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Key } from '../../Keyboard';
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";
const DEBUG_SCROLL = false; const DEBUG_SCROLL = false;
@ -535,29 +535,19 @@ export default class ScrollPanel extends React.Component {
* @param {object} ev the keyboard event * @param {object} ev the keyboard event
*/ */
handleScrollKey = ev => { handleScrollKey = ev => {
switch (ev.key) { const roomAction = getKeyBindingsManager().getRoomAction(ev);
case Key.PAGE_UP: switch (roomAction) {
if (!ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) { case RoomAction.ScrollUp:
this.scrollRelative(-1); this.scrollRelative(-1);
}
break; break;
case RoomAction.RoomScrollDown:
case Key.PAGE_DOWN:
if (!ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) {
this.scrollRelative(1); this.scrollRelative(1);
}
break; break;
case RoomAction.JumpToFirstMessage:
case Key.HOME:
if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) {
this.scrollToTop(); this.scrollToTop();
}
break; break;
case RoomAction.JumpToLatestMessage:
case Key.END:
if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) {
this.scrollToBottom(); this.scrollToBottom();
}
break; break;
} }
}; };

View File

@ -485,16 +485,14 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
if (model.autoComplete && model.autoComplete.hasCompletions()) { if (model.autoComplete && model.autoComplete.hasCompletions()) {
const autoComplete = model.autoComplete; const autoComplete = model.autoComplete;
switch (autocompleteAction) { switch (autocompleteAction) {
case AutocompleteAction.CompleteOrPrevSelection:
case AutocompleteAction.PrevSelection: case AutocompleteAction.PrevSelection:
autoComplete.onUpArrow(event); autoComplete.selectPreviousSelection();
handled = true; handled = true;
break; break;
case AutocompleteAction.CompleteOrNextSelection:
case AutocompleteAction.NextSelection: case AutocompleteAction.NextSelection:
autoComplete.onDownArrow(event); autoComplete.selectNextSelection();
handled = true;
break;
case AutocompleteAction.ApplySelection:
autoComplete.onTab(event);
handled = true; handled = true;
break; break;
case AutocompleteAction.Cancel: case AutocompleteAction.Cancel:
@ -504,8 +502,10 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
default: default:
return; // don't preventDefault on anything else return; // don't preventDefault on anything else
} }
} else if (autocompleteAction === AutocompleteAction.ApplySelection) { } else if (autocompleteAction === AutocompleteAction.CompleteOrPrevSelection
this.tabCompleteName(event); || autocompleteAction === AutocompleteAction.CompleteOrNextSelection) {
// there is no current autocomplete window, try to open it
this.tabCompleteName();
handled = true; handled = true;
} else if (event.key === Key.BACKSPACE || event.key === Key.DELETE) { } else if (event.key === Key.BACKSPACE || event.key === Key.DELETE) {
this.formatBarRef.current.hide(); this.formatBarRef.current.hide();
@ -517,7 +517,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
} }
}; };
private async tabCompleteName(event: React.KeyboardEvent) { private async tabCompleteName() {
try { try {
await new Promise<void>(resolve => this.setState({showVisualBell: false}, resolve)); await new Promise<void>(resolve => this.setState({showVisualBell: false}, resolve));
const {model} = this.props; const {model} = this.props;
@ -540,7 +540,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
// Don't try to do things with the autocomplete if there is none shown // Don't try to do things with the autocomplete if there is none shown
if (model.autoComplete) { if (model.autoComplete) {
await model.autoComplete.onTab(event); await model.autoComplete.startSelection();
if (!model.autoComplete.hasSelection()) { if (!model.autoComplete.hasSelection()) {
this.setState({showVisualBell: true}); this.setState({showVisualBell: true});
model.autoComplete.close(); model.autoComplete.close();

View File

@ -68,24 +68,24 @@ export default class AutocompleteWrapperModel {
this.updateCallback({close: true}); this.updateCallback({close: true});
} }
public async onTab(e: KeyboardEvent) { /**
* If there is no current autocompletion, start one and move to the first selection.
*/
public async startSelection() {
const acComponent = this.getAutocompleterComponent(); const acComponent = this.getAutocompleterComponent();
if (acComponent.countCompletions() === 0) { if (acComponent.countCompletions() === 0) {
// Force completions to show for the text currently entered // Force completions to show for the text currently entered
await acComponent.forceComplete(); await acComponent.forceComplete();
// Select the first item by moving "down" // Select the first item by moving "down"
await acComponent.moveSelection(+1); await acComponent.moveSelection(+1);
} else {
await acComponent.moveSelection(e.shiftKey ? -1 : +1);
} }
} }
public onUpArrow(e: KeyboardEvent) { public selectPreviousSelection() {
this.getAutocompleterComponent().moveSelection(-1); this.getAutocompleterComponent().moveSelection(-1);
} }
public onDownArrow(e: KeyboardEvent) { public selectNextSelection() {
this.getAutocompleterComponent().moveSelection(+1); this.getAutocompleterComponent().moveSelection(+1);
} }