From ad2541299f9bd12c2f3bd52d5a3f42f4dcf71021 Mon Sep 17 00:00:00 2001
From: David Baker <dave@matrix.org>
Date: Tue, 21 Jun 2016 16:47:40 +0100
Subject: [PATCH 1/3] Add ability to delete an alias from room directory

Hidden behind shift-click for now, but we're going to need to do this a lot to moderate the public room list.
---
 src/components/structures/RoomDirectory.js | 57 ++++++++++++++++++----
 1 file changed, 48 insertions(+), 9 deletions(-)

diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js
index cc16e3c6e6..3e3d295986 100644
--- a/src/components/structures/RoomDirectory.js
+++ b/src/components/structures/RoomDirectory.js
@@ -52,6 +52,18 @@ module.exports = React.createClass({
     },
 
     componentDidMount: function() {
+        this.getPublicRooms();
+    },
+
+    componentWillUnmount: function() {
+        // dis.dispatch({
+        //     action: 'ui_opacity',
+        //     sideOpacity: 1.0,
+        //     middleOpacity: 1.0,
+        // });
+    },
+
+    getPublicRooms: function() {
         var self = this;
         MatrixClientPeg.get().publicRooms(function (err, data) {
             if (err) {
@@ -68,20 +80,43 @@ module.exports = React.createClass({
                     publicRooms: data.chunk,
                     loading: false,
                 });
-                self.forceUpdate();
             }
         });
     },
 
-    componentWillUnmount: function() {
-        // dis.dispatch({
-        //     action: 'ui_opacity',
-        //     sideOpacity: 1.0,
-        //     middleOpacity: 1.0,
-        // });
+    deleteAliasClicked: function(roomAlias) {
+        var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
+        var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+        Modal.createDialog(QuestionDialog, {
+            title: "Delete Alias",
+            description: `Are you sure you want to remove the alias '${roomAlias}'?`,
+            onFinished: (should_delete) => {
+                if (should_delete) {
+                    var Loader = sdk.getComponent("elements.Spinner");
+                    var modal = Modal.createDialog(Loader);
+
+                    MatrixClientPeg.get().deleteAlias(roomAlias).done(() => {
+                        modal.close();
+                        this.getPublicRooms();
+                    }, function(err) {
+                        modal.close();
+                        Modal.createDialog(ErrorDialog, {
+                            title: "Failed to delete alias",
+                            description: err.toString()
+                        });
+                    });
+                }
+            }
+        });
     },
 
-    showRoom: function(roomId, roomAlias) {
+    showRoom: function(roomId, roomAlias, ev) {
+        if (ev.shiftKey) {
+            ev.preventDefault();
+            this.deleteAliasClicked(roomAlias);
+            return;
+        }
+
         // extract the metadata from the publicRooms structure to pass
         // as out-of-band data to view_room, because we get information
         // here that we can't get other than by joining the room in some
@@ -175,7 +210,11 @@ module.exports = React.createClass({
             topic = linkifyString(sanitizeHtml(topic));
 
             rows.unshift(
-                <tr key={ rooms[i].room_id } onClick={self.showRoom.bind(null, rooms[i].room_id, alias)}>
+                <tr key={ rooms[i].room_id }
+                    onClick={self.showRoom.bind(null, rooms[i].room_id, alias)}
+                    // cancel onMouseDown otherwise shift-clicking highlights text
+                    onMouseDown={(ev) => {ev.preventDefault();}}
+                >
                     <td className="mx_RoomDirectory_roomAvatar">
                         <BaseAvatar width={24} height={24} resizeMethod='crop'
                             name={ name } idName={ name }

From ff5c7072d712063f9de16dbfb07a8b8aa9f4655c Mon Sep 17 00:00:00 2001
From: David Baker <dave@matrix.org>
Date: Wed, 22 Jun 2016 14:52:55 +0100
Subject: [PATCH 2/3] Mark the room as private (unlisted) too

Also clean up RoomDirectory a bit and just pass the room object around rather than the name / alias, so now we don't have to look up the room by ID again.
---
 src/components/structures/RoomDirectory.js | 123 +++++++++++----------
 1 file changed, 66 insertions(+), 57 deletions(-)

diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js
index 3e3d295986..2b96caec74 100644
--- a/src/components/structures/RoomDirectory.js
+++ b/src/components/structures/RoomDirectory.js
@@ -84,73 +84,77 @@ module.exports = React.createClass({
         });
     },
 
-    deleteAliasClicked: function(roomAlias) {
+    /**
+     * A limited interface for removing rooms from the directory.
+     * Will set the room to not be publicly visible and delete the
+     * default alias. In the long term, it would be better to allow
+     * HS admins to do this through the RoomSettings interface, but
+     * this needs SPEC-417.
+     */
+    removeFromDirectory: function(room) {
+        var alias = get_display_alias_for_room(room);
+        var name = room.name || alias || "Unnamed room";
+
         var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
         var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
-        Modal.createDialog(QuestionDialog, {
-            title: "Delete Alias",
-            description: `Are you sure you want to remove the alias '${roomAlias}'?`,
-            onFinished: (should_delete) => {
-                if (should_delete) {
-                    var Loader = sdk.getComponent("elements.Spinner");
-                    var modal = Modal.createDialog(Loader);
 
-                    MatrixClientPeg.get().deleteAlias(roomAlias).done(() => {
-                        modal.close();
-                        this.getPublicRooms();
-                    }, function(err) {
-                        modal.close();
-                        Modal.createDialog(ErrorDialog, {
-                            title: "Failed to delete alias",
-                            description: err.toString()
-                        });
+        var room_alias = get_display_alias_for_room(room);
+
+        Modal.createDialog(QuestionDialog, {
+            title: "Remove from Directory",
+            description: `Delete the room alias '${room_alias}' and remove '${name}' from the directory?`,
+            onFinished: (should_delete) => {
+                if (!should_delete) return;
+
+                var Loader = sdk.getComponent("elements.Spinner");
+                var modal = Modal.createDialog(Loader);
+                var step = `remove '${name}' from the directory.`;
+
+                MatrixClientPeg.get().setRoomDirectoryVisibility(room.room_id, 'private').then(() => {
+                    step = 'delete the alias.';
+                    MatrixClientPeg.get().deleteAlias(room_alias);
+                }).done(() => {
+                    modal.close();
+                    this.getPublicRooms();
+                }, function(err) {
+                    modal.close();
+                    Modal.createDialog(ErrorDialog, {
+                        title: "Failed to "+step,
+                        description: err.toString()
                     });
-                }
+                });
             }
         });
     },
 
-    showRoom: function(roomId, roomAlias, ev) {
+    showRoom: function(room, ev) {
         if (ev.shiftKey) {
             ev.preventDefault();
-            this.deleteAliasClicked(roomAlias);
+            this.removeFromDirectory(room);
             return;
         }
 
-        // extract the metadata from the publicRooms structure to pass
-        // as out-of-band data to view_room, because we get information
-        // here that we can't get other than by joining the room in some
-        // cases.
-        var room;
-        if (roomId) {
-            for (var i = 0; i < this.state.publicRooms.length; ++i) {
-                if (this.state.publicRooms[i].room_id == roomId) {
-                    room = this.state.publicRooms[i];
-                    break;
-                }
-            }
-        }
         var oob_data = {};
-        if (room) {
-            if (MatrixClientPeg.get().isGuest()) {
-                if (!room.world_readable && !room.guest_can_join) {
-                    var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
-                    Modal.createDialog(NeedToRegisterDialog, {
-                        title: "Failed to join the room",
-                        description: "This room is inaccessible to guests. You may be able to join if you register."
-                    });
-                    return;
-                }
+        if (MatrixClientPeg.get().isGuest()) {
+            if (!room.world_readable && !room.guest_can_join) {
+                var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
+                Modal.createDialog(NeedToRegisterDialog, {
+                    title: "Failed to join the room",
+                    description: "This room is inaccessible to guests. You may be able to join if you register."
+                });
+                return;
             }
-
-            oob_data = {
-                avatarUrl: room.avatar_url,
-                // XXX: This logic is duplicated from the JS SDK which
-                // would normally decide what the name is.
-                name: room.name || room.canonical_alias || (room.aliases ? room.aliases[0] : "Unnamed room"),
-            };
         }
 
+        var room_alias = get_display_alias_for_room(room);
+
+        oob_data = {
+            avatarUrl: room.avatar_url,
+            // XXX: This logic is duplicated from the JS SDK which
+            // would normally decide what the name is.
+            name: room.name || room_alias || "Unnamed room",
+        };
+
         var payload = {
             oob_data: oob_data,
             action: 'view_room',
@@ -159,10 +163,10 @@ module.exports = React.createClass({
         // which servers to start querying. However, there's no other way to join rooms in
         // this list without aliases at present, so if roomAlias isn't set here we have no
         // choice but to supply the ID.
-        if (roomAlias) {
-            payload.room_alias = roomAlias;
+        if (room_alias) {
+            payload.room_alias = room_alias;
         } else {
-            payload.room_id = roomId;
+            payload.room_id = room.room_id;
         }
         dis.dispatch(payload);
     },
@@ -185,8 +189,7 @@ module.exports = React.createClass({
         var self = this;
         var guestRead, guestJoin, perms;
         for (var i = 0; i < rooms.length; i++) {
-            var alias = rooms[i].canonical_alias || (rooms[i].aliases ? rooms[i].aliases[0] : "");
-            var name = rooms[i].name || alias || "Unnamed room";
+            var name = rooms[i].name || get_display_alias_for_room(rooms[i]) || "Unnamed room";
             guestRead = null;
             guestJoin = null;
 
@@ -211,7 +214,7 @@ module.exports = React.createClass({
 
             rows.unshift(
                 <tr key={ rooms[i].room_id }
-                    onClick={self.showRoom.bind(null, rooms[i].room_id, alias)}
+                    onClick={self.showRoom.bind(null, rooms[i])}
                     // cancel onMouseDown otherwise shift-clicking highlights text
                     onMouseDown={(ev) => {ev.preventDefault();}}
                 >
@@ -228,7 +231,7 @@ module.exports = React.createClass({
                         <div className="mx_RoomDirectory_topic"
                              onClick={ function(e) { e.stopPropagation() } }
                              dangerouslySetInnerHTML={{ __html: topic }}/>
-                        <div className="mx_RoomDirectory_alias">{ alias }</div>
+                        <div className="mx_RoomDirectory_alias">{ get_display_alias_for_room(rooms[i]) }</div>
                     </td>
                     <td className="mx_RoomDirectory_roomMemberCount">
                         { rooms[i].num_joined_members }
@@ -276,3 +279,9 @@ module.exports = React.createClass({
         );
     }
 });
+
+// Similar to matrix-react-sdk's MatrixTools.getDisplayAliasForRoom
+// but works with the objects we get from the public room list
+function get_display_alias_for_room(room) {
+    return  room.canonical_alias || (room.aliases ? room.aliases[0] : "");
+}

From c35c9f7c3afe606b18a6082e84db314bbbddbcaa Mon Sep 17 00:00:00 2001
From: David Baker <dave@matrix.org>
Date: Wed, 22 Jun 2016 16:20:06 +0100
Subject: [PATCH 3/3] PR feedback

---
 src/components/structures/RoomDirectory.js | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js
index 2b96caec74..7bcc5397d2 100644
--- a/src/components/structures/RoomDirectory.js
+++ b/src/components/structures/RoomDirectory.js
@@ -98,11 +98,9 @@ module.exports = React.createClass({
         var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
         var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
 
-        var room_alias = get_display_alias_for_room(room);
-
         Modal.createDialog(QuestionDialog, {
             title: "Remove from Directory",
-            description: `Delete the room alias '${room_alias}' and remove '${name}' from the directory?`,
+            description: `Delete the room alias '${alias}' and remove '${name}' from the directory?`,
             onFinished: (should_delete) => {
                 if (!should_delete) return;
 
@@ -112,12 +110,13 @@ module.exports = React.createClass({
 
                 MatrixClientPeg.get().setRoomDirectoryVisibility(room.room_id, 'private').then(() => {
                     step = 'delete the alias.';
-                    MatrixClientPeg.get().deleteAlias(room_alias);
+                    return MatrixClientPeg.get().deleteAlias(alias);
                 }).done(() => {
                     modal.close();
                     this.getPublicRooms();
                 }, function(err) {
                     modal.close();
+                    this.getPublicRooms();
                     Modal.createDialog(ErrorDialog, {
                         title: "Failed to "+step,
                         description: err.toString()