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

Commit b336e18

Browse files
authored
Merge pull request #9374 from matrix-org/feat/matrix-wysisyg-integration
First step of matrix-wysiwyg integration
2 parents 4a98e26 + 203f75f commit b336e18

File tree

14 files changed

+693
-15
lines changed

14 files changed

+693
-15
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"dependencies": {
5858
"@babel/runtime": "^7.12.5",
5959
"@matrix-org/analytics-events": "^0.2.0",
60+
"@matrix-org/matrix-wysiwyg": "^0.0.2",
6061
"@matrix-org/react-sdk-module-api": "^0.0.3",
6162
"@sentry/browser": "^6.11.0",
6263
"@sentry/tracing": "^6.11.0",

res/css/_components.pcss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@
295295
@import "./views/rooms/_TopUnreadMessagesBar.pcss";
296296
@import "./views/rooms/_VoiceRecordComposerTile.pcss";
297297
@import "./views/rooms/_WhoIsTypingTile.pcss";
298+
@import "./views/rooms/wysiwyg_composer/_WysiwygComposer.pcss";
298299
@import "./views/settings/_AvatarSetting.pcss";
299300
@import "./views/settings/_CrossSigningPanel.pcss";
300301
@import "./views/settings/_CryptographyPanel.pcss";
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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+
.mx_WysiwygComposer {
18+
flex: 1;
19+
display: flex;
20+
flex-direction: column;
21+
font-size: $font-14px;
22+
/* fixed line height to prevent emoji from being taller than text */
23+
line-height: $font-18px;
24+
justify-content: center;
25+
margin-right: 6px;
26+
/* don't grow wider than available space */
27+
min-width: 0;
28+
29+
.mx_WysiwygComposer_container {
30+
flex: 1;
31+
display: flex;
32+
flex-direction: column;
33+
/* min-height at this level so the mx_BasicMessageComposer_input */
34+
/* still stays vertically centered when less than 55px. */
35+
/* We also set this to ensure the voice message recording widget */
36+
/* doesn't cause a jump. */
37+
min-height: 55px;
38+
39+
.mx_WysiwygComposer_content {
40+
border: 1px solid;
41+
border-radius: 20px;
42+
padding: 8px 10px;
43+
/* this will center the contenteditable */
44+
/* in it's parent vertically */
45+
/* while keeping the autocomplete at the top */
46+
/* of the composer. The parent needs to be a flex container for this to work. */
47+
margin: auto 0;
48+
/* max-height at this level so autocomplete doesn't get scrolled too */
49+
max-height: 140px;
50+
overflow-y: auto;
51+
}
52+
}
53+
}

src/components/views/rooms/MessageComposer.tsx

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ import {
5858
startNewVoiceBroadcastRecording,
5959
VoiceBroadcastRecordingsStore,
6060
} from '../../../voice-broadcast';
61+
import { WysiwygComposer } from './wysiwyg_composer/WysiwygComposer';
6162

6263
let instanceCount = 0;
6364

@@ -105,6 +106,7 @@ export default class MessageComposer extends React.Component<IProps, IState> {
105106
private voiceRecordingButton = createRef<VoiceRecordComposerTile>();
106107
private ref: React.RefObject<HTMLDivElement> = createRef();
107108
private instanceId: number;
109+
private composerSendMessage?: () => void;
108110

109111
private _voiceRecording: Optional<VoiceMessageRecording>;
110112

@@ -313,6 +315,7 @@ export default class MessageComposer extends React.Component<IProps, IState> {
313315
}
314316

315317
this.messageComposerInput.current?.sendMessage();
318+
this.composerSendMessage?.();
316319
};
317320

318321
private onChange = (model: EditorModel) => {
@@ -321,6 +324,12 @@ export default class MessageComposer extends React.Component<IProps, IState> {
321324
});
322325
};
323326

327+
private onWysiwygChange = (content: string) => {
328+
this.setState({
329+
isComposerEmpty: content?.length === 0,
330+
});
331+
};
332+
324333
private onVoiceStoreUpdate = () => {
325334
this.updateRecordingState();
326335
};
@@ -394,20 +403,37 @@ export default class MessageComposer extends React.Component<IProps, IState> {
394403

395404
const canSendMessages = this.context.canSendMessages && !this.context.tombstone;
396405
if (canSendMessages) {
397-
controls.push(
398-
<SendMessageComposer
399-
ref={this.messageComposerInput}
400-
key="controls_input"
401-
room={this.props.room}
402-
placeholder={this.renderPlaceholderText()}
403-
permalinkCreator={this.props.permalinkCreator}
404-
relation={this.props.relation}
405-
replyToEvent={this.props.replyToEvent}
406-
onChange={this.onChange}
407-
disabled={this.state.haveRecording}
408-
toggleStickerPickerOpen={this.toggleStickerPickerOpen}
409-
/>,
410-
);
406+
const isWysiwygComposerEnabled = SettingsStore.getValue("feature_wysiwyg_composer");
407+
408+
if (isWysiwygComposerEnabled) {
409+
controls.push(
410+
<WysiwygComposer key="controls_input"
411+
disabled={this.state.haveRecording}
412+
onChange={this.onWysiwygChange}
413+
permalinkCreator={this.props.permalinkCreator}
414+
relation={this.props.relation}
415+
replyToEvent={this.props.replyToEvent}>
416+
{ (sendMessage) => {
417+
this.composerSendMessage = sendMessage;
418+
} }
419+
</WysiwygComposer>,
420+
);
421+
} else {
422+
controls.push(
423+
<SendMessageComposer
424+
ref={this.messageComposerInput}
425+
key="controls_input"
426+
room={this.props.room}
427+
placeholder={this.renderPlaceholderText()}
428+
permalinkCreator={this.props.permalinkCreator}
429+
relation={this.props.relation}
430+
replyToEvent={this.props.replyToEvent}
431+
onChange={this.onChange}
432+
disabled={this.state.haveRecording}
433+
toggleStickerPickerOpen={this.toggleStickerPickerOpen}
434+
/>,
435+
);
436+
}
411437

412438
controls.push(<VoiceRecordComposerTile
413439
key="controls_voice_record"
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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, { useCallback, useState } from 'react';
18+
import { useWysiwyg } from "@matrix-org/matrix-wysiwyg";
19+
import { IEventRelation, MatrixEvent } from 'matrix-js-sdk/src/models/event';
20+
21+
import { useRoomContext } from '../../../../contexts/RoomContext';
22+
import { sendMessage } from './message';
23+
import { RoomPermalinkCreator } from '../../../../utils/permalinks/Permalinks';
24+
import { useMatrixClientContext } from '../../../../contexts/MatrixClientContext';
25+
26+
interface WysiwygProps {
27+
disabled?: boolean;
28+
onChange: (content: string) => void;
29+
relation?: IEventRelation;
30+
replyToEvent?: MatrixEvent;
31+
permalinkCreator: RoomPermalinkCreator;
32+
includeReplyLegacyFallback?: boolean;
33+
children?: (sendMessage: () => void) => void;
34+
}
35+
36+
export function WysiwygComposer(
37+
{ disabled = false, onChange, children, ...props }: WysiwygProps,
38+
) {
39+
const roomContext = useRoomContext();
40+
const mxClient = useMatrixClientContext();
41+
42+
const [content, setContent] = useState<string>();
43+
const { ref, isWysiwygReady, wysiwyg } = useWysiwyg({ onChange: (_content) => {
44+
setContent(_content);
45+
onChange(_content);
46+
} });
47+
48+
const memoizedSendMessage = useCallback(() => {
49+
sendMessage(content, { mxClient, roomContext, ...props });
50+
wysiwyg.clear();
51+
ref.current?.focus();
52+
}, [content, mxClient, roomContext, wysiwyg, props, ref]);
53+
54+
return (
55+
<div className="mx_WysiwygComposer">
56+
<div className="mx_WysiwygComposer_container">
57+
<div className="mx_WysiwygComposer_content"
58+
ref={ref}
59+
contentEditable={!disabled && isWysiwygReady}
60+
role="textbox"
61+
aria-multiline="true"
62+
aria-autocomplete="list"
63+
aria-haspopup="listbox"
64+
dir="auto"
65+
aria-disabled={disabled || !isWysiwygReady}
66+
/>
67+
</div>
68+
{ children?.(memoizedSendMessage) }
69+
</div>
70+
);
71+
}

0 commit comments

Comments
 (0)