From de4773ba935bfc06391fb819e39680a41de9d31b Mon Sep 17 00:00:00 2001
From: David Baker <dave@matrix.org>
Date: Fri, 17 Feb 2017 15:50:30 +0000
Subject: [PATCH 1/5] Show when you've been kicked or banned

Update the room state when you've been kicked or banned, and show
a message in the preview bar, including the reason.
---
 src/components/structures/RoomView.js        | 11 +++++
 src/components/views/rooms/RoomPreviewBar.js | 50 +++++++++++++++++---
 2 files changed, 55 insertions(+), 6 deletions(-)

diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js
index 662d322fb4..5bf192dfc6 100644
--- a/src/components/structures/RoomView.js
+++ b/src/components/structures/RoomView.js
@@ -159,6 +159,7 @@ module.exports = React.createClass({
         MatrixClientPeg.get().on("Room.name", this.onRoomName);
         MatrixClientPeg.get().on("Room.accountData", this.onRoomAccountData);
         MatrixClientPeg.get().on("RoomState.members", this.onRoomStateMember);
+        MatrixClientPeg.get().on("RoomMember.membership", this.onRoomMemberMembership);
         MatrixClientPeg.get().on("accountData", this.onAccountData);
 
         this.tabComplete = new TabComplete({
@@ -347,6 +348,7 @@ module.exports = React.createClass({
             MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
             MatrixClientPeg.get().removeListener("Room.accountData", this.onRoomAccountData);
             MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
+            MatrixClientPeg.get().removeListener("RoomMember.membership", this.onRoomMemberMembership);
             MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
         }
 
@@ -612,6 +614,12 @@ module.exports = React.createClass({
         this._updateRoomMembers();
     },
 
+    onRoomMemberMembership: function(ev, member, oldMembership) {
+        if (member.userId == MatrixClientPeg.get().credentials.userId) {
+            this.forceUpdate();
+        }
+    },
+
     // rate limited because a power level change will emit an event for every
     // member in the room.
     _updateRoomMembers: new rate_limited_func(function() {
@@ -1456,6 +1464,7 @@ module.exports = React.createClass({
                             />
                             <div className="mx_RoomView_auxPanel">
                                 <RoomPreviewBar onJoinClick={ this.onJoinButtonClicked }
+                                                onForgetClick={ this.onForgetClick }
                                                 onRejectClick={ this.onRejectThreepidInviteButtonClicked }
                                                 canPreview={ false } error={ this.state.roomLoadError }
                                                 roomAlias={room_alias}
@@ -1498,6 +1507,7 @@ module.exports = React.createClass({
                         />
                         <div className="mx_RoomView_auxPanel">
                             <RoomPreviewBar onJoinClick={ this.onJoinButtonClicked }
+                                            onForgetClick={ this.onForgetClick }
                                             onRejectClick={ this.onRejectButtonClicked }
                                             inviterName={ inviterName }
                                             canPreview={ false }
@@ -1573,6 +1583,7 @@ module.exports = React.createClass({
             }
             aux = (
                 <RoomPreviewBar onJoinClick={this.onJoinButtonClicked}
+                                onForgetClick={ this.onForgetClick }
                                 onRejectClick={this.onRejectThreepidInviteButtonClicked}
                                 spinner={this.state.joining}
                                 inviterName={inviterName}
diff --git a/src/components/views/rooms/RoomPreviewBar.js b/src/components/views/rooms/RoomPreviewBar.js
index 51ae0fe965..61515e07ca 100644
--- a/src/components/views/rooms/RoomPreviewBar.js
+++ b/src/components/views/rooms/RoomPreviewBar.js
@@ -1,5 +1,6 @@
 /*
 Copyright 2015, 2016 OpenMarket Ltd
+Copyright 2017 Vector Creations Ltd
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -79,6 +80,12 @@ module.exports = React.createClass({
         }
     },
 
+    _roomNameElement: function(fallback) {
+        fallback = fallback || 'a room';
+        const name = this.props.room ? this.props.room.name : (this.props.room_alias || "");
+        return name ? <b>{ name }</b> : fallback;
+    },
+
     render: function() {
         var joinBlock, previewBlock;
 
@@ -89,6 +96,16 @@ module.exports = React.createClass({
             </div>);
         }
 
+        const myMember = this.props.room ? this.props.room.currentState.members[
+            MatrixClientPeg.get().credentials.userId
+        ] : null;
+        const kicked = (
+            myMember &&
+            myMember.membership == 'leave' &&
+            myMember.events.member.getSender() != MatrixClientPeg.get().credentials.userId
+        );
+        const banned = myMember && myMember.membership == 'ban';
+
         if (this.props.inviterName) {
             var emailMatchBlock;
             if (this.props.invitedEmail) {
@@ -122,8 +139,31 @@ module.exports = React.createClass({
                 </div>
             );
 
-        }
-        else if (this.props.error) {
+        } else if (kicked || banned) {
+            const verb = kicked ? 'kicked' : 'banned';
+            const roomName = this._roomNameElement('this room');
+            const kicker = MatrixClientPeg.get().getUser(
+                myMember.events.member.getSender()
+            );
+            let reason;
+            if (myMember.events.member.getContent().reason) {
+                reason = <div>Reason: {myMember.events.member.getContent().reason}</div>
+            }
+            let rejoinBlock;
+            if (!banned) {
+                rejoinBlock = <a onClick={ this.props.onJoinClick }><b>Rejoin</b></a>;
+            }
+            joinBlock = (
+                <div>
+                    <div className="mx_RoomPreviewBar_join_text">
+                        You have been {verb} from {roomName} by {kicker.displayName}.<br />
+                        {reason}
+                        <a onClick={ this.props.onForgetClick }><b>Forget</b></a><br />
+                        {rejoinBlock}
+                    </div>
+                </div>
+            );
+        } else if (this.props.error) {
             var name = this.props.roomAlias || "This room";
             var error;
             if (this.props.error.errcode == 'M_NOT_FOUND') {
@@ -138,10 +178,8 @@ module.exports = React.createClass({
                     </div>
                 </div>
             );
-        }
-        else {
-            var name = this.props.room ? this.props.room.name : (this.props.room_alias || "");
-            name = name ? <b>{ name }</b> : "a room";
+        } else {
+            const name = this._roomNameElement();
             joinBlock = (
                 <div>
                     <div className="mx_RoomPreviewBar_join_text">

From 9f9de46b10cefa674f4677eb9e239f47acac42ac Mon Sep 17 00:00:00 2001
From: David Baker <dave@matrix.org>
Date: Fri, 17 Feb 2017 16:09:25 +0000
Subject: [PATCH 2/5] Add onForgetClick proptype

---
 src/components/views/rooms/RoomPreviewBar.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/components/views/rooms/RoomPreviewBar.js b/src/components/views/rooms/RoomPreviewBar.js
index 61515e07ca..b6d3cf2f50 100644
--- a/src/components/views/rooms/RoomPreviewBar.js
+++ b/src/components/views/rooms/RoomPreviewBar.js
@@ -27,6 +27,7 @@ module.exports = React.createClass({
     propTypes: {
         onJoinClick: React.PropTypes.func,
         onRejectClick: React.PropTypes.func,
+        onForgetClick: React.PropTypes.func,
 
         // if inviterName is specified, the preview bar will shown an invite to the room.
         // You should also specify onRejectClick if specifiying inviterName

From a3aea6ba2d75bc4e3d8468036fc25fdc36320ea4 Mon Sep 17 00:00:00 2001
From: David Baker <dave@matrix.org>
Date: Fri, 17 Feb 2017 16:11:20 +0000
Subject: [PATCH 3/5] Swap rejoin / forget

---
 src/components/views/rooms/RoomPreviewBar.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/components/views/rooms/RoomPreviewBar.js b/src/components/views/rooms/RoomPreviewBar.js
index b6d3cf2f50..4eaa1cea71 100644
--- a/src/components/views/rooms/RoomPreviewBar.js
+++ b/src/components/views/rooms/RoomPreviewBar.js
@@ -152,15 +152,15 @@ module.exports = React.createClass({
             }
             let rejoinBlock;
             if (!banned) {
-                rejoinBlock = <a onClick={ this.props.onJoinClick }><b>Rejoin</b></a>;
+                rejoinBlock = <div><a onClick={ this.props.onJoinClick }><b>Rejoin</b></a></div>;
             }
             joinBlock = (
                 <div>
                     <div className="mx_RoomPreviewBar_join_text">
                         You have been {verb} from {roomName} by {kicker.displayName}.<br />
                         {reason}
-                        <a onClick={ this.props.onForgetClick }><b>Forget</b></a><br />
                         {rejoinBlock}
+                        <a onClick={ this.props.onForgetClick }><b>Forget</b></a>
                     </div>
                 </div>
             );

From 1f5fdf794583d7474d8bafa0ec1e90b0288cafff Mon Sep 17 00:00:00 2001
From: David Baker <dave@matrix.org>
Date: Fri, 17 Feb 2017 16:14:51 +0000
Subject: [PATCH 4/5] Get a member object, not the user object

---
 src/components/views/rooms/RoomPreviewBar.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/components/views/rooms/RoomPreviewBar.js b/src/components/views/rooms/RoomPreviewBar.js
index 4eaa1cea71..3476c47f09 100644
--- a/src/components/views/rooms/RoomPreviewBar.js
+++ b/src/components/views/rooms/RoomPreviewBar.js
@@ -143,7 +143,7 @@ module.exports = React.createClass({
         } else if (kicked || banned) {
             const verb = kicked ? 'kicked' : 'banned';
             const roomName = this._roomNameElement('this room');
-            const kicker = MatrixClientPeg.get().getUser(
+            const kicker = this.props.room.currentState.getMember(
                 myMember.events.member.getSender()
             );
             let reason;
@@ -157,7 +157,7 @@ module.exports = React.createClass({
             joinBlock = (
                 <div>
                     <div className="mx_RoomPreviewBar_join_text">
-                        You have been {verb} from {roomName} by {kicker.displayName}.<br />
+                        You have been {verb} from {roomName} by {kicker.name}.<br />
                         {reason}
                         {rejoinBlock}
                         <a onClick={ this.props.onForgetClick }><b>Forget</b></a>

From b18473ccb28b7da707580b6ae43f8e38be7aa540 Mon Sep 17 00:00:00 2001
From: David Baker <dave@matrix.org>
Date: Fri, 17 Feb 2017 16:35:18 +0000
Subject: [PATCH 5/5] Handle there being no member event when banned

Here, and also in MemberEventListSummary where this also broke.
---
 src/components/views/elements/MemberEventListSummary.js | 2 +-
 src/components/views/rooms/RoomPreviewBar.js            | 6 ++++--
 2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/src/components/views/elements/MemberEventListSummary.js b/src/components/views/elements/MemberEventListSummary.js
index 1a73b5a50e..510d861730 100644
--- a/src/components/views/elements/MemberEventListSummary.js
+++ b/src/components/views/elements/MemberEventListSummary.js
@@ -381,7 +381,7 @@ module.exports = React.createClass({
             // Initialise a user's events
             if (!userEvents[userId]) {
                 userEvents[userId] = [];
-                avatarMembers.push(e.target);
+                if (e.target) avatarMembers.push(e.target);
             }
             userEvents[userId].push({
                 mxEvent: e,
diff --git a/src/components/views/rooms/RoomPreviewBar.js b/src/components/views/rooms/RoomPreviewBar.js
index 3476c47f09..43c3b05295 100644
--- a/src/components/views/rooms/RoomPreviewBar.js
+++ b/src/components/views/rooms/RoomPreviewBar.js
@@ -143,9 +143,11 @@ module.exports = React.createClass({
         } else if (kicked || banned) {
             const verb = kicked ? 'kicked' : 'banned';
             const roomName = this._roomNameElement('this room');
-            const kicker = this.props.room.currentState.getMember(
+            const kickerMember = this.props.room.currentState.getMember(
                 myMember.events.member.getSender()
             );
+            const kickerName = kickerMember ?
+                kickerMember.name : myMember.events.member.getSender();
             let reason;
             if (myMember.events.member.getContent().reason) {
                 reason = <div>Reason: {myMember.events.member.getContent().reason}</div>
@@ -157,7 +159,7 @@ module.exports = React.createClass({
             joinBlock = (
                 <div>
                     <div className="mx_RoomPreviewBar_join_text">
-                        You have been {verb} from {roomName} by {kicker.name}.<br />
+                        You have been {verb} from {roomName} by {kickerName}.<br />
                         {reason}
                         {rejoinBlock}
                         <a onClick={ this.props.onForgetClick }><b>Forget</b></a>