From 41733c6f1817e22a589cb53596db7efe2580bf33 Mon Sep 17 00:00:00 2001 From: Ajay Bura <32841439+ajbura@users.noreply.github.com> Date: Thu, 18 Jul 2024 09:39:34 +0530 Subject: [PATCH] make hotkeys work again --- 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 a78c13f2..9c7fd980 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, }} > <Menu variant="Surface"> diff --git a/src/app/components/UIAFlowOverlay.tsx b/src/app/components/UIAFlowOverlay.tsx index f788eb0f..dc517b48 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 ( <Overlay open backdrop={<OverlayBackdrop />}> - <FocusTrap focusTrapOptions={{ initialFocus: false }}> + <FocusTrap focusTrapOptions={{ initialFocus: false, escapeDeactivates: stopPropagation }}> <Box style={{ height: '100%' }} direction="Column" grow="Yes" gap="400"> <Box grow="Yes" direction="Column" alignItems="Center" justifyContent="Center"> {children} diff --git a/src/app/components/editor/Editor.preview.tsx b/src/app/components/editor/Editor.preview.tsx index ad67dc12..b760dddc 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, }} > <Modal size="500"> diff --git a/src/app/components/editor/Toolbar.tsx b/src/app/components/editor/Toolbar.tsx index 0c82855d..e5c7d16e 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, }} > <Menu style={{ padding: config.space.S100 }}> diff --git a/src/app/components/editor/autocomplete/AutocompleteMenu.tsx b/src/app/components/editor/autocomplete/AutocompleteMenu.tsx index fc4327da..5d2d917c 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, }} > <Menu className={css.AutocompleteMenu}> diff --git a/src/app/components/emoji-board/EmojiBoard.tsx b/src/app/components/emoji-board/EmojiBoard.tsx index 408ce85d..53172efd 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, }} > <EmojiBoardLayout diff --git a/src/app/components/leave-room-prompt/LeaveRoomPrompt.tsx b/src/app/components/leave-room-prompt/LeaveRoomPrompt.tsx index f4de9edf..217491e6 100644 --- a/src/app/components/leave-room-prompt/LeaveRoomPrompt.tsx +++ b/src/app/components/leave-room-prompt/LeaveRoomPrompt.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 LeaveRoomPromptProps = { roomId: string; @@ -52,6 +53,7 @@ export function LeaveRoomPrompt({ roomId, onDone, onCancel }: LeaveRoomPromptPro initialFocus: false, onDeactivate: onCancel, clickOutsideDeactivates: true, + escapeDeactivates: stopPropagation, }} > <Dialog variant="Surface"> diff --git a/src/app/components/leave-space-prompt/LeaveSpacePrompt.tsx b/src/app/components/leave-space-prompt/LeaveSpacePrompt.tsx index 1132b44d..8709b942 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, }} > <Dialog variant="Surface"> diff --git a/src/app/components/message/content/FileContent.tsx b/src/app/components/message/content/FileContent.tsx index af064a32..f09c1e07 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) => ( <TooltipProvider @@ -101,6 +102,7 @@ export function ReadTextFile({ body, mimeType, url, encInfo, renderViewer }: Rea initialFocus: false, onDeactivate: () => setTextViewer(false), clickOutsideDeactivates: true, + escapeDeactivates: stopPropagation, }} > <Modal @@ -184,6 +186,7 @@ export function ReadPdfFile({ body, mimeType, url, encInfo, renderViewer }: Read initialFocus: false, onDeactivate: () => setPdfViewer(false), clickOutsideDeactivates: true, + escapeDeactivates: stopPropagation, }} > <Modal diff --git a/src/app/components/message/content/ImageContent.tsx b/src/app/components/message/content/ImageContent.tsx index a64b8e91..90c46354 100644 --- a/src/app/components/message/content/ImageContent.tsx +++ b/src/app/components/message/content/ImageContent.tsx @@ -26,6 +26,7 @@ import { getFileSrcUrl } from './util'; import * as css from './style.css'; import { bytesToSize } from '../../../utils/common'; import { FALLBACK_MIMETYPE } from '../../../utils/mimeTypes'; +import { stopPropagation } from '../../../utils/keyboard'; type RenderViewerProps = { src: string; @@ -108,6 +109,7 @@ export const ImageContent = as<'div', ImageContentProps>( initialFocus: false, onDeactivate: () => setViewer(false), clickOutsideDeactivates: true, + escapeDeactivates: stopPropagation, }} > <Modal diff --git a/src/app/components/room-card/RoomCard.tsx b/src/app/components/room-card/RoomCard.tsx index 370b790d..79dd87db 100644 --- a/src/app/components/room-card/RoomCard.tsx +++ b/src/app/components/room-card/RoomCard.tsx @@ -26,7 +26,7 @@ import { nameInitials } from '../../utils/common'; import { millify } from '../../plugins/millify'; import { useMatrixClient } from '../../hooks/useMatrixClient'; import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback'; -import { onEnterOrSpace } from '../../utils/keyboard'; +import { onEnterOrSpace, stopPropagation } from '../../utils/keyboard'; import { RoomType, StateEvent } from '../../../types/matrix/room'; import { useJoinedRoomId } from '../../hooks/useJoinedRoomId'; import { useElementSizeObserver } from '../../hooks/useElementSizeObserver'; @@ -107,6 +107,7 @@ function ErrorDialog({ initialFocus: false, clickOutsideDeactivates: true, onDeactivate: closeError, + escapeDeactivates: stopPropagation, }} > <Dialog variant="Surface"> @@ -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 489bb9ba..30a4f632 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, }} > <Menu style={{ maxWidth: toRem(150), width: '100vw' }}> diff --git a/src/app/features/lobby/LobbyHeader.tsx b/src/app/features/lobby/LobbyHeader.tsx index a23faada..e01d3ad5 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, }} > <LobbyMenu diff --git a/src/app/features/lobby/LobbyHero.tsx b/src/app/features/lobby/LobbyHero.tsx index a92a49f3..a2b31e8f 100644 --- a/src/app/features/lobby/LobbyHero.tsx +++ b/src/app/features/lobby/LobbyHero.tsx @@ -10,7 +10,7 @@ import { UseStateProvider } from '../../components/UseStateProvider'; import { RoomTopicViewer } from '../../components/room-topic-viewer'; import * as css from './LobbyHero.css'; import { PageHero } from '../../components/page'; -import { onEnterOrSpace } from '../../utils/keyboard'; +import { onEnterOrSpace, stopPropagation } from '../../utils/keyboard'; export function LobbyHero() { const mx = useMatrixClient(); @@ -46,6 +46,7 @@ export function LobbyHero() { initialFocus: false, clickOutsideDeactivates: true, onDeactivate: () => setViewTopic(false), + escapeDeactivates: stopPropagation, }} > <RoomTopicViewer diff --git a/src/app/features/lobby/RoomItem.tsx b/src/app/features/lobby/RoomItem.tsx index 4e7dd6af..3cc425f2 100644 --- a/src/app/features/lobby/RoomItem.tsx +++ b/src/app/features/lobby/RoomItem.tsx @@ -31,7 +31,7 @@ import { } from '../../components/RoomSummaryLoader'; import { UseStateProvider } from '../../components/UseStateProvider'; import { RoomTopicViewer } from '../../components/room-topic-viewer'; -import { onEnterOrSpace } from '../../utils/keyboard'; +import { onEnterOrSpace, stopPropagation } from '../../utils/keyboard'; import { Membership, RoomType } from '../../../types/matrix/room'; import * as css from './RoomItem.css'; import * as styleCss from './style.css'; @@ -264,6 +264,7 @@ function RoomProfile({ initialFocus: false, clickOutsideDeactivates: true, onDeactivate: () => setView(false), + escapeDeactivates: stopPropagation, }} > <RoomTopicViewer diff --git a/src/app/features/lobby/SpaceItem.tsx b/src/app/features/lobby/SpaceItem.tsx index e9d9356a..04f7e2cc 100644 --- a/src/app/features/lobby/SpaceItem.tsx +++ b/src/app/features/lobby/SpaceItem.tsx @@ -34,6 +34,7 @@ import * as styleCss from './style.css'; import { ErrorCode } from '../../cs-errorcode'; import { useDraggableItem } from './DnD'; import { openCreateRoom, openSpaceAddExisting } from '../../../client/action/navigation'; +import { stopPropagation } from '../../utils/keyboard'; function SpaceProfileLoading() { return ( @@ -277,6 +278,7 @@ function AddRoomButton({ item }: { item: HierarchyItem }) { clickOutsideDeactivates: true, isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown', isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', + escapeDeactivates: stopPropagation, }} > <Menu style={{ padding: config.space.S100 }}> @@ -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, }} > <Menu style={{ padding: config.space.S100 }}> diff --git a/src/app/features/message-search/SearchFilters.tsx b/src/app/features/message-search/SearchFilters.tsx index 5de188d4..929dd1e9 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, }} > <Menu variant="Surface"> @@ -202,6 +204,7 @@ function SelectRoomButton({ roomList, selectedRooms, onChange }: SelectRoomButto initialFocus: false, onDeactivate: () => setMenuAnchor(undefined), clickOutsideDeactivates: true, + escapeDeactivates: stopPropagation, }} > <Menu variant="Surface" style={{ width: toRem(250) }}> diff --git a/src/app/features/room-nav/RoomNavItem.tsx b/src/app/features/room-nav/RoomNavItem.tsx index fce62375..8ecc81a3 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, }} > <RoomNavItemMenu diff --git a/src/app/features/room/MembersDrawer.tsx b/src/app/features/room/MembersDrawer.tsx index 8a96b84a..70a9aa34 100644 --- a/src/app/features/room/MembersDrawer.tsx +++ b/src/app/features/room/MembersDrawer.tsx @@ -55,6 +55,7 @@ import { millify } from '../../plugins/millify'; import { ScrollTopContainer } from '../../components/scroll-top-container'; import { UserAvatar } from '../../components/user-avatar'; import { useRoomTypingMember } from '../../hooks/useRoomTypingMembers'; +import { stopPropagation } from '../../utils/keyboard'; export const MembershipFilters = { filterJoined: (m: RoomMember) => 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, }} > <Menu style={{ padding: config.space.S100 }}> @@ -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, }} > <Menu style={{ padding: config.space.S100 }}> diff --git a/src/app/features/room/Room.tsx b/src/app/features/room/Room.tsx index 764e9686..fd578ec6 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 ( <PowerLevelsContextProvider value={powerLevels}> <Box grow="Yes"> diff --git a/src/app/features/room/RoomView.tsx b/src/app/features/room/RoomView.tsx index fe145b37..84162fcc 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<HTMLDivElement>(null); + const roomViewRef = useRef<HTMLDivElement>(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 ( <Page ref={roomViewRef}> <RoomViewHeader /> diff --git a/src/app/features/room/RoomViewFollowing.tsx b/src/app/features/room/RoomViewFollowing.tsx index 2f7a583e..58d3f64f 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, }} > <Modal variant="Surface" size="300"> diff --git a/src/app/features/room/RoomViewHeader.tsx b/src/app/features/room/RoomViewHeader.tsx index 61c730f7..aa267c53 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, }} > <RoomTopicViewer @@ -331,6 +333,7 @@ export function RoomViewHeader() { clickOutsideDeactivates: true, isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown', isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', + escapeDeactivates: stopPropagation, }} > <RoomMenu diff --git a/src/app/features/room/message/Message.tsx b/src/app/features/room/message/Message.tsx index 70d5c555..6db366ac 100644 --- a/src/app/features/room/message/Message.tsx +++ b/src/app/features/room/message/Message.tsx @@ -74,6 +74,7 @@ import { } from '../../../pages/pathUtils'; import { copyToClipboard } from '../../../utils/dom'; import { useClientConfig } from '../../../hooks/useClientConfig'; +import { stopPropagation } from '../../../utils/keyboard'; export type ReactionHandler = (keyOrMxc: string, shortcode: string) => void; @@ -148,6 +149,7 @@ export const MessageAllReactionItem = as< returnFocusOnDeactivate: false, onDeactivate: () => handleClose(), clickOutsideDeactivates: true, + escapeDeactivates: stopPropagation, }} > <Modal variant="Surface" size="300"> @@ -201,6 +203,7 @@ export const MessageReadReceiptItem = as< initialFocus: false, onDeactivate: handleClose, clickOutsideDeactivates: true, + escapeDeactivates: stopPropagation, }} > <Modal variant="Surface" size="300"> @@ -278,6 +281,7 @@ export const MessageSourceCodeItem = as< initialFocus: false, onDeactivate: handleClose, clickOutsideDeactivates: true, + escapeDeactivates: stopPropagation, }} > <Modal variant="Surface" size="500"> @@ -401,6 +405,7 @@ export const MessageDeleteItem = as< initialFocus: false, onDeactivate: handleClose, clickOutsideDeactivates: true, + escapeDeactivates: stopPropagation, }} > <Dialog variant="Surface"> @@ -530,6 +535,7 @@ export const MessageReportItem = as< initialFocus: false, onDeactivate: handleClose, clickOutsideDeactivates: true, + escapeDeactivates: stopPropagation, }} > <Dialog variant="Surface"> @@ -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, }} > <Menu> @@ -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, }} > <Menu {...props} ref={ref}> diff --git a/src/app/features/room/message/Reactions.tsx b/src/app/features/room/message/Reactions.tsx index 728cf810..a6d7f553 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, }} > <Modal variant="Surface" size="300"> diff --git a/src/app/organisms/search/Search.jsx b/src/app/organisms/search/Search.jsx index c9d1d991..0990a03f 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 18201c98..a2a78106 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, }} > <Menu> diff --git a/src/app/pages/auth/login/PasswordLoginForm.tsx b/src/app/pages/auth/login/PasswordLoginForm.tsx index b9dd14b7..087d384d 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<RectCords>(); @@ -54,6 +55,7 @@ function UsernameHint({ server }: { server: string }) { initialFocus: false, onDeactivate: () => setAnchor(undefined), clickOutsideDeactivates: true, + escapeDeactivates: stopPropagation, }} > <Menu> diff --git a/src/app/pages/client/ClientRoot.tsx b/src/app/pages/client/ClientRoot.tsx index 6a1dbcb1..c36adfc6 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 673a5d9f..c62ef160 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, }} > <DirectMenu requestClose={() => setMenuAnchor(undefined)} /> diff --git a/src/app/pages/client/explore/Explore.tsx b/src/app/pages/client/explore/Explore.tsx index 67f8dc3f..420e1a16 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, }} > <Dialog variant="Surface"> diff --git a/src/app/pages/client/explore/Server.tsx b/src/app/pages/client/explore/Server.tsx index 9fe4e78e..1a81c225 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, }} > <Menu variant="Surface"> @@ -277,6 +279,7 @@ function LimitButton({ limit, onLimitChange }: LimitButtonProps) { initialFocus: false, onDeactivate: () => setMenuAnchor(undefined), clickOutsideDeactivates: true, + escapeDeactivates: stopPropagation, }} > <Menu variant="Surface"> diff --git a/src/app/pages/client/home/Home.tsx b/src/app/pages/client/home/Home.tsx index 33714191..76034cfe 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, }} > <HomeMenu requestClose={() => setMenuAnchor(undefined)} /> diff --git a/src/app/pages/client/inbox/Invites.tsx b/src/app/pages/client/inbox/Invites.tsx index 91dc0291..06e5f6c6 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, }} > <RoomTopicViewer diff --git a/src/app/pages/client/sidebar/DirectTab.tsx b/src/app/pages/client/sidebar/DirectTab.tsx index f25d7bdc..0beb17d8 100644 --- a/src/app/pages/client/sidebar/DirectTab.tsx +++ b/src/app/pages/client/sidebar/DirectTab.tsx @@ -22,6 +22,7 @@ import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize'; import { useNavToActivePathAtom } from '../../../state/hooks/navToActivePath'; import { useDirectRooms } from '../direct/useDirectRooms'; import { markAsRead } from '../../../../client/action/notifications'; +import { stopPropagation } from '../../../utils/keyboard'; type DirectMenuProps = { requestClose: () => void; @@ -120,6 +121,7 @@ export function DirectTab() { clickOutsideDeactivates: true, isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown', isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', + escapeDeactivates: stopPropagation, }} > <DirectMenu requestClose={() => setMenuAnchor(undefined)} /> diff --git a/src/app/pages/client/sidebar/HomeTab.tsx b/src/app/pages/client/sidebar/HomeTab.tsx index 0b5135ca..41f7c648 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, }} > <HomeMenu requestClose={() => setMenuAnchor(undefined)} /> diff --git a/src/app/pages/client/sidebar/SpaceTabs.tsx b/src/app/pages/client/sidebar/SpaceTabs.tsx index 99c04965..8635a35f 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, }} > <SpaceMenu diff --git a/src/app/pages/client/space/Space.tsx b/src/app/pages/client/space/Space.tsx index 8eab6b49..e947373b 100644 --- a/src/app/pages/client/space/Space.tsx +++ b/src/app/pages/client/space/Space.tsx @@ -73,6 +73,7 @@ import { useClientConfig } from '../../../hooks/useClientConfig'; import { useClosedNavCategoriesAtom } from '../../../state/hooks/closedNavCategories'; import { useStateEvent } from '../../../hooks/useStateEvent'; import { StateEvent } from '../../../../types/matrix/room'; +import { stopPropagation } from '../../../utils/keyboard'; type SpaceMenuProps = { room: Room; @@ -248,6 +249,7 @@ function SpaceHeader() { clickOutsideDeactivates: true, isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown', isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', + escapeDeactivates: stopPropagation, }} > <SpaceMenu room={space} requestClose={() => setMenuAnchor(undefined)} /> diff --git a/src/app/utils/keyboard.ts b/src/app/utils/keyboard.ts index 8ec435d3..da3fe8cb 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 856fcadc..00000000 --- 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 };