diff --git a/src/TextForEvent.js b/src/TextForEvent.js
index 030c346ccc..05d83d740a 100644
--- a/src/TextForEvent.js
+++ b/src/TextForEvent.js
@@ -366,6 +366,15 @@ function textForCallInviteEvent(event) {
function textForThreePidInviteEvent(event) {
const senderName = event.sender ? event.sender.name : event.getSender();
+
+ if (!event.getContent().display_name) {
+ const targetDisplayName = event.getPrevContent().display_name || _t("Someone");
+ return _t('%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.', {
+ senderName,
+ targetDisplayName,
+ });
+ }
+
return _t('%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.', {
senderName,
targetDisplayName: event.getContent().display_name,
diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js
index 5c745b04cc..74820c804a 100644
--- a/src/components/structures/RightPanel.js
+++ b/src/components/structures/RightPanel.js
@@ -50,6 +50,7 @@ export default class RightPanel extends React.Component {
FilePanel: 'FilePanel',
NotificationPanel: 'NotificationPanel',
RoomMemberInfo: 'RoomMemberInfo',
+ Room3pidMemberInfo: 'Room3pidMemberInfo',
GroupMemberInfo: 'GroupMemberInfo',
});
@@ -155,6 +156,7 @@ export default class RightPanel extends React.Component {
groupRoomId: payload.groupRoomId,
groupId: payload.groupId,
member: payload.member,
+ event: payload.event,
});
}
}
@@ -162,6 +164,7 @@ export default class RightPanel extends React.Component {
render() {
const MemberList = sdk.getComponent('rooms.MemberList');
const MemberInfo = sdk.getComponent('rooms.MemberInfo');
+ const ThirdPartyMemberInfo = sdk.getComponent('rooms.ThirdPartyMemberInfo');
const NotificationPanel = sdk.getComponent('structures.NotificationPanel');
const FilePanel = sdk.getComponent('structures.FilePanel');
@@ -180,6 +183,8 @@ export default class RightPanel extends React.Component {
panel = ;
} else if (this.state.phase === RightPanel.Phase.RoomMemberInfo) {
panel = ;
+ } else if (this.state.phase === RightPanel.Phase.Room3pidMemberInfo) {
+ panel = ;
} else if (this.state.phase === RightPanel.Phase.GroupMemberInfo) {
panel = {
if (query) {
@@ -408,6 +415,7 @@ module.exports = React.createClass({
return this._onPending3pidInviteClick(e)}
/>;
}));
}
diff --git a/src/components/views/rooms/ThirdPartyMemberInfo.js b/src/components/views/rooms/ThirdPartyMemberInfo.js
new file mode 100644
index 0000000000..3fe8382251
--- /dev/null
+++ b/src/components/views/rooms/ThirdPartyMemberInfo.js
@@ -0,0 +1,142 @@
+/*
+Copyright 2019 New Vector Ltd.
+
+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 PropTypes from 'prop-types';
+import MatrixClientPeg from "../../../MatrixClientPeg";
+import {MatrixEvent} from "matrix-js-sdk";
+import {_t} from "../../../languageHandler";
+import dis from "../../../dispatcher";
+import sdk from "../../../index";
+import Modal from "../../../Modal";
+
+export default class ThirdPartyMemberInfo extends React.Component {
+ static propTypes = {
+ event: PropTypes.instanceOf(MatrixEvent).isRequired,
+ };
+
+ constructor(props) {
+ super(props);
+
+ const room = MatrixClientPeg.get().getRoom(this.props.event.getRoomId());
+ const me = room.getMember(MatrixClientPeg.get().getUserId());
+ const powerLevels = room.currentState.getStateEvents("m.room.power_levels", "");
+
+ let kickLevel = powerLevels ? powerLevels.getContent().kick : 50;
+ if (typeof(kickLevel) !== 'number') kickLevel = 50;
+
+ const sender = room.getMember(this.props.event.getSender());
+
+ this.state = {
+ stateKey: this.props.event.getStateKey(),
+ roomId: this.props.event.getRoomId(),
+ displayName: this.props.event.getContent().display_name,
+ invited: true,
+ canKick: me ? me.powerLevel > kickLevel : false,
+ senderName: sender ? sender.name : this.props.event.getSender(),
+ };
+ }
+
+ componentWillMount(): void {
+ MatrixClientPeg.get().on("RoomState.events", this.onRoomStateEvents);
+ }
+
+ componentWillUnmount(): void {
+ const client = MatrixClientPeg.get();
+ if (client) {
+ client.removeListener("RoomState.events", this.onRoomStateEvents);
+ }
+ }
+
+ onRoomStateEvents = (ev) => {
+ if (ev.getType() === "m.room.third_party_invite" && ev.getStateKey() === this.state.stateKey) {
+ const newDisplayName = ev.getContent().display_name;
+ const isInvited = !!newDisplayName; // display_name indicates a valid invite
+
+ const newState = {invited: isInvited};
+ if (newDisplayName) newState['displayName'] = newDisplayName;
+ this.setState(newState);
+ }
+ };
+
+ onCancel = () => {
+ dis.dispatch({
+ action: "view_3pid_invite",
+ event: null,
+ });
+ };
+
+ onKickClick = () => {
+ MatrixClientPeg.get().sendStateEvent(this.state.roomId, "m.room.third_party_invite", {}, this.state.stateKey)
+ .catch((err) => {
+ console.error(err);
+
+ // Revert echo because of error
+ this.setState({invited: true});
+
+ const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+ Modal.createTrackedDialog('Revoke 3pid invite failed', '', ErrorDialog, {
+ title: _t("Failed to revoke invite"),
+ description: _t(
+ "Could not revoke the invite. The server may be experiencing a temporary problem or " +
+ "you do not have sufficient permissions to revoke the invite.",
+ ),
+ });
+ });
+
+ // Local echo
+ this.setState({invited: false});
+ };
+
+ render() {
+ const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
+
+ let adminTools = null;
+ if (this.state.canKick && this.state.invited) {
+ adminTools = (
+
+
{_t("Admin Tools")}
+
+
+ {_t("Revoke invite")}
+
+
+
+ );
+ }
+
+ // We shamelessly rip off the MemberInfo styles here.
+ return (
+
+
+
+
{this.state.displayName}
+
+
+
+
+ {_t("Invited by %(sender)s", {sender: this.state.senderName})}
+
+
+
+ {adminTools}
+
+ );
+ }
+}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 94d524d767..0f696e2893 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -223,6 +223,7 @@
"(unknown failure: %(reason)s)": "(unknown failure: %(reason)s)",
"%(senderName)s ended the call.": "%(senderName)s ended the call.",
"%(senderName)s placed a %(callType)s call.": "%(senderName)s placed a %(callType)s call.",
+ "%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.": "%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.",
"%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.",
"%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s made future room history visible to all room members, from the point they are invited.",
"%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s made future room history visible to all room members, from the point they joined.",
@@ -823,6 +824,10 @@
"Stickerpack": "Stickerpack",
"Hide Stickers": "Hide Stickers",
"Show Stickers": "Show Stickers",
+ "Failed to revoke invite": "Failed to revoke invite",
+ "Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.": "Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.",
+ "Revoke invite": "Revoke invite",
+ "Invited by %(sender)s": "Invited by %(sender)s",
"Jump to first unread message.": "Jump to first unread message.",
"Error updating main address": "Error updating main address",
"There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.": "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.",