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'"/>
|