diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 29f6242c4b1..545f65f65e8 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -458,6 +458,15 @@ $left-gutter: 64px; opacity: 1; } +.mx_EventTile_e2eIcon_viewingRedacted { + &::after { + mask-image: url('$(res)/img/feather-customised/trash.custom.svg'); + background-color: $muted-fg-color; + } + + opacity: 1; +} + /* Various markdown overrides */ .mx_EventTile_body { diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 75cdebc9535..5d333ddbfcf 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -83,6 +83,7 @@ interface IProps extends IPosition { interface IState { canRedact: boolean; + canViewRedacted: boolean; canPin: boolean; } @@ -92,6 +93,7 @@ export default class MessageContextMenu extends React.Component state = { canRedact: false, + canViewRedacted: false, canPin: false, }; @@ -118,11 +120,15 @@ export default class MessageContextMenu extends React.Component && this.props.mxEvent.getType() !== EventType.RoomServerAcl && this.props.mxEvent.getType() !== EventType.RoomEncryption; let canPin = room.currentState.mayClientSendStateEvent(EventType.RoomPinnedEvents, cli); + // TODO check for server support first + // TODO allow if isSynapseAdmin too + const canViewRedacted = room.currentState.hasSufficientPowerLevelFor( + 'redact', room.currentState.getMember(cli.credentials.userId)?.powerLevel ?? 0); // HACK: Intentionally say we can't pin if the user doesn't want to use the functionality if (!SettingsStore.getValue("feature_pinning")) canPin = false; - this.setState({ canRedact, canPin }); + this.setState({ canRedact, canViewRedacted, canPin }); }; private isPinned(): boolean { @@ -152,6 +158,22 @@ export default class MessageContextMenu extends React.Component this.closeMenu(); }; + private onViewRedactedClick = (): void => { + MatrixClientPeg.get().unstableFetchRedactedRoomEventContent( + this.props.mxEvent.getRoomId(), + this.props.mxEvent.getId(), + ).then( + unredactedEvt => { + console.log("Fetched redacted event content:", unredactedEvt); + this.closeMenu(); + this.props.mxEvent.showRedactedContent(unredactedEvt); + }, + err => { + console.error("Failed to fetch redacted event content:", err); + } + ); + }; + private onReportEventClick = (): void => { dis.dispatch({ action: Action.OpenReportEventDialog, @@ -284,6 +306,7 @@ export default class MessageContextMenu extends React.Component let endPollButton: JSX.Element; let resendReactionsButton: JSX.Element; let redactButton: JSX.Element; + let viewRedactedButton: JSX.Element; let forwardButton: JSX.Element; let pinButton: JSX.Element; let unhidePreviewButton: JSX.Element; @@ -306,6 +329,16 @@ export default class MessageContextMenu extends React.Component } } + if (mxEvent.isRedacted() && this.state.canViewRedacted) { + viewRedactedButton = ( + + ) + } + if (isSent && this.state.canRedact) { redactButton = ( { externalURLButton } { unhidePreviewButton } { viewSourceButton } + { viewRedactedButton } { resendReactionsButton } { collapseReplyChain } diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index f886482fbaf..8ab54141b59 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -852,6 +852,11 @@ export class UnwrappedEventTile extends React.Component { private renderE2EPadlock() { const ev = this.props.mxEvent; + if (ev.viewingRedactedContent) { + // Not actually an e2ee thing, but we reuse the same slot in the UI. + return + } + // event could not be decrypted if (ev.getContent().msgtype === 'm.bad.encrypted') { return ; @@ -1513,6 +1518,7 @@ function E2ePadlockUnauthenticated(props) { enum E2ePadlockIcon { Normal = "normal", Warning = "warning", + Redacted = "viewingRedacted", } interface IE2ePadlockProps { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 445c90d6c00..491f677b7e4 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1646,6 +1646,7 @@ "Edit message": "Edit message", "Mod": "Mod", "From a thread": "From a thread", + "This is a deleted message": "This is a deleted message", "This event could not be displayed": "This event could not be displayed", "Your key share request has been sent - please check your other sessions for key share requests.": "Your key share request has been sent - please check your other sessions for key share requests.", "Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.": "Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.", @@ -2867,6 +2868,7 @@ "Resume": "Resume", "Hold": "Hold", "Resend %(unsentCount)s reaction(s)": "Resend %(unsentCount)s reaction(s)", + "View content": "View content", "Open in OpenStreetMap": "Open in OpenStreetMap", "Forward": "Forward", "View source": "View source",