diff --git a/src/components/views/rooms/wysiwyg_composer/SendWysiwygComposer.tsx b/src/components/views/rooms/wysiwyg_composer/SendWysiwygComposer.tsx index a63a013cc47..ea424895d6c 100644 --- a/src/components/views/rooms/wysiwyg_composer/SendWysiwygComposer.tsx +++ b/src/components/views/rooms/wysiwyg_composer/SendWysiwygComposer.tsx @@ -59,7 +59,14 @@ export function SendWysiwygComposer( className="mx_SendWysiwygComposer" leftComponent={e2eStatus && } // TODO add emoji support - rightComponent={ false} />} + rightComponent={(composerFunctions, selectPreviousSelection) => + { + selectPreviousSelection(); + setTimeout(() => composerFunctions.insertText(unicode), 100); + return true; + }} + />} {...props} > { (ref, composerFunctions) => ( diff --git a/src/components/views/rooms/wysiwyg_composer/components/Editor.tsx b/src/components/views/rooms/wysiwyg_composer/components/Editor.tsx index 6ebd189089c..4a2958cbc8e 100644 --- a/src/components/views/rooms/wysiwyg_composer/components/Editor.tsx +++ b/src/components/views/rooms/wysiwyg_composer/components/Editor.tsx @@ -18,6 +18,7 @@ import classNames from 'classnames'; import React, { CSSProperties, forwardRef, memo, MutableRefObject, ReactNode } from 'react'; import { useIsExpanded } from '../hooks/useIsExpanded'; +import { useSelection } from '../hooks/useSelection'; const HEIGHT_BREAKING_POINT = 20; @@ -25,7 +26,7 @@ interface EditorProps { disabled: boolean; placeholder?: string; leftComponent?: ReactNode; - rightComponent?: ReactNode; + rightComponent?: (selectPreviousSelection: () => void) => ReactNode; } export const Editor = memo( @@ -33,6 +34,8 @@ 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(ref as MutableRefObject); return
- { rightComponent } + { 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 f019c2e1788..c8972f923e3 100644 --- a/src/components/views/rooms/wysiwyg_composer/components/PlainTextComposer.tsx +++ b/src/components/views/rooms/wysiwyg_composer/components/PlainTextComposer.tsx @@ -33,7 +33,10 @@ interface PlainTextComposerProps { initialContent?: string; className?: string; leftComponent?: ReactNode; - rightComponent?: ReactNode; + rightComponent?: ( + composerFunctions: ComposerFunctions, + selectPreviousSelection: () => void + ) => ReactNode; children?: ( ref: MutableRefObject, composerFunctions: ComposerFunctions, @@ -58,6 +61,8 @@ export function PlainTextComposer({ useSetCursorPosition(disabled, ref); const { isFocused, onFocus } = useIsFocused(); const computedPlaceholder = !content && placeholder || undefined; + const rightComp = + (selectPreviousSelection: () => void) => rightComponent(composerFunctions, selectPreviousSelection); return
- + { children?.(ref, composerFunctions) }
; } diff --git a/src/components/views/rooms/wysiwyg_composer/components/WysiwygComposer.tsx b/src/components/views/rooms/wysiwyg_composer/components/WysiwygComposer.tsx index 05afc3d3283..509218e0d5c 100644 --- a/src/components/views/rooms/wysiwyg_composer/components/WysiwygComposer.tsx +++ b/src/components/views/rooms/wysiwyg_composer/components/WysiwygComposer.tsx @@ -32,7 +32,10 @@ interface WysiwygComposerProps { initialContent?: string; className?: string; leftComponent?: ReactNode; - rightComponent?: ReactNode; + rightComponent?: ( + composerFunctions: FormattingFunctions, + selectPreviousSelection: () => void + ) => ReactNode; children?: ( ref: MutableRefObject, wysiwyg: FormattingFunctions, @@ -69,10 +72,12 @@ export const WysiwygComposer = memo(function WysiwygComposer( const { isFocused, onFocus } = useIsFocused(); const computedPlaceholder = !content && placeholder || undefined; + const rightComp = (selectPreviousSelection: () => void) => rightComponent(wysiwyg, selectPreviousSelection); + return (
- + { children?.(ref, wysiwyg) }
); diff --git a/src/components/views/rooms/wysiwyg_composer/hooks/useComposerFunctions.ts b/src/components/views/rooms/wysiwyg_composer/hooks/useComposerFunctions.ts index 99a89589ee4..abfde035a5f 100644 --- a/src/components/views/rooms/wysiwyg_composer/hooks/useComposerFunctions.ts +++ b/src/components/views/rooms/wysiwyg_composer/hooks/useComposerFunctions.ts @@ -23,5 +23,8 @@ export function useComposerFunctions(ref: RefObject) { ref.current.innerHTML = ''; } }, + insertText: (text: string) => { + // TODO + }, }), [ref]); } diff --git a/src/components/views/rooms/wysiwyg_composer/hooks/useSelection.ts b/src/components/views/rooms/wysiwyg_composer/hooks/useSelection.ts new file mode 100644 index 00000000000..48aeda1ff8f --- /dev/null +++ b/src/components/views/rooms/wysiwyg_composer/hooks/useSelection.ts @@ -0,0 +1,54 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { RefObject, useCallback, useEffect, useRef } from "react"; + +import useFocus from "../../../../../hooks/useFocus"; + +export function useSelection(ref: RefObject) { + const selectionRef = useRef({ + anchorOffset: 0, + focusOffset: 0, + }); + const [isFocused, focusProps] = useFocus(); + + useEffect(() => { + function onSelectionChange() { + const selection = document.getSelection(); + console.log('selection', selection); + selectionRef.current = { + anchorOffset: selection.anchorOffset, + focusOffset: selection.focusOffset, + }; + } + + if (isFocused) { + document.addEventListener('selectionchange', onSelectionChange); + } + + return () => document.removeEventListener('selectionchange', onSelectionChange); + }, [isFocused]); + + const selectPreviousSelection = useCallback(() => { + const range = new Range(); + range.setStart(ref.current.firstChild, selectionRef.current.anchorOffset); + range.setEnd(ref.current.firstChild, selectionRef.current.focusOffset); + document.getSelection().removeAllRanges(); + document.getSelection().addRange(range); + }, [selectionRef, ref]); + + return { ...focusProps, selectPreviousSelection }; +} diff --git a/src/components/views/rooms/wysiwyg_composer/types.ts b/src/components/views/rooms/wysiwyg_composer/types.ts index 96095abebfd..60367933530 100644 --- a/src/components/views/rooms/wysiwyg_composer/types.ts +++ b/src/components/views/rooms/wysiwyg_composer/types.ts @@ -16,4 +16,5 @@ limitations under the License. export type ComposerFunctions = { clear: () => void; + insertText: (text: string) => void; };