Room input improvements (#1502)

* prevent context menu when editing message

* send sticker body (#1479)

* update emojiboard search text reaction input label

* stop generating upload image thumbnail (#1475)

* maintain upload order

* Fix message options spinner variant

* add markdown toggle in editor toolbar

* fix heading toggle icon update with cursor move

* add hotkeys for heading

* change editor markdown btn style

* use Ctrl + Enter to send message (#1470)

* fix reaction tooltip word-break

* add shift in editor hokeys with number

* stop parsing markdown in link
This commit is contained in:
Ajay Bura 2023-10-25 16:50:38 +11:00 committed by GitHub
parent c7e5c1fce8
commit 2957a45c4b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 162 additions and 71 deletions

View file

@ -66,3 +66,7 @@ export const EditorToolbarBase = style({
export const EditorToolbar = style({
padding: config.space.S100,
});
export const MarkdownBtnBox = style({
paddingRight: config.space.S100,
});

View file

@ -19,6 +19,7 @@ import {
import React, { ReactNode, useState } from 'react';
import { ReactEditor, useSlate } from 'slate-react';
import {
headingLevel,
isAnyMarkActive,
isBlockActive,
isMarkActive,
@ -31,6 +32,8 @@ import { BlockType, MarkType } from './types';
import { HeadingLevel } from './slate';
import { isMacOS } from '../../utils/user-agent';
import { KeySymbol } from '../../utils/key-symbol';
import { useSetting } from '../../state/hooks/settings';
import { settingsAtom } from '../../state/settings';
function BtnTooltip({ text, shortCode }: { text: string; shortCode?: string }) {
return (
@ -115,13 +118,13 @@ export function BlockButton({ format, icon, tooltip }: BlockButtonProps) {
export function HeadingBlockButton() {
const editor = useSlate();
const [level, setLevel] = useState<HeadingLevel>(1);
const level = headingLevel(editor);
const [open, setOpen] = useState(false);
const isActive = isBlockActive(editor, BlockType.Heading);
const modKey = isMacOS() ? KeySymbol.Command : 'Ctrl';
const handleMenuSelect = (selectedLevel: HeadingLevel) => {
setOpen(false);
setLevel(selectedLevel);
toggleBlock(editor, BlockType.Heading, { level: selectedLevel });
ReactEditor.focus(editor);
};
@ -130,7 +133,6 @@ export function HeadingBlockButton() {
<PopOut
open={open}
offset={5}
align="Start"
position="Top"
content={
<FocusTrap
@ -145,15 +147,51 @@ export function HeadingBlockButton() {
>
<Menu style={{ padding: config.space.S100 }}>
<Box gap="100">
<IconButton onClick={() => handleMenuSelect(1)} size="400" radii="300">
<Icon size="200" src={Icons.Heading1} />
</IconButton>
<IconButton onClick={() => handleMenuSelect(2)} size="400" radii="300">
<Icon size="200" src={Icons.Heading2} />
</IconButton>
<IconButton onClick={() => handleMenuSelect(3)} size="400" radii="300">
<Icon size="200" src={Icons.Heading3} />
</IconButton>
<TooltipProvider
tooltip={<BtnTooltip text="Heading 1" shortCode={`${modKey} + Shift + 1`} />}
delay={500}
>
{(triggerRef) => (
<IconButton
ref={triggerRef}
onClick={() => handleMenuSelect(1)}
size="400"
radii="300"
>
<Icon size="200" src={Icons.Heading1} />
</IconButton>
)}
</TooltipProvider>
<TooltipProvider
tooltip={<BtnTooltip text="Heading 2" shortCode={`${modKey} + Shift + 2`} />}
delay={500}
>
{(triggerRef) => (
<IconButton
ref={triggerRef}
onClick={() => handleMenuSelect(2)}
size="400"
radii="300"
>
<Icon size="200" src={Icons.Heading2} />
</IconButton>
)}
</TooltipProvider>
<TooltipProvider
tooltip={<BtnTooltip text="Heading 3" shortCode={`${modKey} + Shift + 3`} />}
delay={500}
>
{(triggerRef) => (
<IconButton
ref={triggerRef}
onClick={() => handleMenuSelect(3)}
size="400"
radii="300"
>
<Icon size="200" src={Icons.Heading3} />
</IconButton>
)}
</TooltipProvider>
</Box>
</Menu>
</FocusTrap>
@ -169,7 +207,7 @@ export function HeadingBlockButton() {
size="400"
radii="300"
>
<Icon size="200" src={Icons[`Heading${level}`]} />
<Icon size="200" src={level ? Icons[`Heading${level}`] : Icons.Heading1} />
<Icon size="200" src={isActive ? Icons.Cross : Icons.ChevronBottom} />
</IconButton>
)}
@ -210,8 +248,10 @@ export function ExitFormatting({ tooltip }: ExitFormattingProps) {
export function Toolbar() {
const editor = useSlate();
const modKey = isMacOS() ? KeySymbol.Command : 'Ctrl';
const disableInline = isBlockActive(editor, BlockType.CodeBlock);
const canEscape = isAnyMarkActive(editor) || !isBlockActive(editor, BlockType.Paragraph);
const [isMarkdown, setIsMarkdown] = useSetting(settingsAtom, 'isMarkdown');
return (
<Box className={css.EditorToolbarBase}>
@ -271,12 +311,12 @@ export function Toolbar() {
<BlockButton
format={BlockType.OrderedList}
icon={Icons.OrderList}
tooltip={<BtnTooltip text="Ordered List" shortCode={`${modKey} + 7`} />}
tooltip={<BtnTooltip text="Ordered List" shortCode={`${modKey} + Shift + 7`} />}
/>
<BlockButton
format={BlockType.UnorderedList}
icon={Icons.UnorderList}
tooltip={<BtnTooltip text="Unordered List" shortCode={`${modKey} + 8`} />}
tooltip={<BtnTooltip text="Unordered List" shortCode={`${modKey} + Shift + 8`} />}
/>
<HeadingBlockButton />
</Box>
@ -292,6 +332,28 @@ export function Toolbar() {
</Box>
</>
)}
<Box className={css.MarkdownBtnBox} shrink="No" grow="Yes" justifyContent="End">
<TooltipProvider
align="End"
tooltip={<BtnTooltip text="Inline Markdown" />}
delay={500}
>
{(triggerRef) => (
<IconButton
ref={triggerRef}
variant="SurfaceVariant"
onClick={() => setIsMarkdown(!isMarkdown)}
aria-pressed={isMarkdown}
size="300"
radii="300"
disabled={disableInline || !!isAnyMarkActive(editor)}
>
<Icon size="200" src={Icons.Markdown} filled={isMarkdown} />
</IconButton>
)}
</TooltipProvider>
<span />
</Box>
</Box>
</Scroll>
</Box>

View file

@ -15,12 +15,15 @@ export const INLINE_HOTKEYS: Record<string, MarkType> = {
const INLINE_KEYS = Object.keys(INLINE_HOTKEYS);
export const BLOCK_HOTKEYS: Record<string, BlockType> = {
'mod+7': BlockType.OrderedList,
'mod+8': BlockType.UnorderedList,
'mod+shift+7': BlockType.OrderedList,
'mod+shift+8': BlockType.UnorderedList,
"mod+'": BlockType.BlockQuote,
'mod+;': BlockType.CodeBlock,
};
const BLOCK_KEYS = Object.keys(BLOCK_HOTKEYS);
const isHeading1 = isKeyHotkey('mod+shift+1');
const isHeading2 = isKeyHotkey('mod+shift+2');
const isHeading3 = isKeyHotkey('mod+shift+3');
/**
* @return boolean true if shortcut is toggled.
@ -86,6 +89,18 @@ export const toggleKeyboardShortcut = (editor: Editor, event: KeyboardEvent<Elem
return false;
});
if (blockToggled) return true;
if (isHeading1(event)) {
toggleBlock(editor, BlockType.Heading, { level: 1 });
return true;
}
if (isHeading2(event)) {
toggleBlock(editor, BlockType.Heading, { level: 2 });
return true;
}
if (isHeading3(event)) {
toggleBlock(editor, BlockType.Heading, { level: 3 });
return true;
}
const inlineToggled = isBlockActive(editor, BlockType.CodeBlock)
? false

View file

@ -52,6 +52,16 @@ export const isBlockActive = (editor: Editor, format: BlockType) => {
return !!match;
};
export const headingLevel = (editor: Editor): HeadingLevel | undefined => {
const [nodeEntry] = Editor.nodes(editor, {
match: (node) => Element.isElement(node) && node.type === BlockType.Heading,
});
const [node] = nodeEntry ?? [];
if (!node) return undefined;
if ('level' in node) return node.level;
return undefined;
};
type BlockOption = { level: HeadingLevel };
const NESTED_BLOCK = [
BlockType.OrderedList,