Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Accessibility: Add Keyboard Navigation for Messages #12328

Closed
wants to merge 16 commits into from
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/accessibility/KeyboardShortcuts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ export enum KeyBindingAction {
SelectPrevUnreadRoom = "KeyBinding.previousUnreadRoom",
/** Select next room with unread messages */
SelectNextUnreadRoom = "KeyBinding.nextUnreadRoom",
/** Select prev message */
SelectPrevMessage = "KeyBinding.previousMessage",
/** Select next message */
SelectNextMessage = "KeyBinding.nextMessage",

/** Switches to a space by number */
SwitchToSpaceByNumber = "KeyBinding.switchToSpaceByNumber",
Expand Down Expand Up @@ -286,6 +290,8 @@ export const CATEGORIES: Record<CategoryName, ICategory> = {
KeyBindingAction.SelectPrevUnreadRoom,
KeyBindingAction.SelectNextRoom,
KeyBindingAction.SelectPrevRoom,
KeyBindingAction.SelectNextMessage,
KeyBindingAction.SelectPrevMessage,
KeyBindingAction.OpenUserSettings,
KeyBindingAction.SwitchToSpaceByNumber,
KeyBindingAction.PreviousVisitedRoomOrSpace,
Expand Down Expand Up @@ -558,6 +564,20 @@ export const KEYBOARD_SHORTCUTS: IKeyboardShortcuts = {
},
displayName: _td("keyboard|prev_room"),
},
[KeyBindingAction.SelectNextMessage]: {
default: {
ctrlOrCmdKey: true,
key: Key.ARROW_DOWN,
},
displayName: _td("keyboard|next_message"),
},
[KeyBindingAction.SelectPrevMessage]: {
default: {
ctrlOrCmdKey: true,
key: Key.ARROW_UP,
},
displayName: _td("keyboard|prev_message"),
},
[KeyBindingAction.CancelAutocomplete]: {
default: {
key: Key.ESCAPE,
Expand Down
9 changes: 9 additions & 0 deletions src/components/structures/LoggedInView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,8 @@ class LoggedInView extends React.Component<IProps, IState> {

const roomAction = getKeyBindingsManager().getRoomAction(ev);
switch (roomAction) {
case KeyBindingAction.SelectPrevMessage:
case KeyBindingAction.SelectNextMessage:
case KeyBindingAction.ScrollUp:
case KeyBindingAction.ScrollDown:
case KeyBindingAction.JumpToFirstMessage:
Expand All @@ -472,6 +474,13 @@ class LoggedInView extends React.Component<IProps, IState> {

const navAction = getKeyBindingsManager().getNavigationAction(ev);
switch (navAction) {
case KeyBindingAction.SelectPrevMessage:
case KeyBindingAction.SelectNextMessage:
// pass the event down to the scroll panel
this.onScrollKeyPressed(ev);
handled = true;
break;

case KeyBindingAction.FilterRooms:
dis.dispatch({
action: "focus_room_filter",
Expand Down
49 changes: 49 additions & 0 deletions src/components/structures/MessagePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ import { MainGrouper } from "./grouper/MainGrouper";
import { CreationGrouper } from "./grouper/CreationGrouper";
import { _t } from "../../languageHandler";
import { getLateEventInfo } from "./grouper/LateEventGrouper";
import { getKeyBindingsManager } from "../../KeyBindingsManager";
import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";

const CONTINUATION_MAX_INTERVAL = 5 * 60 * 1000; // 5 minutes
const continuedTypes = [EventType.Sticker, EventType.RoomMessage];
Expand Down Expand Up @@ -205,6 +207,7 @@ interface IReadReceiptForUser {
*/
export default class MessagePanel extends React.Component<IProps, IState> {
public static contextType = RoomContext;
public focusedEventId?: string;
public context!: React.ContextType<typeof RoomContext>;

public static defaultProps = {
Expand Down Expand Up @@ -422,6 +425,52 @@ export default class MessagePanel extends React.Component<IProps, IState> {
* @param {KeyboardEvent} ev: the keyboard event to handle
*/
public handleScrollKey(ev: React.KeyboardEvent | KeyboardEvent): void {
const navAction = getKeyBindingsManager().getNavigationAction(ev);
if (navAction === KeyBindingAction.SelectPrevMessage || navAction === KeyBindingAction.SelectNextMessage) {
const events: WrappedEvent[] = this.props.events.map((event) => {
return { event, shouldShow: this.shouldShowEvent(event) };
});
const currentEventId =
this.focusedEventId ||
this.props.highlightedEventId ||
events
.reduce(
// Get the id of the last event in the list that's shown.
(prev: WrappedEvent, wrappedEvent: WrappedEvent): WrappedEvent =>
wrappedEvent?.shouldShow ? wrappedEvent : prev,
)
?.event?.getId();
if (navAction === KeyBindingAction.SelectPrevMessage) {
events.reverse();
}
let previousEventId = null;
for (let i = events.length - 1; i >= 0; i--) {
const { event, shouldShow } = events[i];
if (!shouldShow) {
continue;
}
const eventId = event.getId()!;
if (previousEventId && eventId === currentEventId) {
document
.querySelector<HTMLElement>('.mx_EventTile[data-event-id="' + previousEventId + '"]')
?.focus();
this.focusedEventId = previousEventId;
ev.preventDefault();
return;
}
previousEventId = eventId;
}
if (navAction === KeyBindingAction.SelectNextMessage) {
defaultDispatcher.dispatch(
{
action: Action.FocusSendMessageComposer,
context: TimelineRenderingType.Room,
},
true,
);
}
}

this.scrollPanel.current?.handleScrollKey(ev);
}

Expand Down
12 changes: 12 additions & 0 deletions src/components/views/rooms/SendMessageComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,18 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
return;
}
const replyingToThread = this.props.relation?.key === THREAD_RELATION_TYPE.name;
const navAction = getKeyBindingsManager().getNavigationAction(event);
switch (navAction) {
case KeyBindingAction.SelectPrevMessage:
(
document.querySelector<HTMLElement>(".mx_EventTile_selected") ||
document.querySelector<HTMLElement>(".mx_EventTile_last")
)?.focus();
event.preventDefault();
event.stopPropagation();
return;
}

const action = getKeyBindingsManager().getMessageComposerAction(event);
switch (action) {
case KeyBindingAction.SendMessage:
Expand Down
2 changes: 2 additions & 0 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -1351,12 +1351,14 @@
"navigate_next_message_edit": "Navigate to next message to edit",
"navigate_prev_history": "Previous recently visited room or space",
"navigate_prev_message_edit": "Navigate to previous message to edit",
"next_message": "Next message",
"next_room": "Next room or DM",
"next_unread_room": "Next unread room or DM",
"number": "[number]",
"open_user_settings": "Open user settings",
"page_down": "Page Down",
"page_up": "Page Up",
"prev_message": "Previous message",
"prev_room": "Previous room or DM",
"prev_unread_room": "Previous unread room or DM",
"room_list_collapse_section": "Collapse room list section",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -992,6 +992,46 @@ exports[`KeyboardUserSettingsTab renders list of keyboard shortcuts 1`] = `
</kbd>
</div>
</li>
<li
class="mx_KeyboardShortcut_shortcutRow"
>
Next message
<div
class="mx_KeyboardShortcut"
>
<kbd>

Ctrl

</kbd>
+
<kbd>


</kbd>
</div>
</li>
<li
class="mx_KeyboardShortcut_shortcutRow"
>
Previous message
<div
class="mx_KeyboardShortcut"
>
<kbd>

Ctrl

</kbd>
+
<kbd>


</kbd>
</div>
</li>
</ul>
</div>
</div>
Expand Down