Add emoji handling for plain text mode (#9727)

Add emoji handling for plain text mode
This commit is contained in:
Florian Duros 2022-12-09 11:38:14 +01:00 committed by GitHub
parent 888e69f39a
commit 65f9843576
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 53 additions and 24 deletions

View file

@ -34,7 +34,7 @@ export const Editor = memo(
function Editor({ disabled, placeholder, leftComponent, rightComponent }: EditorProps, ref, function Editor({ disabled, placeholder, leftComponent, rightComponent }: EditorProps, ref,
) { ) {
const isExpanded = useIsExpanded(ref as MutableRefObject<HTMLDivElement | null>, HEIGHT_BREAKING_POINT); const isExpanded = useIsExpanded(ref as MutableRefObject<HTMLDivElement | null>, HEIGHT_BREAKING_POINT);
const { onFocus, onBlur, selectPreviousSelection } = useSelection(); const { onFocus, onBlur, selectPreviousSelection, onInput } = useSelection();
return <div return <div
data-testid="WysiwygComposerEditor" data-testid="WysiwygComposerEditor"
@ -59,6 +59,7 @@ export const Editor = memo(
aria-disabled={disabled} aria-disabled={disabled}
onFocus={onFocus} onFocus={onFocus}
onBlur={onBlur} onBlur={onBlur}
onInput={onInput}
/> />
</div> </div>
{ rightComponent?.(selectPreviousSelection) } { rightComponent?.(selectPreviousSelection) }

View file

@ -54,8 +54,9 @@ export function PlainTextComposer({
rightComponent, rightComponent,
}: PlainTextComposerProps, }: PlainTextComposerProps,
) { ) {
const { ref, onInput, onPaste, onKeyDown, content } = usePlainTextListeners(initialContent, onChange, onSend); const { ref, onInput, onPaste, onKeyDown, content, setContent } =
const composerFunctions = useComposerFunctions(ref); usePlainTextListeners(initialContent, onChange, onSend);
const composerFunctions = useComposerFunctions(ref, setContent);
usePlainTextInitialization(initialContent, ref); usePlainTextInitialization(initialContent, ref);
useSetCursorPosition(disabled, ref); useSetCursorPosition(disabled, ref);
const { isFocused, onFocus } = useIsFocused(); const { isFocused, onFocus } = useIsFocused();

View file

@ -16,7 +16,9 @@ limitations under the License.
import { RefObject, useMemo } from "react"; import { RefObject, useMemo } from "react";
export function useComposerFunctions(ref: RefObject<HTMLDivElement>) { import { setSelection } from "../utils/selection";
export function useComposerFunctions(ref: RefObject<HTMLDivElement>, setContent: (content: string) => void) {
return useMemo(() => ({ return useMemo(() => ({
clear: () => { clear: () => {
if (ref.current) { if (ref.current) {
@ -24,7 +26,20 @@ export function useComposerFunctions(ref: RefObject<HTMLDivElement>) {
} }
}, },
insertText: (text: string) => { 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]);
} }

View file

@ -36,12 +36,16 @@ export function usePlainTextListeners(
onSend?.(); onSend?.();
}), [ref, onSend]); }), [ref, onSend]);
const setText = useCallback((text: string) => {
setContent(text);
onChange?.(text);
}, [onChange]);
const onInput = useCallback((event: SyntheticEvent<HTMLDivElement, InputEvent | ClipboardEvent>) => { const onInput = useCallback((event: SyntheticEvent<HTMLDivElement, InputEvent | ClipboardEvent>) => {
if (isDivElement(event.target)) { if (isDivElement(event.target)) {
setContent(event.target.innerHTML); setText(event.target.innerHTML);
onChange?.(event.target.innerHTML);
} }
}, [onChange]); }, [setText]);
const isCtrlEnter = useSettingValue<boolean>("MessageComposerInput.ctrlEnterToSend"); const isCtrlEnter = useSettingValue<boolean>("MessageComposerInput.ctrlEnterToSend");
const onKeyDown = useCallback((event: KeyboardEvent<HTMLDivElement>) => { const onKeyDown = useCallback((event: KeyboardEvent<HTMLDivElement>) => {
@ -52,5 +56,5 @@ export function usePlainTextListeners(
} }
}, [isCtrlEnter, send]); }, [isCtrlEnter, send]);
return { ref, onInput, onPaste: onInput, onKeyDown, content }; return { ref, onInput, onPaste: onInput, onKeyDown, content, setContent: setText };
} }

View file

@ -14,13 +14,26 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { useCallback, useEffect, useRef } from "react"; import { MutableRefObject, useCallback, useEffect, useRef } from "react";
import useFocus from "../../../../../hooks/useFocus"; import useFocus from "../../../../../hooks/useFocus";
import { setSelection } from "../utils/selection"; import { setSelection } from "../utils/selection";
type SubSelection = Pick<Selection, 'anchorNode' | 'anchorOffset' | 'focusNode' | 'focusOffset'>; type SubSelection = Pick<Selection, 'anchorNode' | 'anchorOffset' | 'focusNode' | 'focusOffset'>;
function setSelectionRef(selectionRef: MutableRefObject<SubSelection>) {
const selection = document.getSelection();
if (selection) {
selectionRef.current = {
anchorNode: selection.anchorNode,
anchorOffset: selection.anchorOffset,
focusNode: selection.focusNode,
focusOffset: selection.focusOffset,
};
}
}
export function useSelection() { export function useSelection() {
const selectionRef = useRef<SubSelection>({ const selectionRef = useRef<SubSelection>({
anchorNode: null, anchorNode: null,
@ -32,16 +45,7 @@ export function useSelection() {
useEffect(() => { useEffect(() => {
function onSelectionChange() { function onSelectionChange() {
const selection = document.getSelection(); setSelectionRef(selectionRef);
if (selection) {
selectionRef.current = {
anchorNode: selection.anchorNode,
anchorOffset: selection.anchorOffset,
focusNode: selection.focusNode,
focusOffset: selection.focusOffset,
};
}
} }
if (isFocused) { if (isFocused) {
@ -51,9 +55,13 @@ export function useSelection() {
return () => document.removeEventListener('selectionchange', onSelectionChange); return () => document.removeEventListener('selectionchange', onSelectionChange);
}, [isFocused]); }, [isFocused]);
const onInput = useCallback(() => {
setSelectionRef(selectionRef);
}, []);
const selectPreviousSelection = useCallback(() => { const selectPreviousSelection = useCallback(() => {
setSelection(selectionRef.current); setSelection(selectionRef.current);
}, [selectionRef]); }, []);
return { ...focusProps, selectPreviousSelection }; return { ...focusProps, selectPreviousSelection, onInput };
} }

View file

@ -251,7 +251,7 @@ describe('SendWysiwygComposer', () => {
describe.each([ describe.each([
{ isRichTextEnabled: true }, { isRichTextEnabled: true },
// TODO { isRichTextEnabled: false }, { isRichTextEnabled: false },
])('Emoji when %s', ({ isRichTextEnabled }) => { ])('Emoji when %s', ({ isRichTextEnabled }) => {
let emojiButton: HTMLElement; let emojiButton: HTMLElement;