Fix editor bugs (#1281)
authorAjay Bura <32841439+ajbura@users.noreply.github.com>
Tue, 13 Jun 2023 17:47:18 +0000 (03:47 +1000)
committerGitHub <noreply@github.com>
Tue, 13 Jun 2023 17:47:18 +0000 (23:17 +0530)
* focus editor on reply click

* fix emoji and sticker img object-fit

* fix cursor not moving with autocomplete

* stop sanitizing sending plain text body

* improve autocomplete query parsing

* add escape to turn off active editor toolbar item

src/app/components/editor/Editor.tsx
src/app/components/editor/autocomplete/EmoticonAutocomplete.tsx
src/app/components/editor/autocomplete/autocompleteQuery.ts
src/app/components/editor/common.ts
src/app/components/editor/keyboard.ts
src/app/components/editor/output.ts
src/app/components/emoji-board/EmojiBoard.css.tsx
src/app/components/emoji-board/EmojiBoard.tsx
src/app/organisms/room/RoomInput.tsx

index edf1ac6d8429faac7cf674e01c9197b411fe4378..2657b2135b0c7d23551bc8c54f54982d209012c4 100644 (file)
@@ -93,7 +93,8 @@ export const CustomEditor = forwardRef<HTMLDivElement, CustomEditorProps>(
     const handleKeydown: KeyboardEventHandler = useCallback(
       (evt) => {
         onKeyDown?.(evt);
-        toggleKeyboardShortcut(editor, evt);
+        const shortcutToggled = toggleKeyboardShortcut(editor, evt);
+        if (shortcutToggled) evt.preventDefault();
       },
       [editor, onKeyDown]
     );
index e5af3fa10ab9c25cca35e5f8742b1073088fed53..17712b8707014dbb689963a08642e499fbb0477a 100644 (file)
@@ -104,7 +104,7 @@ export function EmoticonAutocomplete({
                   as="img"
                   src={mx.mxcUrlToHttp(key) || key}
                   alt={emoticon.shortcode}
-                  style={{ width: toRem(24), height: toRem(24) }}
+                  style={{ width: toRem(24), height: toRem(24), objectFit: 'contain' }}
                 />
               ) : (
                 <Box
index 348b44655f149c7f81d06db951a5b2f322935ef5..96dabc57928c4c978231808cdf4086ba9d543816 100644 (file)
@@ -23,13 +23,14 @@ export const getAutocompletePrefix = <TPrefix extends string>(
   validPrefixes: readonly TPrefix[]
 ): TPrefix | undefined => {
   const world = Editor.string(editor, queryRange);
-  const prefix = world[0] as TPrefix | undefined;
-  if (!prefix) return undefined;
-  return validPrefixes.includes(prefix) ? prefix : undefined;
+  return validPrefixes.find((p) => world.startsWith(p));
 };
 
-export const getAutocompleteQueryText = (editor: Editor, queryRange: BaseRange): string =>
-  Editor.string(editor, queryRange).slice(1);
+export const getAutocompleteQueryText = (
+  editor: Editor,
+  queryRange: BaseRange,
+  prefix: string
+): string => Editor.string(editor, queryRange).slice(prefix.length);
 
 export const getAutocompleteQuery = <TPrefix extends string>(
   editor: Editor,
@@ -41,6 +42,6 @@ export const getAutocompleteQuery = <TPrefix extends string>(
   return {
     range: queryRange,
     prefix,
-    text: getAutocompleteQueryText(editor, queryRange),
+    text: getAutocompleteQueryText(editor, queryRange, prefix),
   };
 };
index c9cf086c237430bea99d463ac709764e579de77c..619a1bfee3ab97a8c04455a817f7353b2c7ae283 100644 (file)
@@ -2,11 +2,25 @@ import { BasePoint, BaseRange, Editor, Element, Point, Range, Transforms } from
 import { BlockType, MarkType } from './Elements';
 import { EmoticonElement, FormattedText, HeadingLevel, LinkElement, MentionElement } from './slate';
 
+const ALL_MARK_TYPE: MarkType[] = [
+  MarkType.Bold,
+  MarkType.Code,
+  MarkType.Italic,
+  MarkType.Spoiler,
+  MarkType.StrikeThrough,
+  MarkType.Underline,
+];
+
 export const isMarkActive = (editor: Editor, format: MarkType) => {
   const marks = Editor.marks(editor);
   return marks ? marks[format] === true : false;
 };
 
+export const isAnyMarkActive = (editor: Editor) => {
+  const marks = Editor.marks(editor);
+  return marks && !!ALL_MARK_TYPE.find((type) => marks[type] === true);
+};
+
 export const toggleMark = (editor: Editor, format: MarkType) => {
   const isActive = isMarkActive(editor, format);
 
@@ -17,6 +31,10 @@ export const toggleMark = (editor: Editor, format: MarkType) => {
   }
 };
 
+export const removeAllMark = (editor: Editor) => {
+  ALL_MARK_TYPE.forEach((mark) => Editor.removeMark(editor, mark));
+};
+
 export const isBlockActive = (editor: Editor, format: BlockType) => {
   const [match] = Editor.nodes(editor, {
     match: (node) => Element.isElement(node) && node.type === format,
@@ -140,11 +158,11 @@ export const replaceWithElement = (editor: Editor, selectRange: BaseRange, eleme
 };
 
 export const moveCursor = (editor: Editor, withSpace?: boolean) => {
-  // without timeout it works properly when we select autocomplete with Tab or Space
+  // without timeout move cursor doesn't works properly.
   setTimeout(() => {
     Transforms.move(editor);
     if (withSpace) editor.insertText(' ');
-  }, 1);
+  }, 100);
 };
 
 interface PointUntilCharOptions {
index 52217dd56047c2b708e5dda32004b8b146f97dc5..3fbe5363d5335757e6fc7cf4debc9b19c3791cb1 100644 (file)
@@ -1,7 +1,7 @@
 import { isHotkey } from 'is-hotkey';
 import { KeyboardEvent } from 'react';
 import { Editor } from 'slate';
-import { isBlockActive, toggleBlock, toggleMark } from './common';
+import { isAnyMarkActive, isBlockActive, removeAllMark, toggleBlock, toggleMark } from './common';
 import { BlockType, MarkType } from './Elements';
 
 export const INLINE_HOTKEYS: Record<string, MarkType> = {
@@ -22,19 +22,42 @@ export const BLOCK_HOTKEYS: Record<string, BlockType> = {
 };
 const BLOCK_KEYS = Object.keys(BLOCK_HOTKEYS);
 
-export const toggleKeyboardShortcut = (editor: Editor, event: KeyboardEvent<Element>) => {
-  BLOCK_KEYS.forEach((hotkey) => {
+/**
+ * @return boolean true if shortcut is toggled.
+ */
+export const toggleKeyboardShortcut = (editor: Editor, event: KeyboardEvent<Element>): boolean => {
+  if (isHotkey('escape', 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;
+    }
+    return false;
+  }
+
+  const blockToggled = BLOCK_KEYS.find((hotkey) => {
     if (isHotkey(hotkey, event)) {
       event.preventDefault();
       toggleBlock(editor, BLOCK_HOTKEYS[hotkey]);
+      return true;
     }
+    return false;
   });
+  if (blockToggled) return true;
 
-  if (!isBlockActive(editor, BlockType.CodeBlock))
-    INLINE_KEYS.forEach((hotkey) => {
-      if (isHotkey(hotkey, event)) {
-        event.preventDefault();
-        toggleMark(editor, INLINE_HOTKEYS[hotkey]);
-      }
-    });
+  const inlineToggled = isBlockActive(editor, BlockType.CodeBlock)
+    ? false
+    : INLINE_KEYS.find((hotkey) => {
+        if (isHotkey(hotkey, event)) {
+          event.preventDefault();
+          toggleMark(editor, INLINE_HOTKEYS[hotkey]);
+          return true;
+        }
+        return false;
+      });
+  return !!inlineToggled;
 };
index 091dab794a841b86cd285867a5437596a57cba28..38c54499305b5ebd2f24b66b002562b25edae727 100644 (file)
@@ -88,7 +88,7 @@ const elementToPlainText = (node: CustomElement, children: string): string => {
 
 export const toPlainText = (node: Descendant | Descendant[]): string => {
   if (Array.isArray(node)) return node.map((n) => toPlainText(n)).join('');
-  if (Text.isText(node)) return sanitizeText(node.text);
+  if (Text.isText(node)) return node.text;
 
   const children = node.children.map((n) => toPlainText(n)).join('');
   return elementToPlainText(node, children);
index 0fefc5b95bfc20203490b3c3752f0268c7b94ed1..adeb25005215fe970477cc101a993cbad0db7927 100644 (file)
@@ -122,6 +122,7 @@ export const CustomEmojiImg = style([
   {
     width: toRem(32),
     height: toRem(32),
+    objectFit: 'contain',
   },
 ]);
 
@@ -130,5 +131,6 @@ export const StickerImg = style([
   {
     width: toRem(96),
     height: toRem(96),
+    objectFit: 'contain',
   },
 ]);
index c5f5038c5a98d31a944be26d4d8199e2b98d9382..3b1ccc55462d3c700b73d8db422ef7ba7adc4f55 100644 (file)
@@ -373,6 +373,7 @@ function ImagePackSidebarStack({
               style={{
                 width: toRem(24),
                 height: toRem(24),
+                objectFit: 'contain',
               }}
               src={mx.mxcUrlToHttp(pack.getPackAvatarUrl(usage) ?? '') || pack.avatarUrl}
               alt={label || 'Unknown Pack'}
index 17830ad987758a9525a4f4f6865260a3d739320c..e79f4883b2a31f7b44b9ae914ca4bacdc15548ca 100644 (file)
@@ -184,12 +184,13 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
           body,
           formattedBody,
         });
+        ReactEditor.focus(editor);
       };
       navigation.on(cons.events.navigation.REPLY_TO_CLICKED, handleReplyTo);
       return () => {
         navigation.removeListener(cons.events.navigation.REPLY_TO_CLICKED, handleReplyTo);
       };
-    }, [setReplyDraft]);
+    }, [setReplyDraft, editor]);
 
     const handleRemoveUpload = useCallback(
       (upload: TUploadContent | TUploadContent[]) => {