diff options
-rw-r--r-- | webclient/components/matrix/event-handler-service.js | 98 | ||||
-rw-r--r-- | webclient/components/matrix/event-stream-service.js | 13 | ||||
-rw-r--r-- | webclient/recents/recents-controller.js | 60 | ||||
-rw-r--r-- | webclient/recents/recents.html | 4 | ||||
-rw-r--r-- | webclient/room/room-controller.js | 46 | ||||
-rw-r--r-- | webclient/room/room.html | 5 |
6 files changed, 139 insertions, 87 deletions
diff --git a/webclient/components/matrix/event-handler-service.js b/webclient/components/matrix/event-handler-service.js index 94ac91db5e..277faa6f77 100644 --- a/webclient/components/matrix/event-handler-service.js +++ b/webclient/components/matrix/event-handler-service.js @@ -55,6 +55,11 @@ angular.module('eventHandlerService', []) $rootScope.events.rooms[room_id] = {}; $rootScope.events.rooms[room_id].messages = []; $rootScope.events.rooms[room_id].members = {}; + + // Pagination information + $rootScope.events.rooms[room_id].pagination = { + earliest_token: "END" // how far back we've paginated + } } }; @@ -81,8 +86,15 @@ angular.module('eventHandlerService', []) if (isLiveEvent) { if (event.user_id === matrixService.config().user_id && (event.content.msgtype === "m.text" || event.content.msgtype === "m.emote") ) { - // assume we've already echoed it - // FIXME: track events by ID and ungrey the right message to show it's been delivered + // Assume we've already echoed it. So, there is a fake event in the messages list of the room + // Replace this fake event by the true one + var index = getRoomEventIndex(event.room_id, event.event_id); + if (index) { + $rootScope.events.rooms[event.room_id].messages[index] = event; + } + else { + $rootScope.events.rooms[event.room_id].messages.push(event); + } } else { $rootScope.events.rooms[event.room_id].messages.push(event); @@ -100,7 +112,7 @@ angular.module('eventHandlerService', []) $rootScope.$broadcast(MSG_EVENT, event, isLiveEvent); }; - var handleRoomMember = function(event, isLiveEvent) { + var handleRoomMember = function(event, isLiveEvent, isStateEvent) { initRoom(event.room_id); // if the server is stupidly re-relaying a no-op join, discard it. @@ -112,7 +124,10 @@ angular.module('eventHandlerService', []) } // add membership changes as if they were a room message if something interesting changed - if (event.content.prev !== event.content.membership) { + // Exception: Do not do this if the event is a room state event because such events already come + // as room messages events. Moreover, when they come as room messages events, they are relatively ordered + // with other other room messages + if (event.content.prev !== event.content.membership && !isStateEvent) { if (isLiveEvent) { $rootScope.events.rooms[event.room_id].messages.push(event); } @@ -121,8 +136,13 @@ angular.module('eventHandlerService', []) } } - $rootScope.events.rooms[event.room_id].members[event.state_key] = event; - $rootScope.$broadcast(MEMBER_EVENT, event, isLiveEvent); + // Use data from state event or the latest data from the stream. + // Do not care of events that come when paginating back + if (isStateEvent || isLiveEvent) { + $rootScope.events.rooms[event.room_id].members[event.state_key] = event; + } + + $rootScope.$broadcast(MEMBER_EVENT, event, isLiveEvent, isStateEvent); }; var handlePresence = function(event, isLiveEvent) { @@ -177,6 +197,29 @@ angular.module('eventHandlerService', []) } }; + /** + * Get the index of the event in $rootScope.events.rooms[room_id].messages + * @param {type} room_id the room id + * @param {type} event_id the event id to look for + * @returns {Number | undefined} the index. undefined if not found. + */ + var getRoomEventIndex = function(room_id, event_id) { + var index; + + var room = $rootScope.events.rooms[room_id]; + if (room) { + for (var i = 0; i < room.messages.length; i++) { + var message = room.messages[i]; + console.log(message.event_id); + if (event_id === message.event_id) { + index = i; + break; + } + } + } + return index; + } + return { ROOM_CREATE_EVENT: ROOM_CREATE_EVENT, MSG_EVENT: MSG_EVENT, @@ -186,18 +229,24 @@ angular.module('eventHandlerService', []) CALL_EVENT: CALL_EVENT, NAME_EVENT: NAME_EVENT, - handleEvent: function(event, isLiveEvent) { - // FIXME: event duplication suppression is all broken as the code currently expect to handles - // events multiple times to get their side-effects... -/* - if (eventMap[event.event_id]) { - console.log("discarding duplicate event: " + JSON.stringify(event)); - return; - } - else { - eventMap[event.event_id] = 1; + handleEvent: function(event, isLiveEvent, isStateEvent) { + // Avoid duplicated events + // Needed for rooms where initialSync has not been done. + // In this case, we do not know where to start pagination. So, it starts from the END + // and we can have the same event (ex: joined, invitation) coming from the pagination + // AND from the event stream. + // FIXME: This workaround should be no more required when /initialSync on a particular room + // will be available (as opposite to the global /initialSync done at startup) + if (!isStateEvent) { // Do not consider state events + if (event.event_id && eventMap[event.event_id]) { + console.log("discarding duplicate event: " + JSON.stringify(event, undefined, 4)); + return; + } + else { + eventMap[event.event_id] = 1; + } } -*/ + if (event.type.indexOf('m.call.') === 0) { handleCallEvent(event, isLiveEvent); } @@ -213,7 +262,7 @@ angular.module('eventHandlerService', []) handleMessage(event, isLiveEvent); break; case "m.room.member": - handleRoomMember(event, isLiveEvent); + handleRoomMember(event, isLiveEvent, isStateEvent); break; case "m.presence": handlePresence(event, isLiveEvent); @@ -241,12 +290,21 @@ angular.module('eventHandlerService', []) // isLiveEvents determines whether notifications should be shown, whether // messages get appended to the start/end of lists, etc. - handleEvents: function(events, isLiveEvents) { + handleEvents: function(events, isLiveEvents, isStateEvents) { for (var i=0; i<events.length; i++) { - this.handleEvent(events[i], isLiveEvents); + this.handleEvent(events[i], isLiveEvents, isStateEvents); } }, + // Handle messages from /initialSync or /messages + handleRoomMessages: function(room_id, messages, isLiveEvents) { + this.handleEvents(messages.chunk, isLiveEvents); + + // Store how far back we've paginated + // This assumes the paginations requests are contiguous and in reverse chronological order + $rootScope.events.rooms[room_id].pagination.earliest_token = messages.end; + }, + handleInitialSyncDone: function(initialSyncData) { console.log("# handleInitialSyncDone"); initialSyncDeferred.resolve(initialSyncData); diff --git a/webclient/components/matrix/event-stream-service.js b/webclient/components/matrix/event-stream-service.js index 1bc850a8fa..03b805213d 100644 --- a/webclient/components/matrix/event-stream-service.js +++ b/webclient/components/matrix/event-stream-service.js @@ -104,17 +104,19 @@ angular.module('eventStreamService', []) settings.isActive = true; var deferred = $q.defer(); - // FIXME: We are discarding all the messages. - // XXX FIXME TODO : The discard works because we are doing this all over - // again on EVERY INSTANTIATION of the recents controller. + // Initial sync: get all information and the last message of all rooms of the user matrixService.initialSync(1, false).then( function(response) { var rooms = response.data.rooms; for (var i = 0; i < rooms.length; ++i) { var room = rooms[i]; - // console.log("got room: " + room.room_id); + + if ("messages" in room) { + eventHandlerService.handleRoomMessages(room.room_id, room.messages, false); + } + if ("state" in room) { - eventHandlerService.handleEvents(room.state, false); + eventHandlerService.handleEvents(room.state, false, true); } } @@ -124,6 +126,7 @@ angular.module('eventStreamService', []) // Initial sync is done eventHandlerService.handleInitialSyncDone(response); + // Start event streaming from that point settings.from = response.data.end; doEventStream(deferred); }, diff --git a/webclient/recents/recents-controller.js b/webclient/recents/recents-controller.js index 0553eb9be0..5cf74cad4e 100644 --- a/webclient/recents/recents-controller.js +++ b/webclient/recents/recents-controller.js @@ -16,12 +16,6 @@ 'use strict'; -// XXX FIXME TODO -// We should NOT be dumping things into $rootScope!!!! We should NOT be -// making any requests here, and should READ what is already in the -// rootScope from the event handler service!!! -// XXX FIXME TODO - angular.module('RecentsController', ['matrixService', 'matrixFilter', 'eventHandlerService']) .controller('RecentsController', ['$rootScope', '$scope', 'matrixService', 'eventHandlerService', function($rootScope, $scope, matrixService, eventHandlerService) { @@ -33,11 +27,6 @@ angular.module('RecentsController', ['matrixService', 'matrixFilter', 'eventHand // $rootScope of the parent where the recents component is included can override this value // in order to highlight a specific room in the list $rootScope.recentsSelectedRoomID; - - // XXX FIXME TODO : We should NOT be doing this here, which could be - // repeated for every controller instance. We should be doing this in - // event handler service instead. In additon, this will break if there - // isn't a recents controller visible when the last message comes in :/ var listenToEventStream = function() { // Refresh the list on matrix invitation and message event @@ -59,6 +48,9 @@ angular.module('RecentsController', ['matrixService', 'matrixFilter', 'eventHand if ($rootScope.rooms[event.room_id]) { $rootScope.rooms[event.room_id].lastMsg = event; } + + // Update room users count + $rootScope.rooms[event.room_id].numUsersInRoom = getUsersCountInRoom(event.room_id); } }); $rootScope.$on(eventHandlerService.MSG_EVENT, function(ngEvent, event, isLive) { @@ -78,6 +70,29 @@ angular.module('RecentsController', ['matrixService', 'matrixFilter', 'eventHand }); }; + /** + * Compute the room users number, ie the number of members who has joined the room. + * @param {String} room_id the room id + * @returns {undefined | Number} the room users number if available + */ + var getUsersCountInRoom = function(room_id) { + var memberCount; + + var room = $rootScope.events.rooms[room_id]; + if (room) { + memberCount = 0; + + for (var i in room.members) { + var member = room.members[i]; + + if ("join" === member.membership) { + memberCount = memberCount + 1; + } + } + } + + return memberCount; + } $scope.onInit = function() { // Init recents list only once @@ -85,23 +100,12 @@ angular.module('RecentsController', ['matrixService', 'matrixFilter', 'eventHand return; } - // XXX FIXME TODO - // We should NOT be dumping things into $rootScope!!!! We should NOT be - // making any requests here, and should READ what is already in the - // rootScope from the event handler service!!! - // XXX FIXME TODO - $rootScope.rooms = {}; // Use initialSync data to init the recents list eventHandlerService.waitForInitialSyncCompletion().then( function(initialSyncData) { - // XXX FIXME TODO: - // Any assignments to the rootScope here should be done in - // event handler service and not here, because we could have - // many controllers manipulating and clobbering each other, and - // are unecessarily repeating http requests. var rooms = initialSyncData.data.rooms; for (var i=0; i<rooms.length; i++) { var room = rooms[i]; @@ -114,17 +118,7 @@ angular.module('RecentsController', ['matrixService', 'matrixFilter', 'eventHand $rootScope.rooms[room.room_id].lastMsg = room.messages.chunk[0]; } - - var numUsersInRoom = 0; - if (room.state) { - for (var j=0; j<room.state.length; j++) { - var stateEvent = room.state[j]; - if (stateEvent.type == "m.room.member" && stateEvent.content.membership == "join") { - numUsersInRoom += 1; - } - } - } - $rootScope.rooms[room.room_id].numUsersInRoom = numUsersInRoom; + $rootScope.rooms[room.room_id].numUsersInRoom = getUsersCountInRoom(room.room_id); } // From now, update recents from the stream diff --git a/webclient/recents/recents.html b/webclient/recents/recents.html index 4726b61cb3..d6bea52cbe 100644 --- a/webclient/recents/recents.html +++ b/webclient/recents/recents.html @@ -9,7 +9,9 @@ {{ room.room_id | mRoomName }} </td> <td class="recentsRoomSummaryTS"> - {{ room.numUsersInRoom || '1' }} {{ room.numUsersInRoom == 1 ? 'user' : 'users' }} + <span ng-show="undefined !== room.numUsersInRoom"> + {{ room.numUsersInRoom || '1' }} {{ room.numUsersInRoom == 1 ? 'user' : 'users' }} + </span> </td> <td class="recentsRoomSummaryTS"> {{ (room.lastMsg.ts) | date:'MMM d HH:mm' }} diff --git a/webclient/room/room-controller.js b/webclient/room/room-controller.js index 3d75ef5499..0000fcfc61 100644 --- a/webclient/room/room-controller.js +++ b/webclient/room/room-controller.js @@ -27,8 +27,6 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) $scope.state = { user_id: matrixService.config().user_id, - events_from: "END", // when to start the event stream from. - earliest_token: "END", // stores how far back we've paginated. first_pagination: true, // this is toggled off when the first pagination is done can_paginate: false, // this is toggled off when we are not ready yet to paginate or when we run out of items paginating: false, // used to avoid concurrent pagination requests pulling in dup contents @@ -159,12 +157,15 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) else { $scope.state.paginating = true; } - // console.log("paginateBackMessages from " + $scope.state.earliest_token + " for " + numItems); + + console.log("paginateBackMessages from " + $rootScope.events.rooms[$scope.room_id].pagination.earliest_token + " for " + numItems); var originalTopRow = $("#messageTable>tbody>tr:first")[0]; - matrixService.paginateBackMessages($scope.room_id, $scope.state.earliest_token, numItems).then( + + // Paginate events from the point in cache + matrixService.paginateBackMessages($scope.room_id, $rootScope.events.rooms[$scope.room_id].pagination.earliest_token, numItems).then( function(response) { - eventHandlerService.handleEvents(response.data.chunk, false); - $scope.state.earliest_token = response.data.end; + + eventHandlerService.handleRoomMessages($scope.room_id, response.data, false); if (response.data.chunk.length < MESSAGES_PER_PAGINATION) { // no more messages to paginate. this currently never gets turned true again, as we never // expire paginated contents in the current implementation. @@ -459,7 +460,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) var powerLevel = 50; // default power level for op if (matches) { var user_id = matches[1]; - if (matches.length === 4) { + if (matches.length === 4 && undefined !== matches[3]) { powerLevel = parseInt(matches[3]); } if (powerLevel !== NaN) { @@ -512,8 +513,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) room_id: $scope.room_id, type: "m.room.message", user_id: $scope.state.user_id, - // FIXME: re-enable echo_msg_state when we have a nice way to turn the field off again - // echo_msg_state: "messagePending" // Add custom field to indicate the state of this fake message to HTML + echo_msg_state: "messagePending" // Add custom field to indicate the state of this fake message to HTML }; $scope.textInput = ""; @@ -522,26 +522,21 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) } if (promise) { + // Reset previous feedback + $scope.feedback = ""; + promise.then( - function() { + function(response) { console.log("Request successfully sent"); - if (!echo) { - $scope.textInput = ""; - } -/* - if (echoMessage) { - // Remove the fake echo message from the room messages - // It will be replaced by the one acknowledged by the server - // ...except this causes a nasty flicker. So don't swap messages for now. --matthew - // var index = $rootScope.events.rooms[$scope.room_id].messages.indexOf(echoMessage); - // if (index > -1) { - // $rootScope.events.rooms[$scope.room_id].messages.splice(index, 1); - // } + if (echo) { + // Mark this fake message event with its allocated event_id + // When the true message event will come from the events stream (in handleMessage), + // we will be able to replace the fake one by the true one + echoMessage.event_id = response.data.event_id; } else { $scope.textInput = ""; - } -*/ + } }, function(error) { $scope.feedback = "Request failed: " + error.data.error; @@ -659,9 +654,6 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) var onInit3 = function() { console.log("onInit3"); - - // TODO: We should be able to keep them - eventHandlerService.resetRoomMessages($scope.room_id); // Make recents highlight the current room $scope.recentsSelectedRoomID = $scope.room_id; diff --git a/webclient/room/room.html b/webclient/room/room.html index 5debeaba7c..054b876f81 100644 --- a/webclient/room/room.html +++ b/webclient/room/room.html @@ -93,7 +93,10 @@ </span> </span> - <span ng-show='msg.content.msgtype === "m.emote"' ng-bind-html="'* ' + (members[msg.user_id].displayname || msg.user_id) + ' ' + msg.content.body | linky:'_blank'"/> + <span ng-show='msg.content.msgtype === "m.emote"' + ng-class="msg.echo_msg_state" + ng-bind-html="'* ' + (members[msg.user_id].displayname || msg.user_id) + ' ' + msg.content.body | linky:'_blank'"/> + <span ng-show='msg.content.msgtype === "m.text"' ng-class="msg.echo_msg_state" ng-bind-html="((msg.content.msgtype === 'm.text') ? msg.content.body : '') | linky:'_blank'"/> |