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
This commit is contained in:
Ajay Bura 2023-10-21 18:14:33 +11:00 committed by GitHub
parent 5dc613cd79
commit d5ff55e23e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 65 additions and 45 deletions

View file

@ -261,33 +261,22 @@ export function Toolbar() {
<BlockButton <BlockButton
format={BlockType.BlockQuote} format={BlockType.BlockQuote}
icon={Icons.BlockQuote} icon={Icons.BlockQuote}
tooltip={ tooltip={<BtnTooltip text="Block Quote" shortCode={`${modKey} + '`} />}
<BtnTooltip text="Block Quote" shortCode={`${modKey} + ${KeySymbol.Shift} + '`} />
}
/> />
<BlockButton <BlockButton
format={BlockType.CodeBlock} format={BlockType.CodeBlock}
icon={Icons.BlockCode} icon={Icons.BlockCode}
tooltip={ tooltip={<BtnTooltip text="Block Code" shortCode={`${modKey} + ;`} />}
<BtnTooltip text="Block Code" shortCode={`${modKey} + ${KeySymbol.Shift} + ;`} />
}
/> />
<BlockButton <BlockButton
format={BlockType.OrderedList} format={BlockType.OrderedList}
icon={Icons.OrderList} icon={Icons.OrderList}
tooltip={ tooltip={<BtnTooltip text="Ordered List" shortCode={`${modKey} + 7`} />}
<BtnTooltip text="Ordered List" shortCode={`${modKey} + ${KeySymbol.Shift} + 7`} />
}
/> />
<BlockButton <BlockButton
format={BlockType.UnorderedList} format={BlockType.UnorderedList}
icon={Icons.UnorderList} icon={Icons.UnorderList}
tooltip={ tooltip={<BtnTooltip text="Unordered List" shortCode={`${modKey} + 8`} />}
<BtnTooltip
text="Unordered List"
shortCode={`${modKey} + ${KeySymbol.Shift} + 8`}
/>
}
/> />
<HeadingBlockButton /> <HeadingBlockButton />
</Box> </Box>
@ -296,7 +285,9 @@ export function Toolbar() {
<Line variant="SurfaceVariant" direction="Vertical" style={{ height: toRem(12) }} /> <Line variant="SurfaceVariant" direction="Vertical" style={{ height: toRem(12) }} />
<Box shrink="No" gap="100"> <Box shrink="No" gap="100">
<ExitFormatting <ExitFormatting
tooltip={<BtnTooltip text="Exit Formatting" shortCode={`${modKey} + E`} />} tooltip={
<BtnTooltip text="Exit Formatting" shortCode={`Escape, ${modKey} + E`} />
}
/> />
</Box> </Box>
</> </>

View file

@ -1,6 +1,6 @@
import React, { ReactNode } from 'react'; import React, { ReactNode } from 'react';
import FocusTrap from 'focus-trap-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 { Header, Menu, Scroll, config } from 'folds';
import * as css from './AutocompleteMenu.css'; import * as css from './AutocompleteMenu.css';
@ -22,8 +22,8 @@ export function AutocompleteMenu({ headerContent, requestClose, children }: Auto
returnFocusOnDeactivate: false, returnFocusOnDeactivate: false,
clickOutsideDeactivates: true, clickOutsideDeactivates: true,
allowOutsideClick: true, allowOutsideClick: true,
isKeyForward: (evt: KeyboardEvent) => isHotkey('arrowdown', evt), isKeyForward: (evt: KeyboardEvent) => isKeyHotkey('arrowdown', evt),
isKeyBackward: (evt: KeyboardEvent) => isHotkey('arrowup', evt), isKeyBackward: (evt: KeyboardEvent) => isKeyHotkey('arrowup', evt),
}} }}
> >
<Menu className={css.AutocompleteMenu}> <Menu className={css.AutocompleteMenu}>

View file

@ -1,6 +1,6 @@
import { isHotkey } from 'is-hotkey'; import { isKeyHotkey } from 'is-hotkey';
import { KeyboardEvent } from 'react'; import { KeyboardEvent } from 'react';
import { Editor } from 'slate'; import { Editor, Range } from 'slate';
import { isAnyMarkActive, isBlockActive, removeAllMark, toggleBlock, toggleMark } from './utils'; import { isAnyMarkActive, isBlockActive, removeAllMark, toggleBlock, toggleMark } from './utils';
import { BlockType, MarkType } from './types'; import { BlockType, MarkType } from './types';
@ -15,10 +15,10 @@ export const INLINE_HOTKEYS: Record<string, MarkType> = {
const INLINE_KEYS = Object.keys(INLINE_HOTKEYS); const INLINE_KEYS = Object.keys(INLINE_HOTKEYS);
export const BLOCK_HOTKEYS: Record<string, BlockType> = { export const BLOCK_HOTKEYS: Record<string, BlockType> = {
'mod+shift+7': BlockType.OrderedList, 'mod+7': BlockType.OrderedList,
'mod+shift+8': BlockType.UnorderedList, 'mod+8': BlockType.UnorderedList,
"mod+shift+'": BlockType.BlockQuote, "mod+'": BlockType.BlockQuote,
'mod+shift+;': BlockType.CodeBlock, 'mod+;': BlockType.CodeBlock,
}; };
const BLOCK_KEYS = Object.keys(BLOCK_HOTKEYS); 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. * @return boolean true if shortcut is toggled.
*/ */
export const toggleKeyboardShortcut = (editor: Editor, event: KeyboardEvent<Element>): boolean => { export const toggleKeyboardShortcut = (editor: Editor, event: KeyboardEvent<Element>): 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)) { if (isAnyMarkActive(editor)) {
removeAllMark(editor); removeAllMark(editor);
return true; return true;
@ -40,7 +69,7 @@ export const toggleKeyboardShortcut = (editor: Editor, event: KeyboardEvent<Elem
} }
const blockToggled = BLOCK_KEYS.find((hotkey) => { const blockToggled = BLOCK_KEYS.find((hotkey) => {
if (isHotkey(hotkey, event)) { if (isKeyHotkey(hotkey, event)) {
event.preventDefault(); event.preventDefault();
toggleBlock(editor, BLOCK_HOTKEYS[hotkey]); toggleBlock(editor, BLOCK_HOTKEYS[hotkey]);
return true; return true;
@ -52,7 +81,7 @@ export const toggleKeyboardShortcut = (editor: Editor, event: KeyboardEvent<Elem
const inlineToggled = isBlockActive(editor, BlockType.CodeBlock) const inlineToggled = isBlockActive(editor, BlockType.CodeBlock)
? false ? false
: INLINE_KEYS.find((hotkey) => { : INLINE_KEYS.find((hotkey) => {
if (isHotkey(hotkey, event)) { if (isKeyHotkey(hotkey, event)) {
event.preventDefault(); event.preventDefault();
toggleMark(editor, INLINE_HOTKEYS[hotkey]); toggleMark(editor, INLINE_HOTKEYS[hotkey]);
return true; return true;

View file

@ -28,7 +28,7 @@ import {
toRem, toRem,
} from 'folds'; } from 'folds';
import FocusTrap from 'focus-trap-react'; import FocusTrap from 'focus-trap-react';
import isHotkey from 'is-hotkey'; import { isKeyHotkey } from 'is-hotkey';
import classNames from 'classnames'; import classNames from 'classnames';
import { MatrixClient, Room } from 'matrix-js-sdk'; import { MatrixClient, Room } from 'matrix-js-sdk';
import { atom, useAtomValue, useSetAtom } from 'jotai'; import { atom, useAtomValue, useSetAtom } from 'jotai';
@ -769,9 +769,9 @@ export function EmojiBoard({
clickOutsideDeactivates: true, clickOutsideDeactivates: true,
allowOutsideClick: true, allowOutsideClick: true,
isKeyForward: (evt: KeyboardEvent) => isKeyForward: (evt: KeyboardEvent) =>
!editableActiveElement() && isHotkey(['arrowdown', 'arrowright'], evt), !editableActiveElement() && isKeyHotkey(['arrowdown', 'arrowright'], evt),
isKeyBackward: (evt: KeyboardEvent) => isKeyBackward: (evt: KeyboardEvent) =>
!editableActiveElement() && isHotkey(['arrowup', 'arrowleft'], evt), !editableActiveElement() && isKeyHotkey(['arrowup', 'arrowleft'], evt),
}} }}
> >
<EmojiBoardLayout <EmojiBoardLayout

View file

@ -9,7 +9,7 @@ import React, {
useState, useState,
} from 'react'; } from 'react';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
import isHotkey from 'is-hotkey'; import { isKeyHotkey } from 'is-hotkey';
import { EventType, IContent, MsgType, Room } from 'matrix-js-sdk'; import { EventType, IContent, MsgType, Room } from 'matrix-js-sdk';
import { ReactEditor } from 'slate-react'; import { ReactEditor } from 'slate-react';
import { Transforms, Editor } from 'slate'; import { Transforms, Editor } from 'slate';
@ -319,11 +319,11 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
const handleKeyDown: KeyboardEventHandler = useCallback( const handleKeyDown: KeyboardEventHandler = useCallback(
(evt) => { (evt) => {
if (enterForNewline ? isHotkey('shift+enter', evt) : isHotkey('enter', evt)) { if (enterForNewline ? isKeyHotkey('shift+enter', evt) : isKeyHotkey('enter', evt)) {
evt.preventDefault(); evt.preventDefault();
submit(); submit();
} }
if (isHotkey('escape', evt)) { if (isKeyHotkey('escape', evt)) {
evt.preventDefault(); evt.preventDefault();
setReplyDraft(); setReplyDraft();
} }
@ -333,7 +333,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
const handleKeyUp: KeyboardEventHandler = useCallback( const handleKeyUp: KeyboardEventHandler = useCallback(
(evt) => { (evt) => {
if (isHotkey('escape', evt)) { if (isKeyHotkey('escape', evt)) {
evt.preventDefault(); evt.preventDefault();
return; return;
} }

View file

@ -43,7 +43,7 @@ import {
config, config,
toRem, toRem,
} from 'folds'; } from 'folds';
import isHotkey from 'is-hotkey'; import { isKeyHotkey } from 'is-hotkey';
import Linkify from 'linkify-react'; import Linkify from 'linkify-react';
import { import {
decryptFile, decryptFile,
@ -725,7 +725,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
useCallback( useCallback(
(evt) => { (evt) => {
if ( if (
isHotkey('arrowup', evt) && isKeyHotkey('arrowup', evt) &&
editableActiveElement() && editableActiveElement() &&
document.activeElement?.getAttribute('data-editable-name') === 'RoomInput' && document.activeElement?.getAttribute('data-editable-name') === 'RoomInput' &&
isEmptyEditor(editor) isEmptyEditor(editor)

View file

@ -3,7 +3,7 @@ import { Box, Chip, Icon, IconButton, Icons, Line, PopOut, Spinner, Text, as, co
import { Editor, Transforms } from 'slate'; import { Editor, Transforms } from 'slate';
import { ReactEditor } from 'slate-react'; import { ReactEditor } from 'slate-react';
import { IContent, MatrixEvent, RelationType, Room } from 'matrix-js-sdk'; import { IContent, MatrixEvent, RelationType, Room } from 'matrix-js-sdk';
import isHotkey from 'is-hotkey'; import { isKeyHotkey } from 'is-hotkey';
import { import {
AUTOCOMPLETE_PREFIXES, AUTOCOMPLETE_PREFIXES,
AutocompletePrefix, AutocompletePrefix,
@ -120,11 +120,11 @@ export const MessageEditor = as<'div', MessageEditorProps>(
const handleKeyDown: KeyboardEventHandler = useCallback( const handleKeyDown: KeyboardEventHandler = useCallback(
(evt) => { (evt) => {
if (enterForNewline ? isHotkey('shift+enter', evt) : isHotkey('enter', evt)) { if (enterForNewline ? isKeyHotkey('shift+enter', evt) : isKeyHotkey('enter', evt)) {
evt.preventDefault(); evt.preventDefault();
handleSave(); handleSave();
} }
if (isHotkey('escape', evt)) { if (isKeyHotkey('escape', evt)) {
evt.preventDefault(); evt.preventDefault();
onCancel(); onCancel();
} }
@ -134,7 +134,7 @@ export const MessageEditor = as<'div', MessageEditorProps>(
const handleKeyUp: KeyboardEventHandler = useCallback( const handleKeyUp: KeyboardEventHandler = useCallback(
(evt) => { (evt) => {
if (isHotkey('escape', evt)) { if (isKeyHotkey('escape', evt)) {
evt.preventDefault(); evt.preventDefault();
return; return;
} }

View file

@ -28,7 +28,7 @@ export interface Settings {
const defaultSettings: Settings = { const defaultSettings: Settings = {
themeIndex: 0, themeIndex: 0,
useSystemTheme: true, useSystemTheme: true,
isMarkdown: true, isMarkdown: false,
editorToolbar: false, editorToolbar: false,
useSystemEmoji: false, useSystemEmoji: false,

View file

@ -1,4 +1,4 @@
import isHotkey from 'is-hotkey'; import { isKeyHotkey } from 'is-hotkey';
import { KeyboardEventHandler } from 'react'; import { KeyboardEventHandler } from 'react';
export interface KeyboardEventLike { export interface KeyboardEventLike {
@ -12,14 +12,14 @@ export interface KeyboardEventLike {
} }
export const onTabPress = (evt: KeyboardEventLike, callback: () => void) => { export const onTabPress = (evt: KeyboardEventLike, callback: () => void) => {
if (isHotkey('tab', evt)) { if (isKeyHotkey('tab', evt)) {
evt.preventDefault(); evt.preventDefault();
callback(); callback();
} }
}; };
export const preventScrollWithArrowKey: KeyboardEventHandler = (evt) => { export const preventScrollWithArrowKey: KeyboardEventHandler = (evt) => {
if (isHotkey(['arrowup', 'arrowright', 'arrowdown', 'arrowleft'], evt)) { if (isKeyHotkey(['arrowup', 'arrowright', 'arrowdown', 'arrowleft'], evt)) {
evt.preventDefault(); evt.preventDefault();
} }
}; };