mirror of https://github.com/vector-im/riot-web
Add emoji handling for plain text mode (#9727)
Add emoji handling for plain text modepull/28217/head
parent
888e69f39a
commit
65f9843576
|
@ -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) }
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
|
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
@ -251,7 +251,7 @@ describe('SendWysiwygComposer', () => {
|
|||
|
||||
describe.each([
|
||||
{ isRichTextEnabled: true },
|
||||
// TODO { isRichTextEnabled: false },
|
||||
{ isRichTextEnabled: false },
|
||||
])('Emoji when %s', ({ isRichTextEnabled }) => {
|
||||
let emojiButton: HTMLElement;
|
||||
|
||||
|
|
Loading…
Reference in New Issue