diff --git a/src/components/views/dialogs/ConfirmAndWaitRedactDialog.js b/src/components/views/dialogs/ConfirmAndWaitRedactDialog.js
new file mode 100644
index 0000000000..5fd5129550
--- /dev/null
+++ b/src/components/views/dialogs/ConfirmAndWaitRedactDialog.js
@@ -0,0 +1,87 @@
+/*
+Copyright 2019 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 from 'react';
+import sdk from '../../../index';
+import { _t } from '../../../languageHandler';
+
+/*
+ * A dialog for confirming a redaction.
+ * Also shows a spinner (and possible error) while the redaction is ongoing,
+ * and only closes the dialog when the redaction is done or failed.
+ *
+ * This is done to prevent the edit history dialog racing with the redaction:
+ * if this dialog closes and the MessageEditHistoryDialog is shown again,
+ * it will fetch the relations again, which will race with the ongoing /redact request.
+ * which will cause the edit to appear unredacted.
+ *
+ * To avoid this, we keep the dialog open as long as /redact is in progress.
+ */
+export default class ConfirmAndWaitRedactDialog extends React.PureComponent {
+ constructor(props) {
+ super(props);
+ this.state = {
+ isRedacting: false,
+ redactionErrorCode: null,
+ };
+ }
+
+ onParentFinished = async (proceed) => {
+ if (proceed) {
+ this.setState({isRedacting: true});
+ try {
+ await this.props.redact();
+ this.props.onFinished(true);
+ } catch (error) {
+ const code = error.errcode || error.statusCode;
+ if (typeof code !== "undefined") {
+ this.setState({redactionErrorCode: code});
+ } else {
+ this.props.onFinished(true);
+ }
+ }
+ } else {
+ this.props.onFinished(false);
+ }
+ };
+
+ render() {
+ if (this.state.isRedacting) {
+ if (this.state.redactionErrorCode) {
+ const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+ const code = this.state.redactionErrorCode;
+ return (
+
+ );
+ } else {
+ const BaseDialog = sdk.getComponent("dialogs.BaseDialog");
+ const Spinner = sdk.getComponent('elements.Spinner');
+ return (
+ );
+ }
+ } else {
+ const ConfirmRedactDialog = sdk.getComponent("dialogs.ConfirmRedactDialog");
+ return ();
+ }
+ }
+}
diff --git a/src/components/views/messages/EditHistoryMessage.js b/src/components/views/messages/EditHistoryMessage.js
index 5b00257e87..67ab2f0e2c 100644
--- a/src/components/views/messages/EditHistoryMessage.js
+++ b/src/components/views/messages/EditHistoryMessage.js
@@ -22,6 +22,9 @@ import {MatrixEvent} from 'matrix-js-sdk';
import {pillifyLinks} from '../../../utils/pillify';
import { _t } from '../../../languageHandler';
import sdk from '../../../index';
+import MatrixClientPeg from '../../../MatrixClientPeg';
+import Modal from '../../../Modal';
+import classNames from 'classnames';
export default class EditHistoryMessage extends React.PureComponent {
static propTypes = {
@@ -29,19 +32,46 @@ export default class EditHistoryMessage extends React.PureComponent {
mxEvent: PropTypes.instanceOf(MatrixEvent).isRequired,
};
+ constructor(props) {
+ super(props);
+ const cli = MatrixClientPeg.get();
+ const {userId} = cli.credentials;
+ const event = this.props.mxEvent;
+ const room = cli.getRoom(event.getRoomId());
+ const canRedact = room.currentState.maySendRedactionForEvent(event, userId);
+ this.state = {canRedact};
+ }
+
_onRedactClick = async () => {
+ const event = this.props.mxEvent;
+ const cli = MatrixClientPeg.get();
+ const ConfirmAndWaitRedactDialog = sdk.getComponent("dialogs.ConfirmAndWaitRedactDialog");
+
+ Modal.createTrackedDialog('Confirm Redact Dialog from edit history', '', ConfirmAndWaitRedactDialog, {
+ redact: () => cli.redactEvent(event.getRoomId(), event.getId()),
+ }, 'mx_Dialog_confirmredact');
};
+ pillifyLinks() {
+ // not present for redacted events
+ if (this.refs.content) {
+ pillifyLinks(this.refs.content.children, this.props.mxEvent);
+ }
+ }
+
componentDidMount() {
- pillifyLinks(this.refs.content.children, this.props.mxEvent);
+ this.pillifyLinks();
}
componentDidUpdate() {
- pillifyLinks(this.refs.content.children, this.props.mxEvent);
+ this.pillifyLinks();
}
_renderActionBar() {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
+ if (this.props.mxEvent.isRedacted()) {
+ return null;
+ }
return (
);
@@ -52,8 +82,12 @@ export default class EditHistoryMessage extends React.PureComponent {
const originalContent = mxEvent.getOriginalContent();
const content = originalContent["m.new_content"] || originalContent;
const contentElements = HtmlUtils.bodyToHtml(content);
+ const isRedacted = this.props.mxEvent.isRedacted();
let contentContainer;
- if (mxEvent.getContent().msgtype === "m.emote") {
+ if (isRedacted) {
+ const UnknownBody = sdk.getComponent('messages.UnknownBody');
+ contentContainer = ();
+ } else if (mxEvent.getContent().msgtype === "m.emote") {
const name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender();
contentContainer = (*
{ name }
@@ -63,7 +97,15 @@ export default class EditHistoryMessage extends React.PureComponent {
contentContainer = (
{contentElements}
);
}
const timestamp = formatTime(new Date(mxEvent.getTs()), this.props.isTwelveHour);
- return
+ const sendStatus = mxEvent.getAssociatedStatus();
+ const isSending = (['sending', 'queued', 'encrypting'].indexOf(sendStatus) !== -1);
+ const classes = classNames({
+ "mx_EventTile": true,
+ "mx_EventTile_redacted": isRedacted,
+ "mx_EventTile_sending": isSending,
+ "mx_EventTile_notSent": sendStatus === 'not_sent',
+ });
+ return
{timestamp}
{ contentContainer }
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 0f1b6ebbc1..acabe191b4 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -93,6 +93,7 @@
"Failed to add the following rooms to %(groupId)s:": "Failed to add the following rooms to %(groupId)s:",
"Unnamed Room": "Unnamed Room",
"Error": "Error",
+ "You cannot delete this message. (%(code)s)": "You cannot delete this message. (%(code)s)",
"Unable to load! Check your network connectivity and try again.": "Unable to load! Check your network connectivity and try again.",
"Dismiss": "Dismiss",
"Riot does not have permission to send you notifications - please check your browser settings": "Riot does not have permission to send you notifications - please check your browser settings",
@@ -1125,6 +1126,7 @@
"Start chatting": "Start chatting",
"Click on the button below to start chatting!": "Click on the button below to start chatting!",
"Start Chatting": "Start Chatting",
+ "Removing…": "Removing…",
"Confirm Removal": "Confirm Removal",
"Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.",
"Community IDs cannot be empty.": "Community IDs cannot be empty.",
@@ -1306,7 +1308,6 @@
"Reject invitation": "Reject invitation",
"Are you sure you want to reject the invitation?": "Are you sure you want to reject the invitation?",
"Unable to reject invite": "Unable to reject invite",
- "You cannot delete this message. (%(code)s)": "You cannot delete this message. (%(code)s)",
"Resend": "Resend",
"Resend edit": "Resend edit",
"Resend %(unsentCount)s reaction(s)": "Resend %(unsentCount)s reaction(s)",