summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--docs/client-server/swagger_matrix/rooms204
-rw-r--r--synapse/rest/room.py46
-rw-r--r--webclient/app.css23
-rw-r--r--webclient/recents/recents-controller.js6
-rw-r--r--webclient/recents/recents.html5
-rw-r--r--webclient/room/room-controller.js5
-rw-r--r--webclient/room/room.html6
-rw-r--r--webclient/settings/settings-controller.js2
8 files changed, 284 insertions, 13 deletions
diff --git a/docs/client-server/swagger_matrix/rooms b/docs/client-server/swagger_matrix/rooms
index 1ead8b8c14..bb49ec5a6a 100644
--- a/docs/client-server/swagger_matrix/rooms
+++ b/docs/client-server/swagger_matrix/rooms
@@ -14,13 +14,103 @@
   },
   "apis": [
     {
+      "path": "/rooms/{roomId}/send/{eventType}/{txnId}",
+      "operations": [
+        {
+          "method": "PUT",
+          "summary": "Send a generic non-state event to this room.",
+          "notes": "This operation can also be done as a POST to /rooms/{roomId}/send/{eventType}",
+          "type": "EventId",
+          "nickname": "send_non_state_event",
+          "consumes": [
+            "application/json"
+          ],
+          "parameters": [
+            {
+              "name": "body",
+              "description": "The event contents",
+              "required": true,
+              "type": "EventContent",
+              "paramType": "body"
+            },
+            {
+              "name": "roomId",
+              "description": "The room to send the message in.",
+              "required": true,
+              "type": "string",
+              "paramType": "path"
+            },
+            {
+              "name": "eventType",
+              "description": "The type of event to send.",
+              "required": true,
+              "type": "string",
+              "paramType": "path"
+            },
+            {
+              "name": "txnId",
+              "description": "A client transaction ID to ensure idempotency. This can only be omitted if the HTTP method becomes a POST.",
+              "required": true,
+              "type": "string",
+              "paramType": "path"
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "path": "/rooms/{roomId}/state/{eventType}/{stateKey}",
+      "operations": [
+        {
+          "method": "PUT",
+          "summary": "Send a generic state event to this room.",
+          "notes": "The state key can be omitted, such that you can PUT to /rooms/{roomId}/state/{eventType}. The state key defaults to a 0 length string in this case.",
+          "type": "void",
+          "nickname": "send_state_event",
+          "consumes": [
+            "application/json"
+          ],
+          "parameters": [
+            {
+              "name": "body",
+              "description": "The event contents",
+              "required": true,
+              "type": "EventContent",
+              "paramType": "body"
+            },
+            {
+              "name": "roomId",
+              "description": "The room to send the message in.",
+              "required": true,
+              "type": "string",
+              "paramType": "path"
+            },
+            {
+              "name": "eventType",
+              "description": "The type of event to send.",
+              "required": true,
+              "type": "string",
+              "paramType": "path"
+            },
+            {
+              "name": "stateKey",
+              "description": "An identifier used to specify clobbering semantics. State events with the same (roomId, eventType, stateKey) will be replaced.",
+              "required": true,
+              "type": "string",
+              "paramType": "path"
+            }
+          ]
+        }
+      ]
+    },
+    {
       "path": "/rooms/{roomId}/send/m.room.message/{txnId}",
       "operations": [
         {
           "method": "PUT",
           "summary": "Send a message in this room.",
-          "notes": "Send a message in this room.",
-          "type": "void",
+          "notes": "This operation can also be done as a POST to /rooms/{roomId}/send/m.room.message",
+          "type": "EventId",
           "nickname": "send_message",
           "consumes": [
             "application/json"
@@ -42,7 +132,7 @@
             },
             {
               "name": "txnId",
-              "description": "A client transaction ID to ensure idempotency.",
+              "description": "A client transaction ID to ensure idempotency. This can only be omitted if the HTTP method becomes a POST.",
               "required": true,
               "type": "string",
               "paramType": "path"
@@ -110,8 +200,8 @@
         {
           "method": "PUT",
           "summary": "Send feedback to a message.",
-          "notes": "Send feedback to a message.",
-          "type": "void",
+          "notes": "This operation can also be done as a POST to /rooms/{roomId}/send/m.room.message.feedback",
+          "type": "EventId",
           "nickname": "send_feedback",
           "consumes": [
             "application/json"
@@ -133,7 +223,7 @@
             },
             {
               "name": "txnId",
-              "description": "A client transaction ID to ensure idempotency.",
+              "description": "A client transaction ID to ensure idempotency. This can only be omitted if the HTTP method becomes a POST.",
               "required": true,
               "type": "string",
               "paramType": "path"
@@ -488,6 +578,51 @@
           ]
         }
       ]
+    },
+    {
+      "path": "/rooms/{roomId}/state",
+      "operations": [
+        {
+          "method": "GET",
+          "summary": "Get a list of all the current state events for this room.",
+          "notes": "Get a list of all the current state events for this room.",
+          "type": "array",
+          "items": {
+            "$ref": "Event"
+          },
+          "nickname": "get_state_events",
+          "parameters": [
+            {
+              "name": "roomId",
+              "description": "The room to get a list of current state events from.",
+              "required": true,
+              "type": "string",
+              "paramType": "path"
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "path": "/rooms/{roomId}/initialSync",
+      "operations": [
+        {
+          "method": "GET",
+          "summary": "Get all the current information for this room, including messages and state events.",
+          "notes": "Get all the current information for this room, including messages and state events.",
+          "type": "InitialSyncRoomData",
+          "nickname": "get_room_sync_data",
+          "parameters": [
+            {
+              "name": "roomId",
+              "description": "The room to get information for.",
+              "required": true,
+              "type": "string",
+              "paramType": "path"
+            }
+          ]
+        }
+      ]
     }
   ],
   "models": {
@@ -633,12 +768,17 @@
       "properties": {
         "event_id": {
           "type": "string",
-          "description": "An ID which uniquely identifies this event.",
+          "description": "An ID which uniquely identifies this event. This is automatically set by the server.",
           "required": true
         },
         "room_id": {
           "type": "string",
-          "description": "The room in which this event occurred.",
+          "description": "The room in which this event occurred. This is automatically set by the server.",
+          "required": true
+        },
+        "type": {
+          "type": "string",
+          "description": "The event type.",
           "required": true
         }
       },
@@ -646,6 +786,26 @@
         "MessageEvent"
       ]
     },
+    "EventId": {
+      "id": "EventId",
+      "properties": {
+        "event_id": {
+          "type": "string",
+          "description": "The allocated event ID for this event.",
+          "required": true
+        }
+      }
+    },
+    "EventContent": {
+      "id": "EventContent",
+      "properties": {
+        "__event_content_keys__": {
+          "type": "string",
+          "description": "Event-specific content keys and values.",
+          "required": false
+        }
+      }
+    },
     "MessageEvent": {
       "id": "MessageEvent",
       "properties": {
@@ -670,6 +830,34 @@
           "description": "The fully-qualified user ID."
         }
       }
+    },
+    "InitialSyncRoomData": {
+      "id": "InitialSyncRoomData",
+      "properties": {
+        "membership": {
+          "type": "string",
+          "description": "This user's membership state in this room.",
+          "required": true
+        },
+        "room_id": {
+          "type": "string",
+          "description": "The ID of this room.",
+          "required": true
+        },
+        "messages": {
+          "type": "MessagePaginationChunk",
+          "description": "The most recent messages for this room, governed by the limit parameter.",
+          "required": false
+        },
+        "state": {
+          "type": "array",
+          "description": "A list of state events representing the current state of the room.",
+          "required": false,
+          "items": {
+            "$ref": "Event"
+          }
+        }
+      }
     }
   }
 }
diff --git a/synapse/rest/room.py b/synapse/rest/room.py
index 66efaa76f0..a10b3b54f9 100644
--- a/synapse/rest/room.py
+++ b/synapse/rest/room.py
@@ -322,6 +322,50 @@ class RoomMessageListRestServlet(RestServlet):
         defer.returnValue((200, msgs))
 
 
+# TODO: Needs unit testing
+class RoomStateRestServlet(RestServlet):
+    PATTERN = client_path_pattern("/rooms/(?P<room_id>[^/]*)/state$")
+
+    @defer.inlineCallbacks
+    def on_GET(self, request, room_id):
+        user = yield self.auth.get_user_by_req(request)
+        # TODO: Get all the current state for this room and return in the same
+        # format as initial sync, that is:
+        # [
+        #   { state event }, { state event }
+        # ]
+        defer.returnValue((200, []))
+
+
+# TODO: Needs unit testing
+class RoomInitialSyncRestServlet(RestServlet):
+    PATTERN = client_path_pattern("/rooms/(?P<room_id>[^/]*)/initialSync$")
+
+    @defer.inlineCallbacks
+    def on_GET(self, request, room_id):
+        user = yield self.auth.get_user_by_req(request)
+        # TODO: Get all the initial sync data for this room and return in the
+        # same format as initial sync, that is:
+        # {
+        #   membership: join,
+        #   messages: [
+        #       chunk: [ msg events ],
+        #       start: s_tok,
+        #       end: e_tok
+        #   ],
+        #   room_id: foo,
+        #   state: [
+        #       { state event } , { state event }
+        #   ]
+        # }
+        # Probably worth keeping the keys room_id and membership for parity with
+        # /initialSync even though they must be joined to sync this and know the
+        # room ID, so clients can reuse the same code (room_id and membership
+        # are MANDATORY for /initialSync, so the code will expect it to be
+        # there)
+        defer.returnValue((200, {}))
+
+
 class RoomTriggerBackfill(RestServlet):
     PATTERN = client_path_pattern("/rooms/(?P<room_id>[^/]*)/backfill$")
 
@@ -436,3 +480,5 @@ def register_servlets(hs, http_server):
     RoomMembershipRestServlet(hs).register(http_server)
     RoomSendEventRestServlet(hs).register(http_server)
     PublicRoomListRestServlet(hs).register(http_server)
+    RoomStateRestServlet(hs).register(http_server)
+    RoomInitialSyncRestServlet(hs).register(http_server)
diff --git a/webclient/app.css b/webclient/app.css
index bc23f76f00..16f9dd72b7 100644
--- a/webclient/app.css
+++ b/webclient/app.css
@@ -7,6 +7,16 @@
     
     .leftBlock {
         width: 8em ! important;
+        font-size: 8px ! important;
+    }
+    
+    .rightBlock {
+        width: 0px ! important;
+        display: none ! important;
+    }
+    
+    .avatar {
+        width: 36px ! important;
     }
     
     #header,
@@ -368,6 +378,10 @@ h1 {
     background-color: #f8f8ff;
 }
 
+.recentsRoomSelected {
+    background-color: #eee;
+}
+
 .recentsRoomName {
     font-size: 16px;
     padding-top: 7px;
@@ -387,6 +401,15 @@ h1 {
     padding-bottom: 5px;
 }
 
+/*** Recents in the room page ***/
+#roomRecentsTableWrapper {
+    float: left;
+    max-width: 320px;
+    margin-right: 20px;
+    height: 100%;
+    overflow-y: auto;
+}
+
 /*** Profile ***/
 
 .profile-avatar {
diff --git a/webclient/recents/recents-controller.js b/webclient/recents/recents-controller.js
index a9805fc38a..8f8b08d5bd 100644
--- a/webclient/recents/recents-controller.js
+++ b/webclient/recents/recents-controller.js
@@ -20,7 +20,11 @@ angular.module('RecentsController', ['matrixService', 'eventHandlerService'])
 .controller('RecentsController', ['$scope', 'matrixService', 'eventHandlerService', 'eventStreamService', 
                                function($scope,  matrixService, eventHandlerService, eventStreamService) {
     $scope.rooms = {};
-    
+
+    // $scope of the parent where the recents component is included can override this value
+    // in order to highlight a specific room in the list
+    $scope.recentsSelectedRoomID;
+
     $scope.$on(eventHandlerService.MEMBER_EVENT, function(ngEvent, event, isLive) {
         var config = matrixService.config();
         if (event.state_key === config.user_id && event.content.membership === "invite") {
diff --git a/webclient/recents/recents.html b/webclient/recents/recents.html
index 6d2864ac97..6fda6c5c6b 100644
--- a/webclient/recents/recents.html
+++ b/webclient/recents/recents.html
@@ -1,6 +1,9 @@
 <div ng-controller="RecentsController" data-ng-init="onInit()">
     <table class="recentsTable">
-        <tbody ng-repeat="(rm_id, room) in rooms | orderRecents" ng-click="goToPage('room/' + (room.room_alias ? room.room_alias : room.room_id) )" class ="recentsRoom" >
+        <tbody ng-repeat="(rm_id, room) in rooms | orderRecents" 
+               ng-click="goToPage('room/' + (room.room_alias ? room.room_alias : room.room_id) )" 
+               class ="recentsRoom" 
+               ng-class="{'recentsRoomSelected': (room.room_id === recentsSelectedRoomID)}">
             <tr>
                 <td class="recentsRoomName">
                     {{ room.room_display_name }}
diff --git a/webclient/room/room-controller.js b/webclient/room/room-controller.js
index f49deaa489..6c98db269e 100644
--- a/webclient/room/room-controller.js
+++ b/webclient/room/room-controller.js
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-angular.module('RoomController', ['ngSanitize', 'mUtilities'])
+angular.module('RoomController', ['ngSanitize', 'mFileInput', 'mUtilities'])
 .controller('RoomController', ['$scope', '$http', '$timeout', '$routeParams', '$location', 'matrixService', 'eventStreamService', 'eventHandlerService', 'mFileUpload', 'mUtilities', '$rootScope',
                                function($scope, $http, $timeout, $routeParams, $location, matrixService, eventStreamService, eventHandlerService, mFileUpload, mUtilities, $rootScope) {
    'use strict';
@@ -327,6 +327,9 @@ angular.module('RoomController', ['ngSanitize', 'mUtilities'])
     var onInit2 = function() {
         eventHandlerService.reInitRoom($scope.room_id); 
 
+        // Make recents highlight the current room
+        $scope.recentsSelectedRoomID = $scope.room_id;
+
         // Join the room
         matrixService.join($scope.room_id).then(
             function() {
diff --git a/webclient/room/room.html b/webclient/room/room.html
index c167819f15..236ca0a89b 100644
--- a/webclient/room/room.html
+++ b/webclient/room/room.html
@@ -7,7 +7,11 @@
     <div id="roomName">
         {{ room_alias || room_id }}
     </div>
-    
+
+    <div id="roomRecentsTableWrapper">
+        <div ng-include="'recents/recents.html'"></div>
+    </div>
+
     <div id="usersTableWrapper">
         <table id="usersTable">
             <tr ng-repeat="member in members | orderMembersList">
diff --git a/webclient/settings/settings-controller.js b/webclient/settings/settings-controller.js
index 5d3f7cb2b8..f7d5e8eb75 100644
--- a/webclient/settings/settings-controller.js
+++ b/webclient/settings/settings-controller.js
@@ -16,7 +16,7 @@ limitations under the License.
 
 'use strict';
 
-angular.module('SettingsController', ['matrixService', 'mFileUpload'])
+angular.module('SettingsController', ['matrixService', 'mFileUpload', 'mFileInput'])
 .controller('SettingsController', ['$scope', 'matrixService', 'mFileUpload',
                               function($scope, matrixService, mFileUpload) {                 
     $scope.config = matrixService.config();