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

Commit 6e73a85

Browse files
authored
Merge pull request #9488 from matrix-org/feat/add-message-edition-wysiwyg-composer
Add message editing to wysiwyg composer
2 parents d473b4a + b5ab123 commit 6e73a85

37 files changed

+1679
-585
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.2.0",
60-
"@matrix-org/matrix-wysiwyg": "^0.3.0",
60+
"@matrix-org/matrix-wysiwyg": "^0.3.2",
6161
"@matrix-org/react-sdk-module-api": "^0.0.3",
6262
"@sentry/browser": "^6.11.0",
6363
"@sentry/tracing": "^6.11.0",

res/css/_components.pcss

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,8 +299,10 @@
299299
@import "./views/rooms/_TopUnreadMessagesBar.pcss";
300300
@import "./views/rooms/_VoiceRecordComposerTile.pcss";
301301
@import "./views/rooms/_WhoIsTypingTile.pcss";
302-
@import "./views/rooms/wysiwyg_composer/_FormattingButtons.pcss";
303-
@import "./views/rooms/wysiwyg_composer/_WysiwygComposer.pcss";
302+
@import "./views/rooms/wysiwyg_composer/components/_Editor.pcss";
303+
@import "./views/rooms/wysiwyg_composer/components/_FormattingButtons.pcss";
304+
@import "./views/rooms/wysiwyg_composer/_SendWysiwygComposer.pcss";
305+
@import "./views/rooms/wysiwyg_composer/_EditWysiwygComposer.pcss";
304306
@import "./views/settings/_AvatarSetting.pcss";
305307
@import "./views/settings/_CrossSigningPanel.pcss";
306308
@import "./views/settings/_CryptographyPanel.pcss";
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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_EditWysiwygComposer {
18+
--EditWysiwygComposer-padding-inline: 3px;
19+
20+
display: flex;
21+
flex-direction: column;
22+
max-width: 100%; /* disable overflow */
23+
width: auto;
24+
gap: 8px;
25+
padding: 8px var(--EditWysiwygComposer-padding-inline);
26+
27+
.mx_WysiwygComposer_content {
28+
border-radius: 4px;
29+
border: solid 1px $primary-hairline-color;
30+
background-color: $background;
31+
max-height: 200px;
32+
padding: 3px 6px;
33+
34+
&:focus {
35+
border-color: rgba($accent, 0.5); /* Only ever used here */
36+
}
37+
}
38+
39+
.mx_EditWysiwygComposer_buttons {
40+
display: flex;
41+
flex-flow: row wrap-reverse; /* display "Save" over "Cancel" */
42+
justify-content: flex-end;
43+
gap: 5px;
44+
margin-inline-start: auto;
45+
46+
.mx_AccessibleButton {
47+
flex: 1;
48+
box-sizing: border-box;
49+
min-width: 100px; /* magic number to align the edge of the button with the input area */
50+
}
51+
}
52+
53+
.mx_FormattingButtons_Button {
54+
&:first-child {
55+
margin-left: 0px;
56+
}
57+
}
58+
}

res/css/views/rooms/wysiwyg_composer/_WysiwygComposer.pcss renamed to res/css/views/rooms/wysiwyg_composer/_SendWysiwygComposer.pcss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
.mx_WysiwygComposer {
17+
.mx_SendWysiwygComposer {
1818
flex: 1;
1919
display: flex;
2020
flex-direction: column;
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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_container {
18+
position: relative;
19+
20+
@keyframes visualbell {
21+
from { background-color: $visual-bell-bg-color; }
22+
to { background-color: $background; }
23+
}
24+
25+
.mx_WysiwygComposer_content {
26+
white-space: pre-wrap;
27+
word-wrap: break-word;
28+
outline: none;
29+
overflow-x: hidden;
30+
31+
/* Force caret nodes to be selected in full so that they can be */
32+
/* navigated through in a single keypress */
33+
.caretNode {
34+
user-select: all;
35+
}
36+
}
37+
}

res/css/views/rooms/wysiwyg_composer/_FormattingButtons.pcss renamed to res/css/views/rooms/wysiwyg_composer/components/_FormattingButtons.pcss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ limitations under the License.
4545
left: 6px;
4646
height: 16px;
4747
width: 16px;
48-
background-color: $icon-button-color;
48+
background-color: $tertiary-content;
4949
mask-repeat: no-repeat;
5050
mask-size: contain;
5151
mask-position: center;

src/components/views/messages/TextualBody.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import RoomContext from "../../../contexts/RoomContext";
4848
import AccessibleButton from '../elements/AccessibleButton';
4949
import { options as linkifyOpts } from "../../../linkify-matrix";
5050
import { getParentEventId } from '../../../utils/Reply';
51+
import { EditWysiwygComposer } from '../rooms/wysiwyg_composer';
5152

5253
const MAX_HIGHLIGHT_LENGTH = 4096;
5354

@@ -562,7 +563,10 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
562563

563564
render() {
564565
if (this.props.editState) {
565-
return <EditMessageComposer editState={this.props.editState} className="mx_EventTile_content" />;
566+
const isWysiwygComposerEnabled = SettingsStore.getValue("feature_wysiwyg_composer");
567+
return isWysiwygComposerEnabled ?
568+
<EditWysiwygComposer editorStateTransfer={this.props.editState} className="mx_EventTile_content" /> :
569+
<EditMessageComposer editState={this.props.editState} className="mx_EventTile_content" />;
566570
}
567571
const mxEvent = this.props.mxEvent;
568572
const content = mxEvent.getContent();

src/components/views/rooms/MessageComposer.tsx

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ import {
5858
startNewVoiceBroadcastRecording,
5959
VoiceBroadcastRecordingsStore,
6060
} from '../../../voice-broadcast';
61-
import { WysiwygComposer } from './wysiwyg_composer/WysiwygComposer';
61+
import { SendWysiwygComposer, sendMessage } from './wysiwyg_composer/';
62+
import { MatrixClientProps, withMatrixClientHOC } from '../../../contexts/MatrixClientContext';
6263

6364
let instanceCount = 0;
6465

@@ -78,7 +79,7 @@ function SendButton(props: ISendButtonProps) {
7879
);
7980
}
8081

81-
interface IProps {
82+
interface IProps extends MatrixClientProps {
8283
room: Room;
8384
resizeNotifier: ResizeNotifier;
8485
permalinkCreator: RoomPermalinkCreator;
@@ -89,6 +90,7 @@ interface IProps {
8990
}
9091

9192
interface IState {
93+
composerContent: string;
9294
isComposerEmpty: boolean;
9395
haveRecording: boolean;
9496
recordingTimeLeftSeconds?: number;
@@ -100,13 +102,12 @@ interface IState {
100102
showVoiceBroadcastButton: boolean;
101103
}
102104

103-
export default class MessageComposer extends React.Component<IProps, IState> {
105+
export class MessageComposer extends React.Component<IProps, IState> {
104106
private dispatcherRef?: string;
105107
private messageComposerInput = createRef<SendMessageComposerClass>();
106108
private voiceRecordingButton = createRef<VoiceRecordComposerTile>();
107109
private ref: React.RefObject<HTMLDivElement> = createRef();
108110
private instanceId: number;
109-
private composerSendMessage?: () => void;
110111

111112
private _voiceRecording: Optional<VoiceMessageRecording>;
112113

@@ -124,6 +125,7 @@ export default class MessageComposer extends React.Component<IProps, IState> {
124125

125126
this.state = {
126127
isComposerEmpty: true,
128+
composerContent: '',
127129
haveRecording: false,
128130
recordingTimeLeftSeconds: undefined, // when set to a number, shows a toast
129131
isMenuOpen: false,
@@ -315,7 +317,14 @@ export default class MessageComposer extends React.Component<IProps, IState> {
315317
}
316318

317319
this.messageComposerInput.current?.sendMessage();
318-
this.composerSendMessage?.();
320+
321+
const isWysiwygComposerEnabled = SettingsStore.getValue("feature_wysiwyg_composer");
322+
if (isWysiwygComposerEnabled) {
323+
const { permalinkCreator, relation, replyToEvent } = this.props;
324+
sendMessage(this.state.composerContent,
325+
{ mxClient: this.props.mxClient, roomContext: this.context, permalinkCreator, relation, replyToEvent });
326+
dis.dispatch({ action: Action.ClearAndFocusSendMessageComposer });
327+
}
319328
};
320329

321330
private onChange = (model: EditorModel) => {
@@ -326,6 +335,7 @@ export default class MessageComposer extends React.Component<IProps, IState> {
326335

327336
private onWysiwygChange = (content: string) => {
328337
this.setState({
338+
composerContent: content,
329339
isComposerEmpty: content?.length === 0,
330340
});
331341
};
@@ -402,16 +412,11 @@ export default class MessageComposer extends React.Component<IProps, IState> {
402412
if (canSendMessages) {
403413
if (isWysiwygComposerEnabled) {
404414
controls.push(
405-
<WysiwygComposer key="controls_input"
415+
<SendWysiwygComposer key="controls_input"
406416
disabled={this.state.haveRecording}
407417
onChange={this.onWysiwygChange}
408-
permalinkCreator={this.props.permalinkCreator}
409-
relation={this.props.relation}
410-
replyToEvent={this.props.replyToEvent}>
411-
{ (sendMessage) => {
412-
this.composerSendMessage = sendMessage;
413-
} }
414-
</WysiwygComposer>,
418+
onSend={this.sendMessage}
419+
/>,
415420
);
416421
} else {
417422
controls.push(
@@ -551,3 +556,6 @@ export default class MessageComposer extends React.Component<IProps, IState> {
551556
);
552557
}
553558
}
559+
560+
const MessageComposerWithMatrixClient = withMatrixClientHOC(MessageComposer);
561+
export default MessageComposerWithMatrixClient;
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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, { forwardRef, RefObject } from 'react';
18+
import classNames from 'classnames';
19+
20+
import EditorStateTransfer from '../../../../utils/EditorStateTransfer';
21+
import { WysiwygComposer } from './components/WysiwygComposer';
22+
import { EditionButtons } from './components/EditionButtons';
23+
import { useWysiwygEditActionHandler } from './hooks/useWysiwygEditActionHandler';
24+
import { useEditing } from './hooks/useEditing';
25+
import { useInitialContent } from './hooks/useInitialContent';
26+
27+
interface ContentProps {
28+
disabled: boolean;
29+
}
30+
31+
const Content = forwardRef<HTMLElement, ContentProps>(
32+
function Content({ disabled }: ContentProps, forwardRef: RefObject<HTMLElement>) {
33+
useWysiwygEditActionHandler(disabled, forwardRef);
34+
return null;
35+
},
36+
);
37+
38+
interface EditWysiwygComposerProps {
39+
disabled?: boolean;
40+
onChange?: (content: string) => void;
41+
editorStateTransfer: EditorStateTransfer;
42+
className?: string;
43+
}
44+
45+
export function EditWysiwygComposer({ editorStateTransfer, className, ...props }: EditWysiwygComposerProps) {
46+
const initialContent = useInitialContent(editorStateTransfer);
47+
const isReady = !editorStateTransfer || Boolean(initialContent);
48+
49+
const { editMessage, endEditing, onChange, isSaveDisabled } = useEditing(initialContent, editorStateTransfer);
50+
51+
return isReady && <WysiwygComposer
52+
className={classNames("mx_EditWysiwygComposer", className)}
53+
initialContent={initialContent}
54+
onChange={onChange}
55+
onSend={editMessage}
56+
{...props}>
57+
{ (ref) => (
58+
<>
59+
<Content disabled={props.disabled} ref={ref} />
60+
<EditionButtons onCancelClick={endEditing} onSaveClick={editMessage} isSaveDisabled={isSaveDisabled} />
61+
</>)
62+
}
63+
</WysiwygComposer>;
64+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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, { forwardRef, RefObject } from 'react';
18+
import { FormattingFunctions } from '@matrix-org/matrix-wysiwyg';
19+
20+
import { useWysiwygSendActionHandler } from './hooks/useWysiwygSendActionHandler';
21+
import { WysiwygComposer } from './components/WysiwygComposer';
22+
23+
interface SendWysiwygComposerProps {
24+
disabled?: boolean;
25+
onChange: (content: string) => void;
26+
onSend: () => void;
27+
}
28+
interface ContentProps {
29+
disabled: boolean;
30+
formattingFunctions: FormattingFunctions;
31+
}
32+
33+
const Content = forwardRef<HTMLElement, ContentProps>(
34+
function Content({ disabled, formattingFunctions: wysiwyg }: ContentProps, forwardRef: RefObject<HTMLElement>) {
35+
useWysiwygSendActionHandler(disabled, forwardRef, wysiwyg);
36+
return null;
37+
},
38+
);
39+
40+
export function SendWysiwygComposer(props: SendWysiwygComposerProps) {
41+
return (
42+
<WysiwygComposer className="mx_SendWysiwygComposer" {...props}>{ (ref, wysiwyg) => (
43+
<Content disabled={props.disabled} ref={ref} formattingFunctions={wysiwyg} />
44+
) }
45+
</WysiwygComposer>);
46+
}

0 commit comments

Comments
 (0)