From 65f98435761e3a8d9ef6b95abe3d16a7f212716a Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Fri, 9 Dec 2022 11:38:14 +0100 Subject: [PATCH] Add emoji handling for plain text mode (#9727) Add emoji handling for plain text mode --- .../wysiwyg_composer/components/Editor.tsx | 3 +- .../components/PlainTextComposer.tsx | 5 +-- .../hooks/useComposerFunctions.ts | 21 ++++++++++-- .../hooks/usePlainTextListeners.ts | 12 ++++--- .../wysiwyg_composer/hooks/useSelection.ts | 34 ++++++++++++------- .../SendWysiwygComposer-test.tsx | 2 +- 6 files changed, 53 insertions(+), 24 deletions(-) diff --git a/src/components/views/rooms/wysiwyg_composer/components/Editor.tsx b/src/components/views/rooms/wysiwyg_composer/components/Editor.tsx index b738847ec6..e83f19ce0f 100644 --- a/src/components/views/rooms/wysiwyg_composer/components/Editor.tsx +++ b/src/components/views/rooms/wysiwyg_composer/components/Editor.tsx @@ -34,7 +34,7 @@ export const Editor = memo( function Editor({ disabled, placeholder, leftComponent, rightComponent }: EditorProps, ref, ) { const isExpanded = useIsExpanded(ref as MutableRefObject, HEIGHT_BREAKING_POINT); - const { onFocus, onBlur, selectPreviousSelection } = useSelection(); + const { onFocus, onBlur, selectPreviousSelection, onInput } = useSelection(); return
{ rightComponent?.(selectPreviousSelection) } diff --git a/src/components/views/rooms/wysiwyg_composer/components/PlainTextComposer.tsx b/src/components/views/rooms/wysiwyg_composer/components/PlainTextComposer.tsx index 5339e986cd..7bf453b80b 100644 --- a/src/components/views/rooms/wysiwyg_composer/components/PlainTextComposer.tsx +++ b/src/components/views/rooms/wysiwyg_composer/components/PlainTextComposer.tsx @@ -54,8 +54,9 @@ export function PlainTextComposer({ rightComponent, }: PlainTextComposerProps, ) { - const { ref, onInput, onPaste, onKeyDown, content } = usePlainTextListeners(initialContent, onChange, onSend); - const composerFunctions = useComposerFunctions(ref); + const { ref, onInput, onPaste, onKeyDown, content, setContent } = + usePlainTextListeners(initialContent, onChange, onSend); + const composerFunctions = useComposerFunctions(ref, setContent); usePlainTextInitialization(initialContent, ref); useSetCursorPosition(disabled, ref); const { isFocused, onFocus } = useIsFocused(); diff --git a/src/components/views/rooms/wysiwyg_composer/hooks/useComposerFunctions.ts b/src/components/views/rooms/wysiwyg_composer/hooks/useComposerFunctions.ts index abfde035a5..b9f7146bba 100644 --- a/src/components/views/rooms/wysiwyg_composer/hooks/useComposerFunctions.ts +++ b/src/components/views/rooms/wysiwyg_composer/hooks/useComposerFunctions.ts @@ -16,7 +16,9 @@ limitations under the License. import { RefObject, useMemo } from "react"; -export function useComposerFunctions(ref: RefObject) { +import { setSelection } from "../utils/selection"; + +export function useComposerFunctions(ref: RefObject, setContent: (content: string) => void) { return useMemo(() => ({ clear: () => { if (ref.current) { @@ -24,7 +26,20 @@ export function useComposerFunctions(ref: RefObject) { } }, insertText: (text: string) => { - // TODO + const selection = document.getSelection(); + + if (ref.current && selection) { + const content = ref.current.innerHTML; + const { anchorOffset, focusOffset } = selection; + ref.current.innerHTML = `${content.slice(0, anchorOffset)}${text}${content.slice(focusOffset)}`; + setSelection({ + anchorNode: ref.current.firstChild, + anchorOffset: anchorOffset + text.length, + focusNode: ref.current.firstChild, + focusOffset: focusOffset + text.length, + }); + setContent(ref.current.innerHTML); + } }, - }), [ref]); + }), [ref, setContent]); } diff --git a/src/components/views/rooms/wysiwyg_composer/hooks/usePlainTextListeners.ts b/src/components/views/rooms/wysiwyg_composer/hooks/usePlainTextListeners.ts index bf4678c693..1931c4a9ca 100644 --- a/src/components/views/rooms/wysiwyg_composer/hooks/usePlainTextListeners.ts +++ b/src/components/views/rooms/wysiwyg_composer/hooks/usePlainTextListeners.ts @@ -36,12 +36,16 @@ export function usePlainTextListeners( onSend?.(); }), [ref, onSend]); + const setText = useCallback((text: string) => { + setContent(text); + onChange?.(text); + }, [onChange]); + const onInput = useCallback((event: SyntheticEvent) => { if (isDivElement(event.target)) { - setContent(event.target.innerHTML); - onChange?.(event.target.innerHTML); + setText(event.target.innerHTML); } - }, [onChange]); + }, [setText]); const isCtrlEnter = useSettingValue("MessageComposerInput.ctrlEnterToSend"); const onKeyDown = useCallback((event: KeyboardEvent) => { @@ -52,5 +56,5 @@ export function usePlainTextListeners( } }, [isCtrlEnter, send]); - return { ref, onInput, onPaste: onInput, onKeyDown, content }; + return { ref, onInput, onPaste: onInput, onKeyDown, content, setContent: setText }; } diff --git a/src/components/views/rooms/wysiwyg_composer/hooks/useSelection.ts b/src/components/views/rooms/wysiwyg_composer/hooks/useSelection.ts index 2ae61790db..b27c8c3a4d 100644 --- a/src/components/views/rooms/wysiwyg_composer/hooks/useSelection.ts +++ b/src/components/views/rooms/wysiwyg_composer/hooks/useSelection.ts @@ -14,13 +14,26 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { useCallback, useEffect, useRef } from "react"; +import { MutableRefObject, useCallback, useEffect, useRef } from "react"; import useFocus from "../../../../../hooks/useFocus"; import { setSelection } from "../utils/selection"; type SubSelection = Pick; +function setSelectionRef(selectionRef: MutableRefObject) { + const selection = document.getSelection(); + + if (selection) { + selectionRef.current = { + anchorNode: selection.anchorNode, + anchorOffset: selection.anchorOffset, + focusNode: selection.focusNode, + focusOffset: selection.focusOffset, + }; + } +} + export function useSelection() { const selectionRef = useRef({ anchorNode: null, @@ -32,16 +45,7 @@ export function useSelection() { useEffect(() => { function onSelectionChange() { - const selection = document.getSelection(); - - if (selection) { - selectionRef.current = { - anchorNode: selection.anchorNode, - anchorOffset: selection.anchorOffset, - focusNode: selection.focusNode, - focusOffset: selection.focusOffset, - }; - } + setSelectionRef(selectionRef); } if (isFocused) { @@ -51,9 +55,13 @@ export function useSelection() { return () => document.removeEventListener('selectionchange', onSelectionChange); }, [isFocused]); + const onInput = useCallback(() => { + setSelectionRef(selectionRef); + }, []); + const selectPreviousSelection = useCallback(() => { setSelection(selectionRef.current); - }, [selectionRef]); + }, []); - return { ...focusProps, selectPreviousSelection }; + return { ...focusProps, selectPreviousSelection, onInput }; } diff --git a/test/components/views/rooms/wysiwyg_composer/SendWysiwygComposer-test.tsx b/test/components/views/rooms/wysiwyg_composer/SendWysiwygComposer-test.tsx index 1b28c6ed2e..6cb183bb0a 100644 --- a/test/components/views/rooms/wysiwyg_composer/SendWysiwygComposer-test.tsx +++ b/test/components/views/rooms/wysiwyg_composer/SendWysiwygComposer-test.tsx @@ -251,7 +251,7 @@ describe('SendWysiwygComposer', () => { describe.each([ { isRichTextEnabled: true }, - // TODO { isRichTextEnabled: false }, + { isRichTextEnabled: false }, ])('Emoji when %s', ({ isRichTextEnabled }) => { let emojiButton: HTMLElement;