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 @@ -13,14 +13,104 @@ "token": [] }, "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[^/]*)/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[^/]*)/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[^/]*)/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 @@
- +
{{ 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 @@
{{ room_alias || room_id }}
- + +
+
+
+
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();