mirror of
https://github.com/element-hq/element-web
synced 2024-11-27 03:36:07 +03:00
Add emoji handling for plain text mode (#9727)
Add emoji handling for plain text mode
This commit is contained in:
parent
888e69f39a
commit
65f9843576
6 changed files with 53 additions and 24 deletions
|
@ -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) }
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 };
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 };
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue