From d5ff55e23ed0ade6979e2d87bb48be9349e1ef98 Mon Sep 17 00:00:00 2001 From: Ajay Bura <32841439+ajbura@users.noreply.github.com> Date: Sat, 21 Oct 2023 18:14:33 +1100 Subject: [PATCH] Fix hotkeys (#1468) * use hotkey using key instead of which (default) * remove shift from block formatting hotkeys * smartly exit formatting with backspace * set markdown to off by default * exit formatting with escape --- src/app/components/editor/Toolbar.tsx | 23 +++------ .../editor/autocomplete/AutocompleteMenu.tsx | 6 +-- src/app/components/editor/keyboard.ts | 47 +++++++++++++++---- src/app/components/emoji-board/EmojiBoard.tsx | 6 +-- src/app/organisms/room/RoomInput.tsx | 8 ++-- src/app/organisms/room/RoomTimeline.tsx | 4 +- .../organisms/room/message/MessageEditor.tsx | 8 ++-- src/app/state/settings.ts | 2 +- src/app/utils/keyboard.ts | 6 +-- 9 files changed, 65 insertions(+), 45 deletions(-) diff --git a/src/app/components/editor/Toolbar.tsx b/src/app/components/editor/Toolbar.tsx index 342dd10..766a1d8 100644 --- a/src/app/components/editor/Toolbar.tsx +++ b/src/app/components/editor/Toolbar.tsx @@ -261,33 +261,22 @@ export function Toolbar() { - } + tooltip={} /> - } + tooltip={} /> - } + tooltip={} /> - } + tooltip={} /> @@ -296,7 +285,9 @@ export function Toolbar() { } + tooltip={ + + } /> diff --git a/src/app/components/editor/autocomplete/AutocompleteMenu.tsx b/src/app/components/editor/autocomplete/AutocompleteMenu.tsx index e7c8df3..fc4327d 100644 --- a/src/app/components/editor/autocomplete/AutocompleteMenu.tsx +++ b/src/app/components/editor/autocomplete/AutocompleteMenu.tsx @@ -1,6 +1,6 @@ import React, { ReactNode } from 'react'; import FocusTrap from 'focus-trap-react'; -import isHotkey from 'is-hotkey'; +import { isKeyHotkey } from 'is-hotkey'; import { Header, Menu, Scroll, config } from 'folds'; import * as css from './AutocompleteMenu.css'; @@ -22,8 +22,8 @@ export function AutocompleteMenu({ headerContent, requestClose, children }: Auto returnFocusOnDeactivate: false, clickOutsideDeactivates: true, allowOutsideClick: true, - isKeyForward: (evt: KeyboardEvent) => isHotkey('arrowdown', evt), - isKeyBackward: (evt: KeyboardEvent) => isHotkey('arrowup', evt), + isKeyForward: (evt: KeyboardEvent) => isKeyHotkey('arrowdown', evt), + isKeyBackward: (evt: KeyboardEvent) => isKeyHotkey('arrowup', evt), }} > diff --git a/src/app/components/editor/keyboard.ts b/src/app/components/editor/keyboard.ts index b6d4d69..7031749 100644 --- a/src/app/components/editor/keyboard.ts +++ b/src/app/components/editor/keyboard.ts @@ -1,6 +1,6 @@ -import { isHotkey } from 'is-hotkey'; +import { isKeyHotkey } from 'is-hotkey'; import { KeyboardEvent } from 'react'; -import { Editor } from 'slate'; +import { Editor, Range } from 'slate'; import { isAnyMarkActive, isBlockActive, removeAllMark, toggleBlock, toggleMark } from './utils'; import { BlockType, MarkType } from './types'; @@ -15,10 +15,10 @@ export const INLINE_HOTKEYS: Record = { const INLINE_KEYS = Object.keys(INLINE_HOTKEYS); export const BLOCK_HOTKEYS: Record = { - 'mod+shift+7': BlockType.OrderedList, - 'mod+shift+8': BlockType.UnorderedList, - "mod+shift+'": BlockType.BlockQuote, - 'mod+shift+;': BlockType.CodeBlock, + 'mod+7': BlockType.OrderedList, + 'mod+8': BlockType.UnorderedList, + "mod+'": BlockType.BlockQuote, + 'mod+;': BlockType.CodeBlock, }; const BLOCK_KEYS = Object.keys(BLOCK_HOTKEYS); @@ -26,7 +26,36 @@ const BLOCK_KEYS = Object.keys(BLOCK_HOTKEYS); * @return boolean true if shortcut is toggled. */ export const toggleKeyboardShortcut = (editor: Editor, event: KeyboardEvent): boolean => { - if (isHotkey('mod+e', event)) { + if (isKeyHotkey('backspace', event) && editor.selection && Range.isCollapsed(editor.selection)) { + const startPoint = Range.start(editor.selection); + if (startPoint.offset !== 0) return false; + + const [parentNode, parentPath] = Editor.parent(editor, startPoint); + + if (Editor.isEditor(parentNode)) return false; + + if (parentNode.type === BlockType.Heading) { + toggleBlock(editor, BlockType.Paragraph); + return true; + } + if ( + parentNode.type === BlockType.CodeLine || + parentNode.type === BlockType.QuoteLine || + parentNode.type === BlockType.ListItem + ) { + // exit formatting only when line block + // is first of last of it's parent + const parentLocation = { at: parentPath }; + const [previousNode] = Editor.previous(editor, parentLocation) ?? []; + const [nextNode] = Editor.next(editor, parentLocation) ?? []; + if (!previousNode || !nextNode) { + toggleBlock(editor, BlockType.Paragraph); + return true; + } + } + } + + if (isKeyHotkey('mod+e', event) || isKeyHotkey('escape', event)) { if (isAnyMarkActive(editor)) { removeAllMark(editor); return true; @@ -40,7 +69,7 @@ export const toggleKeyboardShortcut = (editor: Editor, event: KeyboardEvent { - if (isHotkey(hotkey, event)) { + if (isKeyHotkey(hotkey, event)) { event.preventDefault(); toggleBlock(editor, BLOCK_HOTKEYS[hotkey]); return true; @@ -52,7 +81,7 @@ export const toggleKeyboardShortcut = (editor: Editor, event: KeyboardEvent { - if (isHotkey(hotkey, event)) { + if (isKeyHotkey(hotkey, event)) { event.preventDefault(); toggleMark(editor, INLINE_HOTKEYS[hotkey]); return true; diff --git a/src/app/components/emoji-board/EmojiBoard.tsx b/src/app/components/emoji-board/EmojiBoard.tsx index 94ba14c..52df925 100644 --- a/src/app/components/emoji-board/EmojiBoard.tsx +++ b/src/app/components/emoji-board/EmojiBoard.tsx @@ -28,7 +28,7 @@ import { toRem, } from 'folds'; import FocusTrap from 'focus-trap-react'; -import isHotkey from 'is-hotkey'; +import { isKeyHotkey } from 'is-hotkey'; import classNames from 'classnames'; import { MatrixClient, Room } from 'matrix-js-sdk'; import { atom, useAtomValue, useSetAtom } from 'jotai'; @@ -769,9 +769,9 @@ export function EmojiBoard({ clickOutsideDeactivates: true, allowOutsideClick: true, isKeyForward: (evt: KeyboardEvent) => - !editableActiveElement() && isHotkey(['arrowdown', 'arrowright'], evt), + !editableActiveElement() && isKeyHotkey(['arrowdown', 'arrowright'], evt), isKeyBackward: (evt: KeyboardEvent) => - !editableActiveElement() && isHotkey(['arrowup', 'arrowleft'], evt), + !editableActiveElement() && isKeyHotkey(['arrowup', 'arrowleft'], evt), }} > ( const handleKeyDown: KeyboardEventHandler = useCallback( (evt) => { - if (enterForNewline ? isHotkey('shift+enter', evt) : isHotkey('enter', evt)) { + if (enterForNewline ? isKeyHotkey('shift+enter', evt) : isKeyHotkey('enter', evt)) { evt.preventDefault(); submit(); } - if (isHotkey('escape', evt)) { + if (isKeyHotkey('escape', evt)) { evt.preventDefault(); setReplyDraft(); } @@ -333,7 +333,7 @@ export const RoomInput = forwardRef( const handleKeyUp: KeyboardEventHandler = useCallback( (evt) => { - if (isHotkey('escape', evt)) { + if (isKeyHotkey('escape', evt)) { evt.preventDefault(); return; } diff --git a/src/app/organisms/room/RoomTimeline.tsx b/src/app/organisms/room/RoomTimeline.tsx index 2a22245..0852ecf 100644 --- a/src/app/organisms/room/RoomTimeline.tsx +++ b/src/app/organisms/room/RoomTimeline.tsx @@ -43,7 +43,7 @@ import { config, toRem, } from 'folds'; -import isHotkey from 'is-hotkey'; +import { isKeyHotkey } from 'is-hotkey'; import Linkify from 'linkify-react'; import { decryptFile, @@ -725,7 +725,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli useCallback( (evt) => { if ( - isHotkey('arrowup', evt) && + isKeyHotkey('arrowup', evt) && editableActiveElement() && document.activeElement?.getAttribute('data-editable-name') === 'RoomInput' && isEmptyEditor(editor) diff --git a/src/app/organisms/room/message/MessageEditor.tsx b/src/app/organisms/room/message/MessageEditor.tsx index f38cfbe..385042e 100644 --- a/src/app/organisms/room/message/MessageEditor.tsx +++ b/src/app/organisms/room/message/MessageEditor.tsx @@ -3,7 +3,7 @@ import { Box, Chip, Icon, IconButton, Icons, Line, PopOut, Spinner, Text, as, co import { Editor, Transforms } from 'slate'; import { ReactEditor } from 'slate-react'; import { IContent, MatrixEvent, RelationType, Room } from 'matrix-js-sdk'; -import isHotkey from 'is-hotkey'; +import { isKeyHotkey } from 'is-hotkey'; import { AUTOCOMPLETE_PREFIXES, AutocompletePrefix, @@ -120,11 +120,11 @@ export const MessageEditor = as<'div', MessageEditorProps>( const handleKeyDown: KeyboardEventHandler = useCallback( (evt) => { - if (enterForNewline ? isHotkey('shift+enter', evt) : isHotkey('enter', evt)) { + if (enterForNewline ? isKeyHotkey('shift+enter', evt) : isKeyHotkey('enter', evt)) { evt.preventDefault(); handleSave(); } - if (isHotkey('escape', evt)) { + if (isKeyHotkey('escape', evt)) { evt.preventDefault(); onCancel(); } @@ -134,7 +134,7 @@ export const MessageEditor = as<'div', MessageEditorProps>( const handleKeyUp: KeyboardEventHandler = useCallback( (evt) => { - if (isHotkey('escape', evt)) { + if (isKeyHotkey('escape', evt)) { evt.preventDefault(); return; } diff --git a/src/app/state/settings.ts b/src/app/state/settings.ts index 7770a75..26e3431 100644 --- a/src/app/state/settings.ts +++ b/src/app/state/settings.ts @@ -28,7 +28,7 @@ export interface Settings { const defaultSettings: Settings = { themeIndex: 0, useSystemTheme: true, - isMarkdown: true, + isMarkdown: false, editorToolbar: false, useSystemEmoji: false, diff --git a/src/app/utils/keyboard.ts b/src/app/utils/keyboard.ts index 56eeb9f..78aa252 100644 --- a/src/app/utils/keyboard.ts +++ b/src/app/utils/keyboard.ts @@ -1,4 +1,4 @@ -import isHotkey from 'is-hotkey'; +import { isKeyHotkey } from 'is-hotkey'; import { KeyboardEventHandler } from 'react'; export interface KeyboardEventLike { @@ -12,14 +12,14 @@ export interface KeyboardEventLike { } export const onTabPress = (evt: KeyboardEventLike, callback: () => void) => { - if (isHotkey('tab', evt)) { + if (isKeyHotkey('tab', evt)) { evt.preventDefault(); callback(); } }; export const preventScrollWithArrowKey: KeyboardEventHandler = (evt) => { - if (isHotkey(['arrowup', 'arrowright', 'arrowdown', 'arrowleft'], evt)) { + if (isKeyHotkey(['arrowup', 'arrowright', 'arrowdown', 'arrowleft'], evt)) { evt.preventDefault(); } };