Make hotkeys work again (#1819)
authorAjay Bura <32841439+ajbura@users.noreply.github.com>
Thu, 18 Jul 2024 13:20:20 +0000 (18:50 +0530)
committerGitHub <noreply@github.com>
Thu, 18 Jul 2024 13:20:20 +0000 (23:20 +1000)
40 files changed:
src/app/components/Pdf-viewer/PdfViewer.tsx
src/app/components/UIAFlowOverlay.tsx
src/app/components/editor/Editor.preview.tsx
src/app/components/editor/Toolbar.tsx
src/app/components/editor/autocomplete/AutocompleteMenu.tsx
src/app/components/emoji-board/EmojiBoard.tsx
src/app/components/leave-room-prompt/LeaveRoomPrompt.tsx
src/app/components/leave-space-prompt/LeaveSpacePrompt.tsx
src/app/components/message/content/FileContent.tsx
src/app/components/message/content/ImageContent.tsx
src/app/components/room-card/RoomCard.tsx
src/app/features/lobby/HierarchyItemMenu.tsx
src/app/features/lobby/LobbyHeader.tsx
src/app/features/lobby/LobbyHero.tsx
src/app/features/lobby/RoomItem.tsx
src/app/features/lobby/SpaceItem.tsx
src/app/features/message-search/SearchFilters.tsx
src/app/features/room-nav/RoomNavItem.tsx
src/app/features/room/MembersDrawer.tsx
src/app/features/room/Room.tsx
src/app/features/room/RoomView.tsx
src/app/features/room/RoomViewFollowing.tsx
src/app/features/room/RoomViewHeader.tsx
src/app/features/room/message/Message.tsx
src/app/features/room/message/Reactions.tsx
src/app/organisms/search/Search.jsx
src/app/pages/auth/ServerPicker.tsx
src/app/pages/auth/login/PasswordLoginForm.tsx
src/app/pages/client/ClientRoot.tsx
src/app/pages/client/direct/Direct.tsx
src/app/pages/client/explore/Explore.tsx
src/app/pages/client/explore/Server.tsx
src/app/pages/client/home/Home.tsx
src/app/pages/client/inbox/Invites.tsx
src/app/pages/client/sidebar/DirectTab.tsx
src/app/pages/client/sidebar/HomeTab.tsx
src/app/pages/client/sidebar/SpaceTabs.tsx
src/app/pages/client/space/Space.tsx
src/app/utils/keyboard.ts
src/client/event/hotkeys.js [deleted file]

index a78c13f2a1c7e37c8646b33614970abb53f3d4d9..9c7fd9804cb34e8bd0918d91867886e6c17315fb 100644 (file)
@@ -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">
index f788eb0fd68849ad8e63e22c2411172c18da3251..dc517b48207589c35498def6637fec281e6bf4aa 100644 (file)
@@ -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}
index ad67dc127c0b86bacc96011ae4a2619571266b5c..b760dddcb3861e6cb4376b6a63864df10f526d6a 100644 (file)
@@ -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">
index 0c82855d3cd7d4c6a2ec3f6f201ed6db97ecefb1..e5c7d16e744b6a44e113599dd88ec01534810079 100644 (file)
@@ -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 }}>
index fc4327daf0e66a6b4f01838752d4fb944a3046fd..5d2d917ca882265af8a86faff74b82e09a62120e 100644 (file)
@@ -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}>
index 408ce85dd7f63202417b0ad5def03120bcfb554d..53172efdfa7c74da3615dd87defad19a227b87ce 100644 (file)
@@ -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
index f4de9edf9718a269f2b382f1e0da7985055b9c49..217491e689d801f85a15a9b51cfd3a68033285f2 100644 (file)
@@ -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">
index 1132b44df12358f57263ce1c27beda42cfb8ec0d..8709b942c7063e2c5db986580b9e0e98525b0ced 100644 (file)
@@ -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">
index af064a32e5f7773faaed358c89650e96e8eaa9a8..f09c1e07691907031824bd3f825f795eb55da97e 100644 (file)
@@ -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
index a64b8e91c9261b67e7e7e1ccec67c0e94ea2a24d..90c46354e4adcf8fcaf6933a4e2761c18fc9406b 100644 (file)
@@ -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
index 370b790d81c3ed053a5d6f35d62cfbea33c83c92..79dd87db2c93f8a65b6daff18e6aaee1b274af9e 100644 (file)
@@ -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)}
index 489bb9bad572cf7319ef774b483625371f27dc3d..30a4f632e52d8519b784ff66db07778176205263 100644 (file)
@@ -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' }}>
index a23faada860c02dd3fe97199dddf4419fef61851..e01d3ad500f418063cf55f2055ec9982cc936f18 100644 (file)
@@ -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
index a92a49f34c785fce38730dad9c114164ce1ccdc1..a2b31e8fb882f6e9b403e75ccb543c1cfcd74ae9 100644 (file)
@@ -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
index 4e7dd6afb9df8d1c2b9c10354960183e20d610b0..3cc425f249e96b9b2b40b2a14f45fa93e59cd67e 100644 (file)
@@ -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
index e9d9356a9dd3ec23d675b8141c769f77f19ea8dd..04f7e2ccef2f05af5c3ac2d5e97f4412e1c32981 100644 (file)
@@ -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 }}>
index 5de188d4e24d6abaad4b91d0253bdb7bfd5524c3..929dd1e919e3fff42c7fefa4faa3ee93dd83f992 100644 (file)
@@ -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) }}>
index fce623750cf9e3272d2446ac18fd9c5709f06d99..8ecc81a31e7a3acfc9e72329cd33ce57b2a3d83e 100644 (file)
@@ -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
index 8a96b84af7fdb9d3c4698388ec38e6ed9a445c79..70a9aa3487b3d950436fb3f1652f9535f53c23b6 100644 (file)
@@ -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 }}>
index 764e9686f51e9476e89aca00a751ee678546e184..fd578ec6cd5cf1d2e1513d8b9193ebee2bb901d2 100644 (file)
@@ -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">
index fe145b37ac5115b1bb747023da57436b1d89e858..84162fcc162baa2adae562d72aa3db07c0dc55c5 100644 (file)
@@ -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 />
index 2f7a583e3f247d9426ff7b9d67eb65be592be6a7..58d3f64f59f0b807c76a9cc7b2f8b37f3d2f533e 100644 (file)
@@ -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">
index 61c730f7a633f7c2bbc168a69d9d8a2c79a7ac95..aa267c5375edbd2134e71cbc3c4abbf0d9f5715a 100644 (file)
@@ -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
index 70d5c55589f6cfbb53acbf534e79f4b1d96b6824..6db366ac44eea934cd8f4647aaa06406557f8461 100644 (file)
@@ -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}>
index 728cf81077f01f736ad1365d6681b8a1e69c40bb..a6d7f553bddcbbf6007f3c7e9ae3e185b35d35a5 100644 (file)
@@ -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">
index c9d1d991f72011775f153fed5d58a9519bec75a6..0990a03fabbc7d54e33d8e753b44c42e8ff0b4c4 100644 (file)
@@ -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];
index 18201c98cbd281e6411326615f4a9ae193c4e052..a2a78106cd4b53f2bc966a61fd2fa4d06fcbb791 100644 (file)
@@ -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>
index b9dd14b7ad79fc4bb3ad7dc2085f8f154a6ad927..087d384d5f3f5afdaa47e706545466358298db77 100644 (file)
@@ -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>
index 6a1dbcb16cd66bbc469e0b591d68610abe91f475..c36adfc64696565cfe08c81d0e7beef5e4b30df0 100644 (file)
@@ -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);
index 673a5d9f15a6563e580496c3ade7f1b410ac236d..c62ef16016e3617cf7734a442107349922a233d2 100644 (file)
@@ -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)} />
index 67f8dc3f1c56d59ee15383872d5bb399796e6379..420e1a1697c64904ad98d88fbf94e2ecbf9bce44 100644 (file)
@@ -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">
index 9fe4e78eb121e5c47faabf155348208c133123df..1a81c225fcf718668622ea6d144995723f11e58a 100644 (file)
@@ -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">
index 33714191c462937414db34bcfeeb686e1a15daf0..76034cfe010ec7df6caef95e770ec53fef53c969 100644 (file)
@@ -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)} />
index 91dc02910f92057dc4bdfe18ea55bbd2789a270f..06e5f6c6be147519e11ac2b5ab508b82d8140a81 100644 (file)
@@ -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
index f25d7bdc7b330e4709660659075974ee9b108419..0beb17d856a4554d3e5edb38320df01255c59ec4 100644 (file)
@@ -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)} />
index 0b5135cae184623d3f83707e6b3655022c47cbad..41f7c64892d284fb99f2d14b59742023dd9eb5ef 100644 (file)
@@ -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)} />
index 99c04965f57dce3350c19973759853454e0a12ba..8635a35fd2b119dc3130af7c7c5ba6f27c9039aa 100644 (file)
@@ -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
index 8eab6b495bcdcae0caf8c3c63174a7febce5dfe1..e947373bdb4ab7ccd45b37d17626c9f19282a8bc 100644 (file)
@@ -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)} />
index 8ec435d3d01034b62fece87691195766356483ff..da3fe8cb5bdcf23c5775b3cb3176e451f45482b3 100644 (file)
@@ -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 (file)
index 856fcad..0000000
+++ /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 };