summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--webclient/components/matrix/event-handler-service.js98
-rw-r--r--webclient/components/matrix/event-stream-service.js13
-rw-r--r--webclient/recents/recents-controller.js60
-rw-r--r--webclient/recents/recents.html4
-rw-r--r--webclient/room/room-controller.js46
-rw-r--r--webclient/room/room.html5
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'"/>