Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 982c83d

Browse files
authored
Merge pull request #9661 from matrix-org/feat/emoji-picker-rich-text-mode
Add emoji handling for rich text mode
2 parents 33dfa14 + afc2c58 commit 982c83d

File tree

13 files changed

+249
-16
lines changed

13 files changed

+249
-16
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
"dependencies": {
5858
"@babel/runtime": "^7.12.5",
5959
"@matrix-org/analytics-events": "^0.3.0",
60-
"@matrix-org/matrix-wysiwyg": "^0.8.0",
60+
"@matrix-org/matrix-wysiwyg": "^0.9.0",
6161
"@matrix-org/react-sdk-module-api": "^0.0.3",
6262
"@sentry/browser": "^7.0.0",
6363
"@sentry/tracing": "^7.0.0",

src/components/views/rooms/wysiwyg_composer/SendWysiwygComposer.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ import { PlainTextComposer } from './components/PlainTextComposer';
2222
import { ComposerFunctions } from './types';
2323
import { E2EStatus } from '../../../../utils/ShieldUtils';
2424
import E2EIcon from '../E2EIcon';
25-
import { EmojiButton } from '../EmojiButton';
2625
import { AboveLeftOf } from '../../../structures/ContextMenu';
26+
import { Emoji } from './components/Emoji';
2727

2828
interface ContentProps {
2929
disabled?: boolean;
@@ -58,8 +58,8 @@ export function SendWysiwygComposer(
5858
return <Composer
5959
className="mx_SendWysiwygComposer"
6060
leftComponent={e2eStatus && <E2EIcon status={e2eStatus} />}
61-
// TODO add emoji support
62-
rightComponent={<EmojiButton menuPosition={menuPosition} addEmoji={() => false} />}
61+
rightComponent={(selectPreviousSelection) =>
62+
<Emoji menuPosition={menuPosition} selectPreviousSelection={selectPreviousSelection} />}
6363
{...props}
6464
>
6565
{ (ref, composerFunctions) => (

src/components/views/rooms/wysiwyg_composer/components/Editor.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,23 @@ import classNames from 'classnames';
1818
import React, { CSSProperties, forwardRef, memo, MutableRefObject, ReactNode } from 'react';
1919

2020
import { useIsExpanded } from '../hooks/useIsExpanded';
21+
import { useSelection } from '../hooks/useSelection';
2122

2223
const HEIGHT_BREAKING_POINT = 20;
2324

2425
interface EditorProps {
2526
disabled: boolean;
2627
placeholder?: string;
2728
leftComponent?: ReactNode;
28-
rightComponent?: ReactNode;
29+
rightComponent?: (selectPreviousSelection: () => void) => ReactNode;
2930
}
3031

3132
export const Editor = memo(
3233
forwardRef<HTMLDivElement, EditorProps>(
3334
function Editor({ disabled, placeholder, leftComponent, rightComponent }: EditorProps, ref,
3435
) {
3536
const isExpanded = useIsExpanded(ref as MutableRefObject<HTMLDivElement | null>, HEIGHT_BREAKING_POINT);
37+
const { onFocus, onBlur, selectPreviousSelection } = useSelection();
3638

3739
return <div
3840
data-testid="WysiwygComposerEditor"
@@ -55,9 +57,11 @@ export const Editor = memo(
5557
aria-haspopup="listbox"
5658
dir="auto"
5759
aria-disabled={disabled}
60+
onFocus={onFocus}
61+
onBlur={onBlur}
5862
/>
5963
</div>
60-
{ rightComponent }
64+
{ rightComponent?.(selectPreviousSelection) }
6165
</div>;
6266
},
6367
),
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import React from 'react';
18+
19+
import { AboveLeftOf } from "../../../../structures/ContextMenu";
20+
import { EmojiButton } from "../../EmojiButton";
21+
import dis from '../../../../../dispatcher/dispatcher';
22+
import { ComposerInsertPayload } from "../../../../../dispatcher/payloads/ComposerInsertPayload";
23+
import { Action } from "../../../../../dispatcher/actions";
24+
import { useRoomContext } from "../../../../../contexts/RoomContext";
25+
26+
interface EmojiProps {
27+
selectPreviousSelection: () => void;
28+
menuPosition: AboveLeftOf;
29+
}
30+
31+
export function Emoji({ selectPreviousSelection, menuPosition }: EmojiProps) {
32+
const roomContext = useRoomContext();
33+
34+
return <EmojiButton menuPosition={menuPosition}
35+
addEmoji={(emoji) => {
36+
selectPreviousSelection();
37+
dis.dispatch<ComposerInsertPayload>({
38+
action: Action.ComposerInsert,
39+
text: emoji,
40+
timelineRenderingType: roomContext.timelineRenderingType,
41+
});
42+
return true;
43+
}}
44+
/>;
45+
}

src/components/views/rooms/wysiwyg_composer/components/PlainTextComposer.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ interface PlainTextComposerProps {
3333
initialContent?: string;
3434
className?: string;
3535
leftComponent?: ReactNode;
36-
rightComponent?: ReactNode;
36+
rightComponent?: (
37+
selectPreviousSelection: () => void
38+
) => ReactNode;
3739
children?: (
3840
ref: MutableRefObject<HTMLDivElement | null>,
3941
composerFunctions: ComposerFunctions,

src/components/views/rooms/wysiwyg_composer/components/WysiwygComposer.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ interface WysiwygComposerProps {
3232
initialContent?: string;
3333
className?: string;
3434
leftComponent?: ReactNode;
35-
rightComponent?: ReactNode;
35+
rightComponent?: (
36+
selectPreviousSelection: () => void
37+
) => ReactNode;
3638
children?: (
3739
ref: MutableRefObject<HTMLDivElement | null>,
3840
wysiwyg: FormattingFunctions,

src/components/views/rooms/wysiwyg_composer/hooks/useComposerFunctions.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,8 @@ export function useComposerFunctions(ref: RefObject<HTMLDivElement>) {
2323
ref.current.innerHTML = '';
2424
}
2525
},
26+
insertText: (text: string) => {
27+
// TODO
28+
},
2629
}), [ref]);
2730
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { useCallback, useEffect, useRef } from "react";
18+
19+
import useFocus from "../../../../../hooks/useFocus";
20+
import { setSelection } from "../utils/selection";
21+
22+
type SubSelection = Pick<Selection, 'anchorNode' | 'anchorOffset' | 'focusNode' | 'focusOffset'>;
23+
24+
export function useSelection() {
25+
const selectionRef = useRef<SubSelection>({
26+
anchorNode: null,
27+
anchorOffset: 0,
28+
focusNode: null,
29+
focusOffset: 0,
30+
});
31+
const [isFocused, focusProps] = useFocus();
32+
33+
useEffect(() => {
34+
function onSelectionChange() {
35+
const selection = document.getSelection();
36+
37+
if (selection) {
38+
selectionRef.current = {
39+
anchorNode: selection.anchorNode,
40+
anchorOffset: selection.anchorOffset,
41+
focusNode: selection.focusNode,
42+
focusOffset: selection.focusOffset,
43+
};
44+
}
45+
}
46+
47+
if (isFocused) {
48+
document.addEventListener('selectionchange', onSelectionChange);
49+
}
50+
51+
return () => document.removeEventListener('selectionchange', onSelectionChange);
52+
}, [isFocused]);
53+
54+
const selectPreviousSelection = useCallback(() => {
55+
setSelection(selectionRef.current);
56+
}, [selectionRef]);
57+
58+
return { ...focusProps, selectPreviousSelection };
59+
}

src/components/views/rooms/wysiwyg_composer/hooks/useWysiwygSendActionHandler.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { TimelineRenderingType, useRoomContext } from "../../../../../contexts/R
2323
import { useDispatcher } from "../../../../../hooks/useDispatcher";
2424
import { focusComposer } from "./utils";
2525
import { ComposerFunctions } from "../types";
26+
import { ComposerType } from "../../../../../dispatcher/payloads/ComposerInsertPayload";
2627

2728
export function useWysiwygSendActionHandler(
2829
disabled: boolean,
@@ -48,7 +49,18 @@ export function useWysiwygSendActionHandler(
4849
composerFunctions.clear();
4950
focusComposer(composerElement, context, roomContext, timeoutId);
5051
break;
51-
// TODO: case Action.ComposerInsert: - see SendMessageComposer
52+
case Action.ComposerInsert:
53+
if (payload.timelineRenderingType !== roomContext.timelineRenderingType) break;
54+
if (payload.composerType !== ComposerType.Send) break;
55+
56+
if (payload.userId) {
57+
// TODO insert mention - see SendMessageComposer
58+
} else if (payload.event) {
59+
// TODO insert quote message - see SendMessageComposer
60+
} else if (payload.text) {
61+
composerFunctions.insertText(payload.text);
62+
}
63+
break;
5264
}
5365
}, [disabled, composerElement, composerFunctions, timeoutId, roomContext]);
5466

src/components/views/rooms/wysiwyg_composer/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ limitations under the License.
1616

1717
export type ComposerFunctions = {
1818
clear: () => void;
19+
insertText: (text: string) => void;
1920
};

0 commit comments

Comments
 (0)