Improve remove recent messages dialog (#6114)

* Allow keeping state events when removing recent messages

The remove recent messages dialog redacts most state events since they
can be abuse vectors as well, however some users that see the option
actually want to use it to only remove messages. This adds a checkbox
option to do so.

Signed-off-by: Robin Townsend <robin@robin.town>

* Don't redact encryption events when removing recent messages

Signed-off-by: Robin Townsend <robin@robin.town>

* Show UserMenu spinner while removing recent messages

This also generalizes the UserMenu spinner to work with other types of
actions in the future.

Signed-off-by: Robin Townsend <robin@robin.town>

* Clarify remove recent messages warning

Clarify that they are removed for everyone in the conversation, not just
yourself.

Signed-off-by: Robin Townsend <robin@robin.town>

* Adjust copy and preserve state events by default

* Redact messages in reverse chronological order

Signed-off-by: Robin Townsend <robin@robin.town>
This commit is contained in:
Robin 2022-03-18 16:08:32 -04:00 committed by GitHub
parent d8a939df5d
commit 83f2bf4261
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 241 additions and 100 deletions

View file

@ -0,0 +1,136 @@
/*
Copyright 2021 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 React, { useState } from 'react';
import { logger } from "matrix-js-sdk/src/logger";
import { MatrixClient } from 'matrix-js-sdk/src/client';
import { RoomMember } from 'matrix-js-sdk/src/models/room-member';
import { Room } from 'matrix-js-sdk/src/models/room';
import { EventTimeline } from 'matrix-js-sdk/src/models/event-timeline';
import { EventType } from "matrix-js-sdk/src/@types/event";
import { _t } from '../../../languageHandler';
import dis from "../../../dispatcher/dispatcher";
import { Action } from "../../../dispatcher/actions";
import { IDialogProps } from "../dialogs/IDialogProps";
import BaseDialog from "../dialogs/BaseDialog";
import InfoDialog from "../dialogs/InfoDialog";
import DialogButtons from "../elements/DialogButtons";
import StyledCheckbox from "../elements/StyledCheckbox";
interface IBulkRedactDialogProps extends IDialogProps {
matrixClient: MatrixClient;
room: Room;
member: RoomMember;
}
const BulkRedactDialog: React.FC<IBulkRedactDialogProps> = props => {
const { matrixClient: cli, room, member, onFinished } = props;
const [keepStateEvents, setKeepStateEvents] = useState(true);
let timeline = room.getLiveTimeline();
let eventsToRedact = [];
while (timeline) {
eventsToRedact = [...eventsToRedact, ...timeline.getEvents().filter(event =>
event.getSender() === member.userId &&
!event.isRedacted() && !event.isRedaction() &&
event.getType() !== EventType.RoomCreate &&
// Don't redact ACLs because that'll obliterate the room
// See https://github.com/matrix-org/synapse/issues/4042 for details.
event.getType() !== EventType.RoomServerAcl &&
// Redacting encryption events is equally bad
event.getType() !== EventType.RoomEncryption,
)];
timeline = timeline.getNeighbouringTimeline(EventTimeline.BACKWARDS);
}
if (eventsToRedact.length === 0) {
return <InfoDialog
onFinished={onFinished}
title={_t("No recent messages by %(user)s found", { user: member.name })}
description={
<div>
<p>{ _t("Try scrolling up in the timeline to see if there are any earlier ones.") }</p>
</div>
}
/>;
} else {
eventsToRedact = eventsToRedact.filter(event => !(keepStateEvents && event.isState()));
const count = eventsToRedact.length;
const user = member.name;
const redact = async () => {
logger.info(`Started redacting recent ${count} messages for ${member.userId} in ${room.roomId}`);
dis.dispatch({
action: Action.BulkRedactStart,
room_id: room.roomId,
});
// Submitting a large number of redactions freezes the UI,
// so first yield to allow to rerender after closing the dialog.
await Promise.resolve();
await Promise.all(eventsToRedact.reverse().map(async event => {
try {
await cli.redactEvent(room.roomId, event.getId());
} catch (err) {
// log and swallow errors
logger.error("Could not redact", event.getId());
logger.error(err);
}
}));
logger.info(`Finished redacting recent ${count} messages for ${member.userId} in ${room.roomId}`);
dis.dispatch({
action: Action.BulkRedactEnd,
room_id: room.roomId,
});
};
return <BaseDialog
className="mx_BulkRedactDialog"
onFinished={onFinished}
title={_t("Remove recent messages by %(user)s", { user })}
contentId="mx_Dialog_content"
>
<div className="mx_Dialog_content" id="mx_Dialog_content">
<p>{ _t("You are about to remove %(count)s messages by %(user)s. " +
"This will remove them permanently for everyone in the conversation. " +
"Do you wish to continue?", { count, user }) }</p>
<p>{ _t("For a large amount of messages, this might take some time. " +
"Please don't refresh your client in the meantime.") }</p>
<StyledCheckbox
checked={keepStateEvents}
onChange={e => setKeepStateEvents(e.target.checked)}
>
{ _t("Preserve system messages") }
</StyledCheckbox>
<div className="mx_BulkRedactDialog_checkboxMicrocopy">
{ _t("Uncheck if you also want to remove system messages on this user " +
"(e.g. membership change, profile change…)") }
</div>
</div>
<DialogButtons
primaryButton={_t("Remove %(count)s messages", { count })}
primaryButtonClass="danger"
primaryDisabled={count === 0}
onPrimaryButtonClick={() => { setImmediate(redact); onFinished(true); }}
onCancel={() => onFinished(false)}
/>
</BaseDialog>;
}
};
export default BulkRedactDialog;