From c4abe39375695bf0f643ec218fdd20e4e18f47bf Mon Sep 17 00:00:00 2001 From: Ajay Bura <32841439+ajbura@users.noreply.github.com> Date: Thu, 18 Jul 2024 18:50:20 +0530 Subject: [PATCH] Make hotkeys work again (#1819) --- src/app/components/Pdf-viewer/PdfViewer.tsx | 2 + src/app/components/UIAFlowOverlay.tsx | 3 +- src/app/components/editor/Editor.preview.tsx | 2 + src/app/components/editor/Toolbar.tsx | 2 + .../editor/autocomplete/AutocompleteMenu.tsx | 3 +- src/app/components/emoji-board/EmojiBoard.tsx | 3 +- .../leave-room-prompt/LeaveRoomPrompt.tsx | 2 + .../leave-space-prompt/LeaveSpacePrompt.tsx | 2 + .../message/content/FileContent.tsx | 3 + .../message/content/ImageContent.tsx | 2 + src/app/components/room-card/RoomCard.tsx | 4 +- src/app/features/lobby/HierarchyItemMenu.tsx | 2 + src/app/features/lobby/LobbyHeader.tsx | 2 + src/app/features/lobby/LobbyHero.tsx | 3 +- src/app/features/lobby/RoomItem.tsx | 3 +- src/app/features/lobby/SpaceItem.tsx | 3 + .../features/message-search/SearchFilters.tsx | 3 + src/app/features/room-nav/RoomNavItem.tsx | 2 + src/app/features/room/MembersDrawer.tsx | 3 + src/app/features/room/Room.tsx | 17 +++++- src/app/features/room/RoomView.tsx | 60 +++++++++++++++++-- src/app/features/room/RoomViewFollowing.tsx | 2 + src/app/features/room/RoomViewHeader.tsx | 3 + src/app/features/room/message/Message.tsx | 8 +++ src/app/features/room/message/Reactions.tsx | 2 + src/app/organisms/search/Search.jsx | 25 +++++++- src/app/pages/auth/ServerPicker.tsx | 2 + .../pages/auth/login/PasswordLoginForm.tsx | 2 + src/app/pages/client/ClientRoot.tsx | 2 - src/app/pages/client/direct/Direct.tsx | 2 + src/app/pages/client/explore/Explore.tsx | 2 + src/app/pages/client/explore/Server.tsx | 3 + src/app/pages/client/home/Home.tsx | 2 + src/app/pages/client/inbox/Invites.tsx | 3 +- src/app/pages/client/sidebar/DirectTab.tsx | 2 + src/app/pages/client/sidebar/HomeTab.tsx | 2 + src/app/pages/client/sidebar/SpaceTabs.tsx | 2 + src/app/pages/client/space/Space.tsx | 2 + src/app/utils/keyboard.ts | 5 ++ src/client/event/hotkeys.js | 24 -------- 40 files changed, 182 insertions(+), 39 deletions(-) delete mode 100644 src/client/event/hotkeys.js diff --git a/src/app/components/Pdf-viewer/PdfViewer.tsx b/src/app/components/Pdf-viewer/PdfViewer.tsx index a78c13f..9c7fd98 100644 --- a/src/app/components/Pdf-viewer/PdfViewer.tsx +++ b/src/app/components/Pdf-viewer/PdfViewer.tsx @@ -26,6 +26,7 @@ import * as css from './PdfViewer.css'; import { AsyncStatus } from '../../hooks/useAsyncCallback'; import { useZoom } from '../../hooks/useZoom'; import { createPage, usePdfDocumentLoader, usePdfJSLoader } from '../../plugins/pdfjs-dist'; +import { stopPropagation } from '../../utils/keyboard'; export type PdfViewerProps = { name: string; @@ -201,6 +202,7 @@ export const PdfViewer = as<'div', PdfViewerProps>( initialFocus: false, onDeactivate: () => setJumpAnchor(undefined), clickOutsideDeactivates: true, + escapeDeactivates: stopPropagation, }} > diff --git a/src/app/components/UIAFlowOverlay.tsx b/src/app/components/UIAFlowOverlay.tsx index f788eb0..dc517b4 100644 --- a/src/app/components/UIAFlowOverlay.tsx +++ b/src/app/components/UIAFlowOverlay.tsx @@ -13,6 +13,7 @@ import { IconButton, } from 'folds'; import FocusTrap from 'focus-trap-react'; +import { stopPropagation } from '../utils/keyboard'; export type UIAFlowOverlayProps = { currentStep: number; @@ -28,7 +29,7 @@ export function UIAFlowOverlay({ }: UIAFlowOverlayProps) { return ( }> - + {children} diff --git a/src/app/components/editor/Editor.preview.tsx b/src/app/components/editor/Editor.preview.tsx index ad67dc1..b760ddd 100644 --- a/src/app/components/editor/Editor.preview.tsx +++ b/src/app/components/editor/Editor.preview.tsx @@ -14,6 +14,7 @@ import { import { CustomEditor, useEditor } from './Editor'; import { Toolbar } from './Toolbar'; +import { stopPropagation } from '../../utils/keyboard'; export function EditorPreview() { const [open, setOpen] = useState(false); @@ -32,6 +33,7 @@ export function EditorPreview() { initialFocus: false, onDeactivate: () => setOpen(false), clickOutsideDeactivates: true, + escapeDeactivates: stopPropagation, }} > diff --git a/src/app/components/editor/Toolbar.tsx b/src/app/components/editor/Toolbar.tsx index 0c82855..e5c7d16 100644 --- a/src/app/components/editor/Toolbar.tsx +++ b/src/app/components/editor/Toolbar.tsx @@ -35,6 +35,7 @@ import { isMacOS } from '../../utils/user-agent'; import { KeySymbol } from '../../utils/key-symbol'; import { useSetting } from '../../state/hooks/settings'; import { settingsAtom } from '../../state/settings'; +import { stopPropagation } from '../../utils/keyboard'; function BtnTooltip({ text, shortCode }: { text: string; shortCode?: string }) { return ( @@ -151,6 +152,7 @@ export function HeadingBlockButton() { isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown' || evt.key === 'ArrowRight', isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp' || evt.key === 'ArrowLeft', + escapeDeactivates: stopPropagation, }} > diff --git a/src/app/components/editor/autocomplete/AutocompleteMenu.tsx b/src/app/components/editor/autocomplete/AutocompleteMenu.tsx index fc4327d..5d2d917 100644 --- a/src/app/components/editor/autocomplete/AutocompleteMenu.tsx +++ b/src/app/components/editor/autocomplete/AutocompleteMenu.tsx @@ -4,7 +4,7 @@ import { isKeyHotkey } from 'is-hotkey'; import { Header, Menu, Scroll, config } from 'folds'; import * as css from './AutocompleteMenu.css'; -import { preventScrollWithArrowKey } from '../../../utils/keyboard'; +import { preventScrollWithArrowKey, stopPropagation } from '../../../utils/keyboard'; type AutocompleteMenuProps = { requestClose: () => void; @@ -24,6 +24,7 @@ export function AutocompleteMenu({ headerContent, requestClose, children }: Auto allowOutsideClick: true, isKeyForward: (evt: KeyboardEvent) => isKeyHotkey('arrowdown', evt), isKeyBackward: (evt: KeyboardEvent) => isKeyHotkey('arrowup', evt), + escapeDeactivates: stopPropagation, }} > diff --git a/src/app/components/emoji-board/EmojiBoard.tsx b/src/app/components/emoji-board/EmojiBoard.tsx index 408ce85..53172ef 100644 --- a/src/app/components/emoji-board/EmojiBoard.tsx +++ b/src/app/components/emoji-board/EmojiBoard.tsx @@ -37,7 +37,7 @@ import * as css from './EmojiBoard.css'; import { EmojiGroupId, IEmoji, IEmojiGroup, emojiGroups, emojis } from '../../plugins/emoji'; import { IEmojiGroupLabels, useEmojiGroupLabels } from './useEmojiGroupLabels'; import { IEmojiGroupIcons, useEmojiGroupIcons } from './useEmojiGroupIcons'; -import { preventScrollWithArrowKey } from '../../utils/keyboard'; +import { preventScrollWithArrowKey, stopPropagation } from '../../utils/keyboard'; import { useRelevantImagePacks } from '../../hooks/useImagePacks'; import { useMatrixClient } from '../../hooks/useMatrixClient'; import { useRecentEmoji } from '../../hooks/useRecentEmoji'; @@ -775,6 +775,7 @@ export function EmojiBoard({ !editableActiveElement() && isKeyHotkey(['arrowdown', 'arrowright'], evt), isKeyBackward: (evt: KeyboardEvent) => !editableActiveElement() && isKeyHotkey(['arrowup', 'arrowleft'], evt), + escapeDeactivates: stopPropagation, }} > diff --git a/src/app/components/leave-space-prompt/LeaveSpacePrompt.tsx b/src/app/components/leave-space-prompt/LeaveSpacePrompt.tsx index 1132b44..8709b94 100644 --- a/src/app/components/leave-space-prompt/LeaveSpacePrompt.tsx +++ b/src/app/components/leave-space-prompt/LeaveSpacePrompt.tsx @@ -19,6 +19,7 @@ import { import { MatrixError } from 'matrix-js-sdk'; import { useMatrixClient } from '../../hooks/useMatrixClient'; import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback'; +import { stopPropagation } from '../../utils/keyboard'; type LeaveSpacePromptProps = { roomId: string; @@ -52,6 +53,7 @@ export function LeaveSpacePrompt({ roomId, onDone, onCancel }: LeaveSpacePromptP initialFocus: false, onDeactivate: onCancel, clickOutsideDeactivates: true, + escapeDeactivates: stopPropagation, }} > diff --git a/src/app/components/message/content/FileContent.tsx b/src/app/components/message/content/FileContent.tsx index af064a3..f09c1e0 100644 --- a/src/app/components/message/content/FileContent.tsx +++ b/src/app/components/message/content/FileContent.tsx @@ -29,6 +29,7 @@ import { mimeTypeToExt, } from '../../../utils/mimeTypes'; import * as css from './style.css'; +import { stopPropagation } from '../../../utils/keyboard'; const renderErrorButton = (retry: () => void, text: string) => ( setTextViewer(false), clickOutsideDeactivates: true, + escapeDeactivates: stopPropagation, }} > setPdfViewer(false), clickOutsideDeactivates: true, + escapeDeactivates: stopPropagation, }} > ( initialFocus: false, onDeactivate: () => setViewer(false), clickOutsideDeactivates: true, + escapeDeactivates: stopPropagation, }} > @@ -236,6 +237,7 @@ export const RoomCard = as<'div', RoomCardProps>( initialFocus: false, clickOutsideDeactivates: true, onDeactivate: closeTopic, + escapeDeactivates: stopPropagation, }} > {renderTopicViewer(roomName, roomTopic, closeTopic)} diff --git a/src/app/features/lobby/HierarchyItemMenu.tsx b/src/app/features/lobby/HierarchyItemMenu.tsx index 489bb9b..30a4f63 100644 --- a/src/app/features/lobby/HierarchyItemMenu.tsx +++ b/src/app/features/lobby/HierarchyItemMenu.tsx @@ -27,6 +27,7 @@ import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback'; import { UseStateProvider } from '../../components/UseStateProvider'; import { LeaveSpacePrompt } from '../../components/leave-space-prompt'; import { LeaveRoomPrompt } from '../../components/leave-room-prompt'; +import { stopPropagation } from '../../utils/keyboard'; type HierarchyItemWithParent = HierarchyItem & { parentId: string; @@ -227,6 +228,7 @@ export function HierarchyItemMenu({ clickOutsideDeactivates: true, isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown', isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', + escapeDeactivates: stopPropagation, }} > diff --git a/src/app/features/lobby/LobbyHeader.tsx b/src/app/features/lobby/LobbyHeader.tsx index a23faad..e01d3ad 100644 --- a/src/app/features/lobby/LobbyHeader.tsx +++ b/src/app/features/lobby/LobbyHeader.tsx @@ -30,6 +30,7 @@ import { openInviteUser, openSpaceSettings } from '../../../client/action/naviga import { IPowerLevels, usePowerLevelsAPI } from '../../hooks/usePowerLevels'; import { UseStateProvider } from '../../components/UseStateProvider'; import { LeaveSpacePrompt } from '../../components/leave-space-prompt'; +import { stopPropagation } from '../../utils/keyboard'; type LobbyMenuProps = { roomId: string; @@ -197,6 +198,7 @@ export function LobbyHeader({ showProfile, powerLevels }: LobbyHeaderProps) { clickOutsideDeactivates: true, isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown', isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', + escapeDeactivates: stopPropagation, }} > setViewTopic(false), + escapeDeactivates: stopPropagation, }} > setView(false), + escapeDeactivates: stopPropagation, }} > evt.key === 'ArrowDown', isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', + escapeDeactivates: stopPropagation, }} > @@ -338,6 +340,7 @@ function AddSpaceButton({ item }: { item: HierarchyItem }) { clickOutsideDeactivates: true, isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown', isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', + escapeDeactivates: stopPropagation, }} > diff --git a/src/app/features/message-search/SearchFilters.tsx b/src/app/features/message-search/SearchFilters.tsx index 5de188d..929dd1e 100644 --- a/src/app/features/message-search/SearchFilters.tsx +++ b/src/app/features/message-search/SearchFilters.tsx @@ -38,6 +38,7 @@ import { } from '../../hooks/useAsyncSearch'; import { DebounceOptions, useDebounce } from '../../hooks/useDebounce'; import { VirtualTile } from '../../components/virtualizer'; +import { stopPropagation } from '../../utils/keyboard'; type OrderButtonProps = { order?: string; @@ -66,6 +67,7 @@ function OrderButton({ order, onChange }: OrderButtonProps) { initialFocus: false, onDeactivate: () => setMenuAnchor(undefined), clickOutsideDeactivates: true, + escapeDeactivates: stopPropagation, }} > @@ -202,6 +204,7 @@ function SelectRoomButton({ roomList, selectedRooms, onChange }: SelectRoomButto initialFocus: false, onDeactivate: () => setMenuAnchor(undefined), clickOutsideDeactivates: true, + escapeDeactivates: stopPropagation, }} > diff --git a/src/app/features/room-nav/RoomNavItem.tsx b/src/app/features/room-nav/RoomNavItem.tsx index fce6237..8ecc81a 100644 --- a/src/app/features/room-nav/RoomNavItem.tsx +++ b/src/app/features/room-nav/RoomNavItem.tsx @@ -36,6 +36,7 @@ import { LeaveRoomPrompt } from '../../components/leave-room-prompt'; import { useClientConfig } from '../../hooks/useClientConfig'; import { useRoomTypingMember } from '../../hooks/useRoomTypingMembers'; import { TypingIndicator } from '../../components/typing-indicator'; +import { stopPropagation } from '../../utils/keyboard'; type RoomNavItemMenuProps = { room: Room; @@ -269,6 +270,7 @@ export function RoomNavItem({ clickOutsideDeactivates: true, isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown', isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', + escapeDeactivates: stopPropagation, }} > m.membership === Membership.Join, @@ -300,6 +301,7 @@ export function MembersDrawer({ room }: MembersDrawerProps) { clickOutsideDeactivates: true, isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown', isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', + escapeDeactivates: stopPropagation, }} > @@ -358,6 +360,7 @@ export function MembersDrawer({ room }: MembersDrawerProps) { clickOutsideDeactivates: true, isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown', isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', + escapeDeactivates: stopPropagation, }} > diff --git a/src/app/features/room/Room.tsx b/src/app/features/room/Room.tsx index 764e968..fd578ec 100644 --- a/src/app/features/room/Room.tsx +++ b/src/app/features/room/Room.tsx @@ -1,6 +1,7 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import { Box, Line } from 'folds'; import { useParams } from 'react-router-dom'; +import { isKeyHotkey } from 'is-hotkey'; import { RoomView } from './RoomView'; import { MembersDrawer } from './MembersDrawer'; import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize'; @@ -8,6 +9,8 @@ import { useSetting } from '../../state/hooks/settings'; import { settingsAtom } from '../../state/settings'; import { PowerLevelsContextProvider, usePowerLevels } from '../../hooks/usePowerLevels'; import { useRoom } from '../../hooks/useRoom'; +import { useKeyDown } from '../../hooks/useKeyDown'; +import { markAsRead } from '../../../client/action/notifications'; export function Room() { const { eventId } = useParams(); @@ -17,6 +20,18 @@ export function Room() { const screenSize = useScreenSizeContext(); const powerLevels = usePowerLevels(room); + useKeyDown( + window, + useCallback( + (evt) => { + if (isKeyHotkey('escape', evt)) { + markAsRead(room.roomId); + } + }, + [room.roomId] + ) + ); + return ( diff --git a/src/app/features/room/RoomView.tsx b/src/app/features/room/RoomView.tsx index fe145b3..84162fc 100644 --- a/src/app/features/room/RoomView.tsx +++ b/src/app/features/room/RoomView.tsx @@ -1,7 +1,8 @@ -import React, { useRef } from 'react'; +import React, { useCallback, useRef } from 'react'; import { Box, Text, config } from 'folds'; import { EventType, Room } from 'matrix-js-sdk'; - +import { ReactEditor } from 'slate-react'; +import { isKeyHotkey } from 'is-hotkey'; import { useStateEvent } from '../../hooks/useStateEvent'; import { StateEvent } from '../../../types/matrix/room'; import { usePowerLevelsAPI, usePowerLevelsContext } from '../../hooks/usePowerLevels'; @@ -15,10 +16,42 @@ import { RoomInput } from './RoomInput'; import { RoomViewFollowing } from './RoomViewFollowing'; import { Page } from '../../components/page'; import { RoomViewHeader } from './RoomViewHeader'; +import { useKeyDown } from '../../hooks/useKeyDown'; +import { editableActiveElement } from '../../utils/dom'; +import navigation from '../../../client/state/navigation'; + +const shouldFocusMessageField = (evt: KeyboardEvent): boolean => { + const { code } = evt; + console.log(code); + if (evt.metaKey || evt.altKey || evt.ctrlKey) { + return false; + } + // do not focus on F keys + if (/^F\d+$/.test(code)) return false; + + // do not focus on numlock/scroll lock + if ( + code.startsWith('OS') || + code.startsWith('Meta') || + code.startsWith('Shift') || + code.startsWith('Alt') || + code.startsWith('Control') || + code.startsWith('Arrow') || + code === 'Tab' || + code === 'Space' || + code === 'Enter' || + code === 'NumLock' || + code === 'ScrollLock' + ) { + return false; + } + + return true; +}; export function RoomView({ room, eventId }: { room: Room; eventId?: string }) { - const roomInputRef = useRef(null); - const roomViewRef = useRef(null); + const roomInputRef = useRef(null); + const roomViewRef = useRef(null); const { roomId } = room; const editor = useEditor(); @@ -33,6 +66,25 @@ export function RoomView({ room, eventId }: { room: Room; eventId?: string }) { ? canSendEvent(EventType.RoomMessage, getPowerLevel(myUserId)) : false; + useKeyDown( + window, + useCallback( + (evt) => { + if (editableActiveElement()) return; + if ( + document.body.lastElementChild?.className !== 'ReactModalPortal' || + navigation.isRawModalVisible + ) { + return; + } + if (shouldFocusMessageField(evt) || isKeyHotkey('mod+v')) { + ReactEditor.focus(editor); + } + }, + [editor] + ) + ); + return ( diff --git a/src/app/features/room/RoomViewFollowing.tsx b/src/app/features/room/RoomViewFollowing.tsx index 2f7a583..58d3f64 100644 --- a/src/app/features/room/RoomViewFollowing.tsx +++ b/src/app/features/room/RoomViewFollowing.tsx @@ -22,6 +22,7 @@ import { useMatrixClient } from '../../hooks/useMatrixClient'; import { useRoomLatestRenderedEvent } from '../../hooks/useRoomLatestRenderedEvent'; import { useRoomEventReaders } from '../../hooks/useRoomEventReaders'; import { EventReaders } from '../../components/event-readers'; +import { stopPropagation } from '../../utils/keyboard'; export type RoomViewFollowingProps = { room: Room; @@ -50,6 +51,7 @@ export const RoomViewFollowing = as<'div', RoomViewFollowingProps>( initialFocus: false, onDeactivate: () => setOpen(false), clickOutsideDeactivates: true, + escapeDeactivates: stopPropagation, }} > diff --git a/src/app/features/room/RoomViewHeader.tsx b/src/app/features/room/RoomViewHeader.tsx index 61c730f..aa267c5 100644 --- a/src/app/features/room/RoomViewHeader.tsx +++ b/src/app/features/room/RoomViewHeader.tsx @@ -57,6 +57,7 @@ import { useRoomAvatar, useRoomName, useRoomTopic } from '../../hooks/useRoomMet import { mDirectAtom } from '../../state/mDirectList'; import { useClientConfig } from '../../hooks/useClientConfig'; import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize'; +import { stopPropagation } from '../../utils/keyboard'; type RoomMenuProps = { room: Room; @@ -240,6 +241,7 @@ export function RoomViewHeader() { initialFocus: false, clickOutsideDeactivates: true, onDeactivate: () => setViewTopic(false), + escapeDeactivates: stopPropagation, }} > evt.key === 'ArrowDown', isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', + escapeDeactivates: stopPropagation, }} > void; @@ -148,6 +149,7 @@ export const MessageAllReactionItem = as< returnFocusOnDeactivate: false, onDeactivate: () => handleClose(), clickOutsideDeactivates: true, + escapeDeactivates: stopPropagation, }} > @@ -201,6 +203,7 @@ export const MessageReadReceiptItem = as< initialFocus: false, onDeactivate: handleClose, clickOutsideDeactivates: true, + escapeDeactivates: stopPropagation, }} > @@ -278,6 +281,7 @@ export const MessageSourceCodeItem = as< initialFocus: false, onDeactivate: handleClose, clickOutsideDeactivates: true, + escapeDeactivates: stopPropagation, }} > @@ -401,6 +405,7 @@ export const MessageDeleteItem = as< initialFocus: false, onDeactivate: handleClose, clickOutsideDeactivates: true, + escapeDeactivates: stopPropagation, }} > @@ -530,6 +535,7 @@ export const MessageReportItem = as< initialFocus: false, onDeactivate: handleClose, clickOutsideDeactivates: true, + escapeDeactivates: stopPropagation, }} > @@ -875,6 +881,7 @@ export const Message = as<'div', MessageProps>( clickOutsideDeactivates: true, isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown', isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', + escapeDeactivates: stopPropagation, }} > @@ -1089,6 +1096,7 @@ export const Event = as<'div', EventProps>( clickOutsideDeactivates: true, isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown', isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', + escapeDeactivates: stopPropagation, }} > diff --git a/src/app/features/room/message/Reactions.tsx b/src/app/features/room/message/Reactions.tsx index 728cf81..a6d7f55 100644 --- a/src/app/features/room/message/Reactions.tsx +++ b/src/app/features/room/message/Reactions.tsx @@ -21,6 +21,7 @@ import { Reaction, ReactionTooltipMsg } from '../../../components/message'; import { useRelations } from '../../../hooks/useRelations'; import * as css from './styles.css'; import { ReactionViewer } from '../reaction-viewer'; +import { stopPropagation } from '../../../utils/keyboard'; export type ReactionsProps = { room: Room; @@ -105,6 +106,7 @@ export const Reactions = as<'div', ReactionsProps>( returnFocusOnDeactivate: false, onDeactivate: () => setViewer(false), clickOutsideDeactivates: true, + escapeDeactivates: stopPropagation, }} > diff --git a/src/app/organisms/search/Search.jsx b/src/app/organisms/search/Search.jsx index c9d1d99..0990a03 100644 --- a/src/app/organisms/search/Search.jsx +++ b/src/app/organisms/search/Search.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useRef } from 'react'; +import React, { useState, useEffect, useRef, useCallback } from 'react'; import { useAtomValue } from 'jotai'; import './Search.scss'; @@ -25,6 +25,8 @@ import { roomToUnreadAtom } from '../../state/room/roomToUnread'; import { roomToParentsAtom } from '../../state/room/roomToParents'; import { allRoomsAtom } from '../../state/room-list/roomList'; import { mDirectAtom } from '../../state/mDirectList'; +import { useKeyDown } from '../../hooks/useKeyDown'; +import { openSearch } from '../../../client/action/navigation'; function useVisiblityToggle(setResult) { const [isOpen, setIsOpen] = useState(false); @@ -49,6 +51,27 @@ function useVisiblityToggle(setResult) { } }, [isOpen]); + useKeyDown( + window, + useCallback((event) => { + // Ctrl/Cmd + + if (event.ctrlKey || event.metaKey) { + // open search modal + if (event.key === 'k') { + event.preventDefault(); + // means some menu or modal window is open + if ( + document.body.lastChild.className !== 'ReactModalPortal' || + navigation.isRawModalVisible + ) { + return; + } + openSearch(); + } + } + }, []) + ); + const requestClose = () => setIsOpen(false); return [isOpen, requestClose]; diff --git a/src/app/pages/auth/ServerPicker.tsx b/src/app/pages/auth/ServerPicker.tsx index 18201c9..a2a7810 100644 --- a/src/app/pages/auth/ServerPicker.tsx +++ b/src/app/pages/auth/ServerPicker.tsx @@ -22,6 +22,7 @@ import { import FocusTrap from 'focus-trap-react'; import { useDebounce } from '../../hooks/useDebounce'; +import { stopPropagation } from '../../utils/keyboard'; export function ServerPicker({ server, @@ -103,6 +104,7 @@ export function ServerPicker({ clickOutsideDeactivates: true, isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown', isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', + escapeDeactivates: stopPropagation, }} > diff --git a/src/app/pages/auth/login/PasswordLoginForm.tsx b/src/app/pages/auth/login/PasswordLoginForm.tsx index b9dd14b..087d384 100644 --- a/src/app/pages/auth/login/PasswordLoginForm.tsx +++ b/src/app/pages/auth/login/PasswordLoginForm.tsx @@ -36,6 +36,7 @@ import { import { PasswordInput } from '../../../components/password-input/PasswordInput'; import { FieldError } from '../FiledError'; import { getResetPasswordPath } from '../../pathUtils'; +import { stopPropagation } from '../../../utils/keyboard'; function UsernameHint({ server }: { server: string }) { const [anchor, setAnchor] = useState(); @@ -54,6 +55,7 @@ function UsernameHint({ server }: { server: string }) { initialFocus: false, onDeactivate: () => setAnchor(undefined), clickOutsideDeactivates: true, + escapeDeactivates: stopPropagation, }} > diff --git a/src/app/pages/client/ClientRoot.tsx b/src/app/pages/client/ClientRoot.tsx index 6a1dbcb..c36adfc 100644 --- a/src/app/pages/client/ClientRoot.tsx +++ b/src/app/pages/client/ClientRoot.tsx @@ -1,7 +1,6 @@ import { Box, Spinner, Text } from 'folds'; import React, { ReactNode, useEffect, useState } from 'react'; import initMatrix from '../../../client/initMatrix'; -import { initHotkeys } from '../../../client/event/hotkeys'; import { getSecret } from '../../../client/state/auth'; import { SplashScreen } from '../../components/splash-screen'; import { CapabilitiesAndMediaConfigLoader } from '../../components/CapabilitiesAndMediaConfigLoader'; @@ -47,7 +46,6 @@ export function ClientRoot({ children }: ClientRootProps) { useEffect(() => { const handleStart = () => { - initHotkeys(); setLoading(false); }; initMatrix.once('init_loading_finished', handleStart); diff --git a/src/app/pages/client/direct/Direct.tsx b/src/app/pages/client/direct/Direct.tsx index 673a5d9..c62ef16 100644 --- a/src/app/pages/client/direct/Direct.tsx +++ b/src/app/pages/client/direct/Direct.tsx @@ -44,6 +44,7 @@ import { PageNav, PageNavContent, PageNavHeader } from '../../../components/page import { useClosedNavCategoriesAtom } from '../../../state/hooks/closedNavCategories'; import { useRoomsUnread } from '../../../state/hooks/unread'; import { markAsRead } from '../../../../client/action/notifications'; +import { stopPropagation } from '../../../utils/keyboard'; type DirectMenuProps = { requestClose: () => void; @@ -118,6 +119,7 @@ function DirectHeader() { clickOutsideDeactivates: true, isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown', isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', + escapeDeactivates: stopPropagation, }} > setMenuAnchor(undefined)} /> diff --git a/src/app/pages/client/explore/Explore.tsx b/src/app/pages/client/explore/Explore.tsx index 67f8dc3..420e1a1 100644 --- a/src/app/pages/client/explore/Explore.tsx +++ b/src/app/pages/client/explore/Explore.tsx @@ -36,6 +36,7 @@ import { getMxIdServer } from '../../../utils/matrix'; import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback'; import { useNavToActivePathMapper } from '../../../hooks/useNavToActivePathMapper'; import { PageNav, PageNavContent, PageNavHeader } from '../../../components/page'; +import { stopPropagation } from '../../../utils/keyboard'; export function AddServer() { const mx = useMatrixClient(); @@ -80,6 +81,7 @@ export function AddServer() { initialFocus: false, clickOutsideDeactivates: true, onDeactivate: () => setDialog(false), + escapeDeactivates: stopPropagation, }} > diff --git a/src/app/pages/client/explore/Server.tsx b/src/app/pages/client/explore/Server.tsx index 9fe4e78..1a81c22 100644 --- a/src/app/pages/client/explore/Server.tsx +++ b/src/app/pages/client/explore/Server.tsx @@ -41,6 +41,7 @@ import * as css from './style.css'; import { allRoomsAtom } from '../../../state/room-list/roomList'; import { useRoomNavigate } from '../../../hooks/useRoomNavigate'; import { getMxIdServer } from '../../../utils/matrix'; +import { stopPropagation } from '../../../utils/keyboard'; const useServerSearchParams = (searchParams: URLSearchParams): ExploreServerPathSearchParams => useMemo( @@ -182,6 +183,7 @@ function ThirdPartyProtocolsSelector({ initialFocus: false, onDeactivate: () => setMenuAnchor(undefined), clickOutsideDeactivates: true, + escapeDeactivates: stopPropagation, }} > @@ -277,6 +279,7 @@ function LimitButton({ limit, onLimitChange }: LimitButtonProps) { initialFocus: false, onDeactivate: () => setMenuAnchor(undefined), clickOutsideDeactivates: true, + escapeDeactivates: stopPropagation, }} > diff --git a/src/app/pages/client/home/Home.tsx b/src/app/pages/client/home/Home.tsx index 3371419..76034cf 100644 --- a/src/app/pages/client/home/Home.tsx +++ b/src/app/pages/client/home/Home.tsx @@ -47,6 +47,7 @@ import { PageNav, PageNavHeader, PageNavContent } from '../../../components/page import { useRoomsUnread } from '../../../state/hooks/unread'; import { markAsRead } from '../../../../client/action/notifications'; import { useClosedNavCategoriesAtom } from '../../../state/hooks/closedNavCategories'; +import { stopPropagation } from '../../../utils/keyboard'; type HomeMenuProps = { requestClose: () => void; @@ -121,6 +122,7 @@ function HomeHeader() { clickOutsideDeactivates: true, isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown', isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', + escapeDeactivates: stopPropagation, }} > setMenuAnchor(undefined)} /> diff --git a/src/app/pages/client/inbox/Invites.tsx b/src/app/pages/client/inbox/Invites.tsx index 91dc029..06e5f6c 100644 --- a/src/app/pages/client/inbox/Invites.tsx +++ b/src/app/pages/client/inbox/Invites.tsx @@ -34,7 +34,7 @@ import { RoomAvatar } from '../../../components/room-avatar'; import { addRoomIdToMDirect, getMxIdLocalPart, guessDmRoomUserId } from '../../../utils/matrix'; import { Time } from '../../../components/message'; import { useElementSizeObserver } from '../../../hooks/useElementSizeObserver'; -import { onEnterOrSpace } from '../../../utils/keyboard'; +import { onEnterOrSpace, stopPropagation } from '../../../utils/keyboard'; import { RoomTopicViewer } from '../../../components/room-topic-viewer'; import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback'; import { useRoomNavigate } from '../../../hooks/useRoomNavigate'; @@ -140,6 +140,7 @@ function InviteCard({ room, userId, direct, compact, onNavigate }: InviteCardPro initialFocus: false, clickOutsideDeactivates: true, onDeactivate: closeTopic, + escapeDeactivates: stopPropagation, }} > void; @@ -120,6 +121,7 @@ export function DirectTab() { clickOutsideDeactivates: true, isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown', isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', + escapeDeactivates: stopPropagation, }} > setMenuAnchor(undefined)} /> diff --git a/src/app/pages/client/sidebar/HomeTab.tsx b/src/app/pages/client/sidebar/HomeTab.tsx index 0b5135c..41f7c64 100644 --- a/src/app/pages/client/sidebar/HomeTab.tsx +++ b/src/app/pages/client/sidebar/HomeTab.tsx @@ -23,6 +23,7 @@ import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize'; import { useNavToActivePathAtom } from '../../../state/hooks/navToActivePath'; import { useHomeRooms } from '../home/useHomeRooms'; import { markAsRead } from '../../../../client/action/notifications'; +import { stopPropagation } from '../../../utils/keyboard'; type HomeMenuProps = { requestClose: () => void; @@ -122,6 +123,7 @@ export function HomeTab() { clickOutsideDeactivates: true, isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown', isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', + escapeDeactivates: stopPropagation, }} > setMenuAnchor(undefined)} /> diff --git a/src/app/pages/client/sidebar/SpaceTabs.tsx b/src/app/pages/client/sidebar/SpaceTabs.tsx index 99c0496..8635a35 100644 --- a/src/app/pages/client/sidebar/SpaceTabs.tsx +++ b/src/app/pages/client/sidebar/SpaceTabs.tsx @@ -90,6 +90,7 @@ import { roomToUnreadAtom } from '../../../state/room/roomToUnread'; import { markAsRead } from '../../../../client/action/notifications'; import { copyToClipboard } from '../../../utils/dom'; import { openInviteUser, openSpaceSettings } from '../../../../client/action/navigation'; +import { stopPropagation } from '../../../utils/keyboard'; type SpaceMenuProps = { room: Room; @@ -463,6 +464,7 @@ function SpaceTab({ clickOutsideDeactivates: true, isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown', isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', + escapeDeactivates: stopPropagation, }} > evt.key === 'ArrowDown', isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', + escapeDeactivates: stopPropagation, }} > setMenuAnchor(undefined)} /> diff --git a/src/app/utils/keyboard.ts b/src/app/utils/keyboard.ts index 8ec435d..da3fe8c 100644 --- a/src/app/utils/keyboard.ts +++ b/src/app/utils/keyboard.ts @@ -30,3 +30,8 @@ export const onEnterOrSpace = (callback: () => void) => (evt: KeyboardEventLike) callback(); } }; + +export const stopPropagation = (evt: KeyboardEvent): boolean => { + evt.stopPropagation(); + return true; +}; diff --git a/src/client/event/hotkeys.js b/src/client/event/hotkeys.js deleted file mode 100644 index 856fcad..0000000 --- a/src/client/event/hotkeys.js +++ /dev/null @@ -1,24 +0,0 @@ -import { openSearch } from '../action/navigation'; -import navigation from '../state/navigation'; - -function listenKeyboard(event) { - // Ctrl/Cmd + - if (event.ctrlKey || event.metaKey) { - // open search modal - if (event.key === 'k') { - event.preventDefault(); - if (navigation.isRawModalVisible) return; - openSearch(); - } - } -} - -function initHotkeys() { - document.body.addEventListener('keydown', listenKeyboard); -} - -function removeHotkeys() { - document.body.removeEventListener('keydown', listenKeyboard); -} - -export { initHotkeys, removeHotkeys };