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:
parent
5dc613cd79
commit
d5ff55e23e
9 changed files with 65 additions and 45 deletions
|
@ -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>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -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}>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue