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

Commit 0e60ad9

Browse files
authored
Hide composer and call buttons when the room is tombstoned (#7975)
1 parent 675b427 commit 0e60ad9

File tree

11 files changed

+196
-93
lines changed

11 files changed

+196
-93
lines changed

src/components/structures/RoomView.tsx

+26-20
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,8 @@ export interface IRoomState {
187187

188188
upgradeRecommendation?: IRecommendedVersion;
189189
canReact: boolean;
190-
canReply: boolean;
190+
canSendMessages: boolean;
191+
tombstone?: MatrixEvent;
191192
layout: Layout;
192193
lowBandwidth: boolean;
193194
alwaysShowTimestamps: boolean;
@@ -259,7 +260,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
259260
showTopUnreadMessagesBar: false,
260261
statusBarVisible: false,
261262
canReact: false,
262-
canReply: false,
263+
canSendMessages: false,
263264
layout: SettingsStore.getValue("layout"),
264265
lowBandwidth: SettingsStore.getValue("lowBandwidth"),
265266
alwaysShowTimestamps: SettingsStore.getValue("alwaysShowTimestamps"),
@@ -371,12 +372,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
371372
: MainSplitContentType.Timeline;
372373
};
373374

374-
private onReadReceiptsChange = () => {
375-
this.setState({
376-
showReadReceipts: SettingsStore.getValue("showReadReceipts", this.state.roomId),
377-
});
378-
};
379-
380375
private onRoomViewStoreUpdate = async (initial?: boolean): Promise<void> => {
381376
if (this.unmounted) {
382377
return;
@@ -1033,10 +1028,15 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
10331028
this.checkWidgets(room);
10341029

10351030
this.setState({
1031+
tombstone: this.getRoomTombstone(),
10361032
liveTimeline: room.getLiveTimeline(),
10371033
});
10381034
};
10391035

1036+
private getRoomTombstone() {
1037+
return this.state.room?.currentState.getStateEvents(EventType.RoomTombstone, "");
1038+
}
1039+
10401040
private async calculateRecommendedVersion(room: Room) {
10411041
const upgradeRecommendation = await room.getRecommendedVersion();
10421042
if (this.unmounted) return;
@@ -1167,17 +1167,23 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
11671167
// ignore if we don't have a room yet
11681168
if (!this.state.room || this.state.room.roomId !== state.roomId) return;
11691169

1170-
if (ev.getType() === EventType.RoomCanonicalAlias) {
1171-
// re-view the room so MatrixChat can manage the alias in the URL properly
1172-
dis.dispatch<ViewRoomPayload>({
1173-
action: Action.ViewRoom,
1174-
room_id: this.state.room.roomId,
1175-
metricsTrigger: undefined, // room doesn't change
1176-
});
1177-
return; // this event cannot affect permissions so bail
1178-
}
1170+
switch (ev.getType()) {
1171+
case EventType.RoomCanonicalAlias:
1172+
// re-view the room so MatrixChat can manage the alias in the URL properly
1173+
dis.dispatch<ViewRoomPayload>({
1174+
action: Action.ViewRoom,
1175+
room_id: this.state.room.roomId,
1176+
metricsTrigger: undefined, // room doesn't change
1177+
});
1178+
break;
1179+
1180+
case EventType.RoomTombstone:
1181+
this.setState({ tombstone: this.getRoomTombstone() });
1182+
break;
11791183

1180-
this.updatePermissions(this.state.room);
1184+
default:
1185+
this.updatePermissions(this.state.room);
1186+
}
11811187
};
11821188

11831189
private onRoomStateUpdate = (state: RoomState) => {
@@ -1201,9 +1207,9 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
12011207
if (room) {
12021208
const me = this.context.getUserId();
12031209
const canReact = room.getMyMembership() === "join" && room.currentState.maySendEvent("m.reaction", me);
1204-
const canReply = room.maySendMessage();
1210+
const canSendMessages = room.maySendMessage();
12051211

1206-
this.setState({ canReact, canReply });
1212+
this.setState({ canReact, canSendMessages });
12071213
}
12081214
}
12091215

src/components/views/messages/MessageActionBar.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
334334
// Like the resend button, the react and reply buttons need to appear before the edit.
335335
// The only catch is we do the reply button first so that we can make sure the react
336336
// button is the very first button without having to do length checks for `splice()`.
337-
if (this.context.canReply) {
337+
if (this.context.canSendMessages) {
338338
if (this.showReplyInThreadAction) {
339339
toolbarOpts.splice(0, 0, threadTooltipButton);
340340
}

src/components/views/rooms/MessageComposer.tsx

+7-30
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import { IEventRelation, MatrixEvent } from "matrix-js-sdk/src/models/event";
2020
import { Room } from "matrix-js-sdk/src/models/room";
2121
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
2222
import { EventType, RelationType } from 'matrix-js-sdk/src/@types/event';
23-
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
2423
import { Optional } from "matrix-events-sdk";
2524

2625
import { _t } from '../../../languageHandler';
@@ -81,8 +80,6 @@ interface IProps {
8180
}
8281

8382
interface IState {
84-
tombstone: MatrixEvent;
85-
canSendMessages: boolean;
8683
isComposerEmpty: boolean;
8784
haveRecording: boolean;
8885
recordingTimeLeftSeconds?: number;
@@ -114,8 +111,6 @@ export default class MessageComposer extends React.Component<IProps, IState> {
114111
VoiceRecordingStore.instance.on(UPDATE_EVENT, this.onVoiceStoreUpdate);
115112

116113
this.state = {
117-
tombstone: this.getRoomTombstone(),
118-
canSendMessages: this.props.room.maySendMessage(),
119114
isComposerEmpty: true,
120115
haveRecording: false,
121116
recordingTimeLeftSeconds: null, // when set to a number, shows a toast
@@ -154,7 +149,6 @@ export default class MessageComposer extends React.Component<IProps, IState> {
154149

155150
public componentDidMount() {
156151
this.dispatcherRef = dis.register(this.onAction);
157-
MatrixClientPeg.get().on(RoomStateEvent.Events, this.onRoomStateEvents);
158152
this.waitForOwnMember();
159153
UIStore.instance.trackElementDimensions(`MessageComposer${this.instanceId}`, this.ref.current);
160154
UIStore.instance.on(`MessageComposer${this.instanceId}`, this.onResize);
@@ -217,9 +211,6 @@ export default class MessageComposer extends React.Component<IProps, IState> {
217211
}
218212

219213
public componentWillUnmount() {
220-
if (MatrixClientPeg.get()) {
221-
MatrixClientPeg.get().removeListener(RoomStateEvent.Events, this.onRoomStateEvents);
222-
}
223214
VoiceRecordingStore.instance.off(UPDATE_EVENT, this.onVoiceStoreUpdate);
224215
dis.unregister(this.dispatcherRef);
225216
UIStore.instance.stopTrackingElementDimensions(`MessageComposer${this.instanceId}`);
@@ -229,33 +220,18 @@ export default class MessageComposer extends React.Component<IProps, IState> {
229220
this.voiceRecording = null;
230221
}
231222

232-
private onRoomStateEvents = (ev: MatrixEvent) => {
233-
if (ev.getRoomId() !== this.props.room.roomId) return;
234-
235-
if (ev.getType() === EventType.RoomTombstone) {
236-
this.setState({ tombstone: this.getRoomTombstone() });
237-
}
238-
if (ev.getType() === EventType.RoomPowerLevels) {
239-
this.setState({ canSendMessages: this.props.room.maySendMessage() });
240-
}
241-
};
242-
243-
private getRoomTombstone() {
244-
return this.props.room.currentState.getStateEvents(EventType.RoomTombstone, '');
245-
}
246-
247223
private onTombstoneClick = (ev) => {
248224
ev.preventDefault();
249225

250-
const replacementRoomId = this.state.tombstone.getContent()['replacement_room'];
226+
const replacementRoomId = this.context.tombstone.getContent()['replacement_room'];
251227
const replacementRoom = MatrixClientPeg.get().getRoom(replacementRoomId);
252228
let createEventId = null;
253229
if (replacementRoom) {
254230
const createEvent = replacementRoom.currentState.getStateEvents(EventType.RoomCreate, '');
255231
if (createEvent && createEvent.getId()) createEventId = createEvent.getId();
256232
}
257233

258-
const viaServers = [this.state.tombstone.getSender().split(':').slice(1).join(':')];
234+
const viaServers = [this.context.tombstone.getSender().split(':').slice(1).join(':')];
259235
dis.dispatch<ViewRoomPayload>({
260236
action: Action.ViewRoom,
261237
highlighted: true,
@@ -374,7 +350,8 @@ export default class MessageComposer extends React.Component<IProps, IState> {
374350
menuPosition = aboveLeftOf(contentRect);
375351
}
376352

377-
if (!this.state.tombstone && this.state.canSendMessages) {
353+
const canSendMessages = this.context.canSendMessages && !this.context.tombstone;
354+
if (canSendMessages) {
378355
controls.push(
379356
<SendMessageComposer
380357
ref={this.messageComposerInput}
@@ -393,8 +370,8 @@ export default class MessageComposer extends React.Component<IProps, IState> {
393370
key="controls_voice_record"
394371
ref={this.voiceRecordingButton}
395372
room={this.props.room} />);
396-
} else if (this.state.tombstone) {
397-
const replacementRoomId = this.state.tombstone.getContent()['replacement_room'];
373+
} else if (this.context.tombstone) {
374+
const replacementRoomId = this.context.tombstone.getContent()['replacement_room'];
398375

399376
const continuesLink = replacementRoomId ? (
400377
<a href={makeRoomPermalink(replacementRoomId)}
@@ -467,7 +444,7 @@ export default class MessageComposer extends React.Component<IProps, IState> {
467444
permalinkCreator={this.props.permalinkCreator} />
468445
<div className="mx_MessageComposer_row">
469446
{ controls }
470-
{ this.state.canSendMessages && <MessageComposerButtons
447+
{ canSendMessages && <MessageComposerButtons
471448
addEmoji={this.addEmoji}
472449
haveRecording={this.state.haveRecording}
473450
isMenuOpen={this.state.isMenuOpen}

src/components/views/rooms/RoomHeader.tsx

+8-1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { contextMenuBelow } from './RoomTile';
4040
import { RoomNotificationStateStore } from '../../../stores/notifications/RoomNotificationStateStore';
4141
import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases';
4242
import { NotificationStateEvents } from '../../../stores/notifications/NotificationState';
43+
import RoomContext from "../../../contexts/RoomContext";
4344

4445
export interface ISearchInfo {
4546
searchTerm: string;
@@ -73,6 +74,9 @@ export default class RoomHeader extends React.Component<IProps, IState> {
7374
excludedRightPanelPhaseButtons: [],
7475
};
7576

77+
static contextType = RoomContext;
78+
public context!: React.ContextType<typeof RoomContext>;
79+
7680
constructor(props, context) {
7781
super(props, context);
7882
const notiStore = RoomNotificationStateStore.instance.getRoomState(props.room);
@@ -200,7 +204,10 @@ export default class RoomHeader extends React.Component<IProps, IState> {
200204

201205
const buttons: JSX.Element[] = [];
202206

203-
if (this.props.inRoom && SettingsStore.getValue("showCallButtonsInComposer")) {
207+
if (this.props.inRoom &&
208+
!this.context.tombstone &&
209+
SettingsStore.getValue("showCallButtonsInComposer")
210+
) {
204211
const voiceCallButton = <AccessibleTooltipButton
205212
className="mx_RoomHeader_button mx_RoomHeader_voiceCallButton"
206213
onClick={() => this.props.onCallPlaced(CallType.Voice)}

src/contexts/RoomContext.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ const RoomContext = createContext<IRoomState>({
4646
showTopUnreadMessagesBar: false,
4747
statusBarVisible: false,
4848
canReact: false,
49-
canReply: false,
49+
canSendMessages: false,
5050
layout: Layout.Group,
5151
lowBandwidth: false,
5252
alwaysShowTimestamps: false,

test/components/structures/MessagePanel-test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class WrappedMessagePanel extends React.Component {
5050
room,
5151
roomId: room.roomId,
5252
canReact: true,
53-
canReply: true,
53+
canSendMessages: true,
5454
showReadReceipts: true,
5555
showRedactions: false,
5656
showJoinLeaves: false,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
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 * as React from "react";
18+
import { mount, ReactWrapper } from "enzyme";
19+
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
20+
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
21+
22+
import '../../../skinned-sdk'; // Must be first for skinning to work
23+
import { createTestClient, mkEvent, mkStubRoom, stubClient } from "../../../test-utils";
24+
import MessageComposer from "../../../../src/components/views/rooms/MessageComposer";
25+
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
26+
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
27+
import RoomContext from "../../../../src/contexts/RoomContext";
28+
29+
describe("MessageComposer", () => {
30+
stubClient();
31+
const cli = createTestClient();
32+
const room = mkStubRoom("!roomId:server", "Room 1", cli);
33+
34+
it("Renders a SendMessageComposer and MessageComposerButtons by default", () => {
35+
const wrapper = wrapAndRender((
36+
<MessageComposer room={room} resizeNotifier={jest.fn()} permalinkCreator={jest.fn()} />
37+
));
38+
39+
expect(wrapper.find("SendMessageComposer")).toHaveLength(1);
40+
expect(wrapper.find("MessageComposerButtons")).toHaveLength(1);
41+
});
42+
43+
it("Does not render a SendMessageComposer or MessageComposerButtons when user has no permission", () => {
44+
const wrapper = wrapAndRender((
45+
<MessageComposer room={room} resizeNotifier={jest.fn()} permalinkCreator={jest.fn()} />
46+
), false);
47+
48+
expect(wrapper.find("SendMessageComposer")).toHaveLength(0);
49+
expect(wrapper.find("MessageComposerButtons")).toHaveLength(0);
50+
expect(wrapper.find(".mx_MessageComposer_noperm_error")).toHaveLength(1);
51+
});
52+
53+
it("Does not render a SendMessageComposer or MessageComposerButtons when room is tombstoned", () => {
54+
const wrapper = wrapAndRender((
55+
<MessageComposer room={room} resizeNotifier={jest.fn()} permalinkCreator={jest.fn()} />
56+
), true, mkEvent({
57+
event: true,
58+
type: "m.room.tombstone",
59+
room: room.roomId,
60+
user: "@user1:server",
61+
skey: "",
62+
content: {},
63+
ts: Date.now(),
64+
}));
65+
66+
expect(wrapper.find("SendMessageComposer")).toHaveLength(0);
67+
expect(wrapper.find("MessageComposerButtons")).toHaveLength(0);
68+
expect(wrapper.find(".mx_MessageComposer_roomReplaced_header")).toHaveLength(1);
69+
});
70+
});
71+
72+
function wrapAndRender(component: React.ReactElement, canSendMessages = true, tombstone?: MatrixEvent): ReactWrapper {
73+
const mockClient = MatrixClientPeg.get();
74+
const roomId = "myroomid";
75+
const room: any = {
76+
currentState: undefined,
77+
roomId,
78+
client: mockClient,
79+
getMember: function(userId: string): RoomMember {
80+
return new RoomMember(roomId, userId);
81+
},
82+
};
83+
return mount(
84+
<MatrixClientContext.Provider value={mockClient}>
85+
<RoomContext.Provider value={{ room, canSendMessages, tombstone }}>
86+
{ component }
87+
</RoomContext.Provider>
88+
</MatrixClientContext.Provider>,
89+
);
90+
}

test/components/views/rooms/MessageComposerButtons-test.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ function createRoomState(room: Room, narrow: boolean): IRoomState {
160160
showTopUnreadMessagesBar: false,
161161
statusBarVisible: false,
162162
canReact: false,
163-
canReply: false,
163+
canSendMessages: false,
164164
layout: Layout.Group,
165165
lowBandwidth: false,
166166
alwaysShowTimestamps: false,

0 commit comments

Comments
 (0)