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 (
{_t("Remove")}
); @@ -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)",