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

View file

@ -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();

View file

@ -16,7 +16,9 @@ limitations under the License.
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(() => ({
clear: () => {
if (ref.current) {
@ -24,7 +26,20 @@ export function useComposerFunctions(ref: RefObject<HTMLDivElement>) {
}
},
insertText: (text: string) => {
// TODO
},
}), [ref]);
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, setContent]);
}

View file

@ -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<HTMLDivElement, InputEvent | ClipboardEvent>) => {
if (isDivElement(event.target)) {
setContent(event.target.innerHTML);
onChange?.(event.target.innerHTML);
setText(event.target.innerHTML);
}
}, [onChange]);
}, [setText]);
const isCtrlEnter = useSettingValue<boolean>("MessageComposerInput.ctrlEnterToSend");
const onKeyDown = useCallback((event: KeyboardEvent<HTMLDivElement>) => {
@ -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 };
}

View file

@ -14,24 +14,14 @@ 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<Selection, 'anchorNode' | 'anchorOffset' | 'focusNode' | 'focusOffset'>;
export function useSelection() {
const selectionRef = useRef<SubSelection>({
anchorNode: null,
anchorOffset: 0,
focusNode: null,
focusOffset: 0,
});
const [isFocused, focusProps] = useFocus();
useEffect(() => {
function onSelectionChange() {
function setSelectionRef(selectionRef: MutableRefObject<SubSelection>) {
const selection = document.getSelection();
if (selection) {
@ -44,6 +34,20 @@ export function useSelection() {
}
}
export function useSelection() {
const selectionRef = useRef<SubSelection>({
anchorNode: null,
anchorOffset: 0,
focusNode: null,
focusOffset: 0,
});
const [isFocused, focusProps] = useFocus();
useEffect(() => {
function onSelectionChange() {
setSelectionRef(selectionRef);
}
if (isFocused) {
document.addEventListener('selectionchange', onSelectionChange);
}
@ -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 };
}

View file

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