From bc5e7445d9cb1ff2ad71981b8d405c221aacbd34 Mon Sep 17 00:00:00 2001 From: Ajay Bura <32841439+ajbura@users.noreply.github.com> Date: Fri, 16 Jun 2023 11:09:09 +1000 Subject: [PATCH] Add ESC btn to toolbar to quickly exit formatting (#1283) * Add ESC btn to toolbar to quickly exit formatting * add horizontal scroll to toolbar item * make editor toolbar usable in touch device * fix editor hotkeys not working in window * remove unused import --- src/app/components/editor/Editor.css.ts | 14 +- src/app/components/editor/Editor.tsx | 8 +- src/app/components/editor/Toolbar.tsx | 225 +++++++++++++++--------- src/app/components/editor/common.ts | 4 +- src/app/components/editor/keyboard.ts | 6 +- src/app/organisms/room/RoomInput.tsx | 58 ++++-- src/app/utils/key-symbol.ts | 3 + 7 files changed, 210 insertions(+), 108 deletions(-) diff --git a/src/app/components/editor/Editor.css.ts b/src/app/components/editor/Editor.css.ts index 034ded7..9ec8cfa 100644 --- a/src/app/components/editor/Editor.css.ts +++ b/src/app/components/editor/Editor.css.ts @@ -43,6 +43,7 @@ export const EditorPlaceholder = style([ { position: 'absolute', zIndex: 1, + width: '100%', opacity: config.opacity.Placeholder, pointerEvents: 'none', userSelect: 'none', @@ -55,9 +56,10 @@ export const EditorPlaceholder = style([ }, ]); -export const EditorToolbar = style([ - DefaultReset, - { - padding: config.space.S100, - }, -]); +export const EditorToolbarBase = style({ + padding: `0 ${config.borderWidth.B300}`, +}); + +export const EditorToolbar = style({ + padding: config.space.S100, +}); diff --git a/src/app/components/editor/Editor.tsx b/src/app/components/editor/Editor.tsx index 2657b21..3f048a6 100644 --- a/src/app/components/editor/Editor.tsx +++ b/src/app/components/editor/Editor.tsx @@ -104,7 +104,13 @@ export const CustomEditor = forwardRef( // eslint-disable-next-line @typescript-eslint/no-unused-vars const { style, ...props } = attributes; return ( - + {children} ); diff --git a/src/app/components/editor/Toolbar.tsx b/src/app/components/editor/Toolbar.tsx index a84fca2..72e2c38 100644 --- a/src/app/components/editor/Toolbar.tsx +++ b/src/app/components/editor/Toolbar.tsx @@ -10,6 +10,7 @@ import { Line, Menu, PopOut, + Scroll, Text, Tooltip, TooltipProvider, @@ -17,7 +18,14 @@ import { } from 'folds'; import React, { ReactNode, useState } from 'react'; import { ReactEditor, useSlate } from 'slate-react'; -import { isBlockActive, isMarkActive, toggleBlock, toggleMark } from './common'; +import { + isAnyMarkActive, + isBlockActive, + isMarkActive, + removeAllMark, + toggleBlock, + toggleMark, +} from './common'; import * as css from './Editor.css'; import { BlockType, MarkType } from './Elements'; import { HeadingLevel } from './slate'; @@ -44,6 +52,11 @@ function BtnTooltip({ text, shortCode }: { text: string; shortCode?: string }) { type MarkButtonProps = { format: MarkType; icon: IconSrc; tooltip: ReactNode }; export function MarkButton({ format, icon, tooltip }: MarkButtonProps) { const editor = useSlate(); + const disableInline = isBlockActive(editor, BlockType.CodeBlock); + + if (disableInline) { + removeAllMark(editor); + } const handleClick = () => { toggleMark(editor, format); @@ -58,10 +71,11 @@ export function MarkButton({ format, icon, tooltip }: MarkButtonProps) { variant="SurfaceVariant" onClick={handleClick} aria-pressed={isMarkActive(editor, format)} - size="300" + size="400" radii="300" + disabled={disableInline} > - + )} @@ -89,10 +103,10 @@ export function BlockButton({ format, icon, tooltip }: BlockButtonProps) { variant="SurfaceVariant" onClick={handleClick} aria-pressed={isBlockActive(editor, format)} - size="300" + size="400" radii="300" > - + )} @@ -115,6 +129,7 @@ export function HeadingBlockButton() { return ( - handleMenuSelect(1)} size="300" radii="300"> - + handleMenuSelect(1)} size="400" radii="300"> + - handleMenuSelect(2)} size="300" radii="300"> - + handleMenuSelect(2)} size="400" radii="300"> + - handleMenuSelect(3)} size="300" radii="300"> - + handleMenuSelect(3)} size="400" radii="300"> + @@ -151,97 +166,143 @@ export function HeadingBlockButton() { variant="SurfaceVariant" onClick={() => (isActive ? toggleBlock(editor, BlockType.Heading) : setOpen(!open))} aria-pressed={isActive} - size="300" + size="400" radii="300" > - - + + )} ); } -export function Toolbar() { +type ExitFormattingProps = { tooltip: ReactNode }; +export function ExitFormatting({ tooltip }: ExitFormattingProps) { const editor = useSlate(); - const allowInline = !isBlockActive(editor, BlockType.CodeBlock); - const modKey = isMacOS() ? KeySymbol.Command : 'Ctrl'; + + const handleClick = () => { + if (isAnyMarkActive(editor)) { + removeAllMark(editor); + } else if (!isBlockActive(editor, BlockType.Paragraph)) { + toggleBlock(editor, BlockType.Paragraph); + } + ReactEditor.focus(editor); + }; return ( - - - - - } - /> - - } - /> - - } - /> - - } - /> - - {allowInline && ( - <> - - - } + + {(triggerRef) => ( + + {`Exit ${KeySymbol.Hyper}`} + + )} + + ); +} + +export function Toolbar() { + const editor = useSlate(); + const modKey = isMacOS() ? KeySymbol.Command : 'Ctrl'; + + const canEscape = isAnyMarkActive(editor) || !isBlockActive(editor, BlockType.Paragraph); + + return ( + + + + <> + + } + /> + } + /> + } + /> + + } + /> + } + /> + } + /> + + + + + + } /> - } + + } /> - } + + } /> - } /> - } - /> - } - /> + - - )} + {canEscape && ( + <> + + + } + /> + + + )} + + ); } diff --git a/src/app/components/editor/common.ts b/src/app/components/editor/common.ts index 619a1bf..2f20790 100644 --- a/src/app/components/editor/common.ts +++ b/src/app/components/editor/common.ts @@ -32,7 +32,9 @@ export const toggleMark = (editor: Editor, format: MarkType) => { }; export const removeAllMark = (editor: Editor) => { - ALL_MARK_TYPE.forEach((mark) => Editor.removeMark(editor, mark)); + ALL_MARK_TYPE.forEach((mark) => { + if (isMarkActive(editor, mark)) Editor.removeMark(editor, mark); + }); }; export const isBlockActive = (editor: Editor, format: BlockType) => { diff --git a/src/app/components/editor/keyboard.ts b/src/app/components/editor/keyboard.ts index 3fbe536..b6e1c3f 100644 --- a/src/app/components/editor/keyboard.ts +++ b/src/app/components/editor/keyboard.ts @@ -15,7 +15,7 @@ export const INLINE_HOTKEYS: Record = { const INLINE_KEYS = Object.keys(INLINE_HOTKEYS); export const BLOCK_HOTKEYS: Record = { - 'mod+shift+0': BlockType.OrderedList, + 'mod+shift+7': BlockType.OrderedList, 'mod+shift+8': BlockType.UnorderedList, "mod+shift+'": BlockType.BlockQuote, 'mod+shift+;': BlockType.CodeBlock, @@ -26,12 +26,12 @@ const BLOCK_KEYS = Object.keys(BLOCK_HOTKEYS); * @return boolean true if shortcut is toggled. */ export const toggleKeyboardShortcut = (editor: Editor, event: KeyboardEvent): boolean => { - if (isHotkey('escape', event)) { + if (isHotkey('mod+e', event)) { if (isAnyMarkActive(editor)) { removeAllMark(editor); return true; } - console.log(isBlockActive(editor, BlockType.Paragraph)); + if (!isBlockActive(editor, BlockType.Paragraph)) { toggleBlock(editor, BlockType.Paragraph); return true; diff --git a/src/app/organisms/room/RoomInput.tsx b/src/app/organisms/room/RoomInput.tsx index e79f488..3a22b57 100644 --- a/src/app/organisms/room/RoomInput.tsx +++ b/src/app/organisms/room/RoomInput.tsx @@ -19,6 +19,7 @@ import { Icon, IconButton, Icons, + Line, Overlay, OverlayBackdrop, OverlayCenter, @@ -95,6 +96,7 @@ import { MessageReply } from '../../molecules/message/Message'; import colorMXID from '../../../util/colorMXID'; import { parseReplyBody, parseReplyFormattedBody } from '../../utils/room'; import { sanitizeText } from '../../utils/sanitize'; +import { getResizeObserverEntry, useResizeObserver } from '../../hooks/useResizeObserver'; interface RoomInputProps { roomViewRef: RefObject; @@ -158,6 +160,16 @@ export const RoomInput = forwardRef( const handlePaste = useFilePasteHandler(handleFiles); const dropZoneVisible = useFileDropZone(roomViewRef, handleFiles); + const [mobile, setMobile] = useState(document.body.clientWidth < 500); + useResizeObserver( + document.body, + useCallback((entries) => { + const bodyEntry = getResizeObserverEntry(document.body, entries); + if (bodyEntry && bodyEntry.contentRect.width < 500) setMobile(true); + else setMobile(false); + }, []) + ); + useEffect(() => { Transforms.insertFragment(editor, msgDraft); }, [editor, msgDraft]); @@ -500,27 +512,36 @@ export const RoomInput = forwardRef( > {(anchorRef) => ( <> - setEmojiBoardTab(EmojiBoardTab.Sticker)} - variant="SurfaceVariant" - size="300" - radii="300" - > - - + {!mobile && ( + setEmojiBoardTab(EmojiBoardTab.Sticker)} + variant="SurfaceVariant" + size="300" + radii="300" + > + + + )} setEmojiBoardTab(EmojiBoardTab.Emoji)} variant="SurfaceVariant" size="300" radii="300" > - + )} @@ -532,7 +553,14 @@ export const RoomInput = forwardRef( } - bottom={toolbar && } + bottom={ + toolbar && ( +
+ + +
+ ) + } /> ); diff --git a/src/app/utils/key-symbol.ts b/src/app/utils/key-symbol.ts index 7e758fd..00dd104 100644 --- a/src/app/utils/key-symbol.ts +++ b/src/app/utils/key-symbol.ts @@ -3,4 +3,7 @@ export enum KeySymbol { Shift = '⇧', Option = '⌥', Control = '⌃', + Hyper = '✦', + Super = '❖', + Escape = '⎋', }