mirror of
				https://github.com/matrix-org/synapse.git
				synced 2025-10-31 12:18:24 +00:00 
			
		
		
		
	Merge pull request #11 from matrix-org/webclient-room-data-restructure
Webclient room data restructure
This commit is contained in:
		
						commit
						020fc15d98
					
				| @ -21,8 +21,8 @@ limitations under the License. | ||||
| 'use strict'; | ||||
| 
 | ||||
| angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'eventStreamService']) | ||||
| .controller('MatrixWebClientController', ['$scope', '$location', '$rootScope', '$timeout', '$animate', 'matrixService', 'mPresence', 'eventStreamService', 'eventHandlerService', 'matrixPhoneService', | ||||
|                                function($scope, $location, $rootScope, $timeout, $animate, matrixService, mPresence, eventStreamService, eventHandlerService, matrixPhoneService) { | ||||
| .controller('MatrixWebClientController', ['$scope', '$location', '$rootScope', '$timeout', '$animate', 'matrixService', 'mPresence', 'eventStreamService', 'eventHandlerService', 'matrixPhoneService', 'modelService', | ||||
|                                function($scope, $location, $rootScope, $timeout, $animate, matrixService, mPresence, eventStreamService, eventHandlerService, matrixPhoneService, modelService) { | ||||
|           | ||||
|     // Check current URL to avoid to display the logout button on the login page
 | ||||
|     $scope.location = $location.path(); | ||||
| @ -117,7 +117,7 @@ angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'even | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         var roomMembers = angular.copy($rootScope.events.rooms[$rootScope.currentCall.room_id].members); | ||||
|         var roomMembers = angular.copy(modelService.getRoom($rootScope.currentCall.room_id).current_room_state.members); | ||||
|         delete roomMembers[matrixService.config().user_id]; | ||||
| 
 | ||||
|         $rootScope.currentCall.user_id = Object.keys(roomMembers)[0]; | ||||
|  | ||||
| @ -31,6 +31,7 @@ var matrixWebClient = angular.module('matrixWebClient', [ | ||||
|     'eventStreamService', | ||||
|     'eventHandlerService', | ||||
|     'notificationService', | ||||
|     'modelService', | ||||
|     'infinite-scroll', | ||||
|     'ui.bootstrap', | ||||
|     'monospaced.elastic' | ||||
|  | ||||
| @ -22,13 +22,12 @@ not care where the event came from, it only needs enough context to be able to | ||||
| process them. Events may be coming from the event stream, the REST API (via  | ||||
| direct GETs or via a pagination stream API), etc. | ||||
| 
 | ||||
| Typically, this service will store events or broadcast them to any listeners | ||||
| (e.g. controllers) via $broadcast. Alternatively, it may update the $rootScope | ||||
| if typically all the $on method would do is update its own $scope. | ||||
| Typically, this service will store events and broadcast them to any listeners | ||||
| (e.g. controllers) via $broadcast.  | ||||
| */ | ||||
| angular.module('eventHandlerService', []) | ||||
| .factory('eventHandlerService', ['matrixService', '$rootScope', '$q', '$timeout', 'mPresence', 'notificationService', | ||||
| function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService) { | ||||
| .factory('eventHandlerService', ['matrixService', '$rootScope', '$q', '$timeout', 'mPresence', 'notificationService', 'modelService', | ||||
| function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService, modelService) { | ||||
|     var ROOM_CREATE_EVENT = "ROOM_CREATE_EVENT"; | ||||
|     var MSG_EVENT = "MSG_EVENT"; | ||||
|     var MEMBER_EVENT = "MEMBER_EVENT"; | ||||
| @ -44,6 +43,7 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService | ||||
|     // of the app, given we never try to reap memory yet)
 | ||||
|     var eventMap = {}; | ||||
| 
 | ||||
|     // TODO: Remove this and replace with modelService.User objects.
 | ||||
|     $rootScope.presence = {}; | ||||
| 
 | ||||
|     var initialSyncDeferred; | ||||
| @ -51,81 +51,43 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService | ||||
|     var reset = function() { | ||||
|         initialSyncDeferred = $q.defer(); | ||||
|          | ||||
|         $rootScope.events = { | ||||
|             rooms: {} // will contain roomId: { messages:[], members:{userid1: event} }
 | ||||
|         }; | ||||
| 
 | ||||
|         $rootScope.presence = {}; | ||||
| 
 | ||||
|         eventMap = {}; | ||||
|     }; | ||||
|     reset(); | ||||
| 
 | ||||
|     var initRoom = function(room_id, room) { | ||||
|         if (!(room_id in $rootScope.events.rooms)) { | ||||
|             console.log("Creating new rooms entry for " + room_id); | ||||
|             $rootScope.events.rooms[room_id] = { | ||||
|                 room_id: room_id, | ||||
|                 messages: [], | ||||
|                 members: {}, | ||||
|                 // Pagination information
 | ||||
|                 pagination: { | ||||
|                     earliest_token: "END"   // how far back we've paginated
 | ||||
|                 } | ||||
|             }; | ||||
|         } | ||||
| 
 | ||||
|         if (room) { // we got an existing room object from initialsync, seemingly.
 | ||||
|             // Report all other metadata of the room object (membership, inviter, visibility, ...)
 | ||||
|             for (var field in room) { | ||||
|                 if (!room.hasOwnProperty(field)) continue; | ||||
| 
 | ||||
|                 if (-1 === ["room_id", "messages", "state"].indexOf(field)) { // why indexOf - why not ===? --Matthew
 | ||||
|                     $rootScope.events.rooms[room_id][field] = room[field]; | ||||
|                 } | ||||
|             } | ||||
|             $rootScope.events.rooms[room_id].membership = room.membership; | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     var resetRoomMessages = function(room_id) { | ||||
|         if ($rootScope.events.rooms[room_id]) { | ||||
|             $rootScope.events.rooms[room_id].messages = []; | ||||
|         } | ||||
|         var room = modelService.getRoom(room_id); | ||||
|         room.events = []; | ||||
|     }; | ||||
|      | ||||
|     // Generic method to handle events data
 | ||||
|     var handleRoomDateEvent = function(event, isLiveEvent, addToRoomMessages) { | ||||
|         // Add topic changes as if they were a room message
 | ||||
|     var handleRoomStateEvent = function(event, isLiveEvent, addToRoomMessages) { | ||||
|         var room = modelService.getRoom(event.room_id); | ||||
|         if (addToRoomMessages) { | ||||
|             if (isLiveEvent) { | ||||
|                 $rootScope.events.rooms[event.room_id].messages.push(event); | ||||
|             } | ||||
|             else { | ||||
|                 $rootScope.events.rooms[event.room_id].messages.unshift(event); | ||||
|             } | ||||
|             // some state events are displayed as messages, so add them.
 | ||||
|             room.addMessageEvent(event, !isLiveEvent); | ||||
|         } | ||||
|          | ||||
|         // live events always update, but non-live events only update if the
 | ||||
|         // ts is later.
 | ||||
|         var latestData = true; | ||||
|         if (!isLiveEvent) { | ||||
|         if (isLiveEvent) { | ||||
|             // update the current room state with the latest state
 | ||||
|             room.current_room_state.storeStateEvent(event); | ||||
|         } | ||||
|         else { | ||||
|             var eventTs = event.origin_server_ts; | ||||
|             var storedEvent = $rootScope.events.rooms[event.room_id][event.type]; | ||||
|             var storedEvent = room.current_room_state.getStateEvent(event.type, event.state_key); | ||||
|             if (storedEvent) { | ||||
|                 if (storedEvent.origin_server_ts > eventTs) { | ||||
|                     // ignore it, we have a newer one already.
 | ||||
|                     latestData = false; | ||||
|                 if (storedEvent.origin_server_ts < eventTs) { | ||||
|                     // the incoming event is newer, use it.
 | ||||
|                     room.current_room_state.storeStateEvent(event); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         if (latestData) { | ||||
|             $rootScope.events.rooms[event.room_id][event.type] = event;          | ||||
|         } | ||||
|         // TODO: handle old_room_state
 | ||||
|     }; | ||||
|      | ||||
|     var handleRoomCreate = function(event, isLiveEvent) { | ||||
|         // For now, we do not use the event data. Simply signal it to the app controllers
 | ||||
|         $rootScope.$broadcast(ROOM_CREATE_EVENT, event, isLiveEvent); | ||||
|     }; | ||||
| 
 | ||||
| @ -133,35 +95,7 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService | ||||
|         matrixService.createRoomIdToAliasMapping(event.room_id, event.content.aliases[0]); | ||||
|     }; | ||||
|      | ||||
|     var handleMessage = function(event, isLiveEvent) { | ||||
|         // Check for empty event content
 | ||||
|         var hasContent = false; | ||||
|         for (var prop in event.content) { | ||||
|             hasContent = true; | ||||
|             break; | ||||
|         } | ||||
|         if (!hasContent) { | ||||
|             // empty json object is a redacted event, so ignore.
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         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. 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); | ||||
|             } | ||||
|              | ||||
|     var displayNotification = function(event) { | ||||
|         if (window.Notification && event.user_id != matrixService.config().user_id) { | ||||
|             var shouldBing = notificationService.containsBingWord( | ||||
|                 matrixService.config().user_id, | ||||
| @ -191,7 +125,7 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService | ||||
|              | ||||
|             if (shouldBing && isIdle) { | ||||
|                 console.log("Displaying notification for "+JSON.stringify(event)); | ||||
|                     var member = getMember(event.room_id, event.user_id); | ||||
|                 var member = modelService.getMember(event.room_id, event.user_id); | ||||
|                 var displayname = getUserDisplayName(event.room_id, event.user_id); | ||||
| 
 | ||||
|                 var message = event.content.body; | ||||
| @ -203,9 +137,9 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService | ||||
|                 } | ||||
| 
 | ||||
|                 var roomTitle = matrixService.getRoomIdToAliasMapping(event.room_id); | ||||
|                     var theRoom = $rootScope.events.rooms[event.room_id]; | ||||
|                     if (!roomTitle && theRoom && theRoom["m.room.name"] && theRoom["m.room.name"].content) { | ||||
|                         roomTitle = theRoom["m.room.name"].content.name; | ||||
|                 var theRoom = modelService.getRoom(event.room_id); | ||||
|                 if (!roomTitle && theRoom.current_room_state.state("m.room.name") && theRoom.current_room_state.state("m.room.name").content) { | ||||
|                     roomTitle = theRoom.current_room_state.state("m.room.name").content.name; | ||||
|                 } | ||||
| 
 | ||||
|                 if (!roomTitle) { | ||||
| @ -223,56 +157,92 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService | ||||
|                 ); | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     var handleMessage = function(event, isLiveEvent) { | ||||
|         // Check for empty event content
 | ||||
|         var hasContent = false; | ||||
|         for (var prop in event.content) { | ||||
|             hasContent = true; | ||||
|             break; | ||||
|         } | ||||
|         if (!hasContent) { | ||||
|             // empty json object is a redacted event, so ignore.
 | ||||
|             return; | ||||
|         } | ||||
|          | ||||
|         // =======================
 | ||||
|          | ||||
|         var room = modelService.getRoom(event.room_id); | ||||
|          | ||||
|         if (event.user_id !== matrixService.config().user_id) { | ||||
|             room.addMessageEvent(event, !isLiveEvent); | ||||
|             displayNotification(event); | ||||
|         } | ||||
|         else { | ||||
|             $rootScope.events.rooms[event.room_id].messages.unshift(event); | ||||
|             // we may have locally echoed this, so we should replace the event
 | ||||
|             // instead of just adding.
 | ||||
|             room.addOrReplaceMessageEvent(event, !isLiveEvent); | ||||
|         } | ||||
|          | ||||
|         // TODO send delivery receipt if isLiveEvent
 | ||||
|          | ||||
|         // $broadcast this, as controllers may want to do funky things such as
 | ||||
|         // scroll to the bottom, etc which cannot be expressed via simple $scope
 | ||||
|         // updates.
 | ||||
|         $rootScope.$broadcast(MSG_EVENT, event, isLiveEvent); | ||||
|     }; | ||||
|      | ||||
|     var handleRoomMember = function(event, isLiveEvent, isStateEvent) { | ||||
|         var room = modelService.getRoom(event.room_id); | ||||
|          | ||||
|         // add membership changes as if they were a room message if something interesting changed
 | ||||
|         // 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
 | ||||
|         // did something change?
 | ||||
|         var memberChanges = undefined; | ||||
|         if (!isStateEvent) { | ||||
|             // could be a membership change, display name change, etc.
 | ||||
|             // Find out which one.
 | ||||
|             var memberChanges = undefined; | ||||
|             if ((event.prev_content === undefined && event.content.membership) || (event.prev_content && (event.prev_content.membership !== event.content.membership))) { | ||||
|                 memberChanges = "membership"; | ||||
|             } | ||||
|             else if (event.prev_content && (event.prev_content.displayname !== event.content.displayname)) { | ||||
|                 memberChanges = "displayname"; | ||||
|             } | ||||
| 
 | ||||
|             // mark the key which changed
 | ||||
|             event.changedKey = memberChanges; | ||||
|         } | ||||
|          | ||||
|          | ||||
|         // modify state before adding the message so it points to the right thing.
 | ||||
|         // The events are copied to avoid referencing the same event when adding
 | ||||
|         // the message (circular json structures)
 | ||||
|         if (isStateEvent || isLiveEvent) { | ||||
|             var newEvent = angular.copy(event); | ||||
|             newEvent.cnt = event.content; | ||||
|             room.current_room_state.storeStateEvent(newEvent); | ||||
|         } | ||||
|         else if (!isLiveEvent) { | ||||
|             // mutate the old room state
 | ||||
|             var oldEvent = angular.copy(event); | ||||
|             oldEvent.cnt = event.content; | ||||
|             if (event.prev_content) { | ||||
|                 // the m.room.member event we are handling is the NEW event. When
 | ||||
|                 // we keep going back in time, we want the PREVIOUS value for displaying
 | ||||
|                 // names/etc, hence the clobber here.
 | ||||
|                 oldEvent.cnt = event.prev_content; | ||||
|             } | ||||
|              | ||||
|             if (event.changedKey === "membership" && event.content.membership === "join") { | ||||
|                 // join has a prev_content but it doesn't contain all the info unlike the join, so use that.
 | ||||
|                 oldEvent.cnt = event.content; | ||||
|             } | ||||
|              | ||||
|             room.old_room_state.storeStateEvent(oldEvent); | ||||
|         } | ||||
|          | ||||
|         // If there was a change we want to display, dump it in the message
 | ||||
|             // list.
 | ||||
|         // list. This has to be done after room state is updated.
 | ||||
|         if (memberChanges) { | ||||
|                 if (isLiveEvent) { | ||||
|                     $rootScope.events.rooms[event.room_id].messages.push(event); | ||||
|                 } | ||||
|                 else { | ||||
|                     $rootScope.events.rooms[event.room_id].messages.unshift(event); | ||||
|                 } | ||||
|             } | ||||
|             room.addMessageEvent(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); | ||||
|     }; | ||||
| @ -283,30 +253,28 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService | ||||
|     }; | ||||
|      | ||||
|     var handlePowerLevels = function(event, isLiveEvent) { | ||||
|         // Keep the latest data. Do not care of events that come when paginating back
 | ||||
|         if (!$rootScope.events.rooms[event.room_id][event.type] || isLiveEvent) { | ||||
|             $rootScope.events.rooms[event.room_id][event.type] = event; | ||||
|         handleRoomStateEvent(event, isLiveEvent); | ||||
|         $rootScope.$broadcast(POWERLEVEL_EVENT, event, isLiveEvent);    | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     var handleRoomName = function(event, isLiveEvent, isStateEvent) { | ||||
|         console.log("handleRoomName room_id: " + event.room_id + " - isLiveEvent: " + isLiveEvent + " - name: " + event.content.name); | ||||
|         handleRoomDateEvent(event, isLiveEvent, !isStateEvent); | ||||
|         handleRoomStateEvent(event, isLiveEvent, !isStateEvent); | ||||
|         $rootScope.$broadcast(NAME_EVENT, event, isLiveEvent); | ||||
|     }; | ||||
|      | ||||
| 
 | ||||
|     var handleRoomTopic = function(event, isLiveEvent, isStateEvent) { | ||||
|         console.log("handleRoomTopic room_id: " + event.room_id + " - isLiveEvent: " + isLiveEvent + " - topic: " + event.content.topic); | ||||
|         handleRoomDateEvent(event, isLiveEvent, !isStateEvent); | ||||
|         handleRoomStateEvent(event, isLiveEvent, !isStateEvent); | ||||
|         $rootScope.$broadcast(TOPIC_EVENT, event, isLiveEvent); | ||||
|     }; | ||||
| 
 | ||||
|     var handleCallEvent = function(event, isLiveEvent) { | ||||
|         $rootScope.$broadcast(CALL_EVENT, event, isLiveEvent); | ||||
|         if (event.type === 'm.call.invite') { | ||||
|             $rootScope.events.rooms[event.room_id].messages.push(event); | ||||
|             var room = modelService.getRoom(event.room_id); | ||||
|             room.addMessageEvent(event, !isLiveEvent); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
| @ -320,8 +288,9 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService | ||||
|         // we need to remove something possibly: do we know the redacted
 | ||||
|         // event ID?
 | ||||
|         if (eventMap[event.redacts]) { | ||||
|             var room = modelService.getRoom(event.room_id); | ||||
|             // remove event from list of messages in this room.
 | ||||
|             var eventList = $rootScope.events.rooms[event.room_id].messages; | ||||
|             var eventList = room.events; | ||||
|             for (var i=0; i<eventList.length; i++) { | ||||
|                 if (eventList[i].event_id === event.redacts) { | ||||
|                     console.log("Removing event " + event.redacts); | ||||
| @ -330,51 +299,10 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             // broadcast the redaction so controllers can nuke this
 | ||||
|             console.log("Redacted an event."); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 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) { | ||||
|             // Start looking from the tail since the first goal of this function 
 | ||||
|             // is to find a messaged among the latest ones
 | ||||
|             for (var i = room.messages.length - 1; i > 0; i--) { | ||||
|                 var message = room.messages[i]; | ||||
|                 if (event_id === message.event_id) { | ||||
|                     index = i; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return index; | ||||
|     }; | ||||
|      | ||||
|     /** | ||||
|      * Get the member object of a room member | ||||
|      * @param {String} room_id the room id | ||||
|      * @param {String} user_id the id of the user | ||||
|      * @returns {undefined | Object} the member object of this user in this room if he is part of the room | ||||
|      */ | ||||
|     var getMember = function(room_id, user_id) { | ||||
|         var member; | ||||
| 
 | ||||
|         var room = $rootScope.events.rooms[room_id]; | ||||
|         if (room) { | ||||
|             member = room.members[user_id]; | ||||
|         } | ||||
|         return member; | ||||
|     }; | ||||
| 
 | ||||
|     /** | ||||
|      * Return the display name of an user acccording to data already downloaded | ||||
|      * @param {String} room_id the room id | ||||
| @ -385,17 +313,17 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService | ||||
|         var displayName; | ||||
| 
 | ||||
|         // Get the user display name from the member list of the room
 | ||||
|         var member = getMember(room_id, user_id); | ||||
|         var member = modelService.getMember(room_id, user_id); | ||||
|         if (member && member.content.displayname) { // Do not consider null displayname
 | ||||
|             displayName = member.content.displayname; | ||||
| 
 | ||||
|             // Disambiguate users who have the same displayname in the room
 | ||||
|             if (user_id !== matrixService.config().user_id) { | ||||
|                 var room = $rootScope.events.rooms[room_id]; | ||||
|                 var room = modelService.getRoom(room_id); | ||||
| 
 | ||||
|                 for (var member_id in room.members) { | ||||
|                     if (room.members.hasOwnProperty(member_id) && member_id !== user_id) { | ||||
|                         var member2 = room.members[member_id]; | ||||
|                 for (var member_id in room.current_room_state.members) { | ||||
|                     if (room.current_room_state.members.hasOwnProperty(member_id) && member_id !== user_id) { | ||||
|                         var member2 = room.current_room_state.members[member_id]; | ||||
|                         if (member2.content.displayname && member2.content.displayname === displayName) { | ||||
|                             displayName = displayName + " (" + user_id + ")"; | ||||
|                             break; | ||||
| @ -434,18 +362,8 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService | ||||
|             $rootScope.$broadcast(RESET_EVENT); | ||||
|         }, | ||||
|      | ||||
|         initRoom: function(room) { | ||||
|             initRoom(room.room_id, room); | ||||
|         }, | ||||
|      | ||||
|         handleEvent: function(event, isLiveEvent, isStateEvent) { | ||||
| 
 | ||||
|             // FIXME: /initialSync on a particular room is not yet available
 | ||||
|             // So initRoom on a new room is not called. Make sure the room data is initialised here
 | ||||
|             if (event.room_id) { | ||||
|                 initRoom(event.room_id); | ||||
|             } | ||||
| 
 | ||||
|             // 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
 | ||||
| @ -504,11 +422,11 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService | ||||
|                         // displays on the Room Info screen.
 | ||||
|                         if (typeof(event.state_key) === "string") { // incls. 0-len strings
 | ||||
|                             if (event.room_id) { | ||||
|                                 handleRoomDateEvent(event, isLiveEvent, false); | ||||
|                                 handleRoomStateEvent(event, isLiveEvent, false); | ||||
|                             } | ||||
|                         } | ||||
|                         console.log("Unable to handle event type " + event.type); | ||||
|                         console.log(JSON.stringify(event, undefined, 4)); | ||||
|                         // console.log(JSON.stringify(event, undefined, 4));
 | ||||
|                         break; | ||||
|                 } | ||||
|             } | ||||
| @ -524,8 +442,6 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService | ||||
| 
 | ||||
|         // Handle messages from /initialSync or /messages
 | ||||
|         handleRoomMessages: function(room_id, messages, isLiveEvents, dir) { | ||||
|             initRoom(room_id); | ||||
| 
 | ||||
|             var events = messages.chunk; | ||||
| 
 | ||||
|             // Handles messages according to their time order
 | ||||
| @ -536,21 +452,67 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService | ||||
|                 } | ||||
|                  | ||||
|                 // Store how far back we've paginated
 | ||||
|                 $rootScope.events.rooms[room_id].pagination.earliest_token = messages.end; | ||||
|                 var room = modelService.getRoom(room_id); | ||||
|                 room.old_room_state.pagination_token = messages.end; | ||||
| 
 | ||||
|             } | ||||
|             else { | ||||
|                 // InitialSync returns messages in chronological order
 | ||||
|                 // InitialSync returns messages in chronological order, so invert
 | ||||
|                 // it to get most recent > oldest
 | ||||
|                 for (var i=events.length - 1; i>=0; i--) { | ||||
|                     this.handleEvent(events[i], isLiveEvents, isLiveEvents); | ||||
|                 } | ||||
|                 // Store where to start pagination
 | ||||
|                 $rootScope.events.rooms[room_id].pagination.earliest_token = messages.start; | ||||
|                 var room = modelService.getRoom(room_id); | ||||
|                 room.old_room_state.pagination_token = messages.start; | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         handleInitialSyncDone: function(initialSyncData) { | ||||
|         handleInitialSyncDone: function(response) { | ||||
|             console.log("# handleInitialSyncDone"); | ||||
|             initialSyncDeferred.resolve(initialSyncData); | ||||
| 
 | ||||
|             var rooms = response.data.rooms; | ||||
|             for (var i = 0; i < rooms.length; ++i) { | ||||
|                 var room = rooms[i]; | ||||
|                  | ||||
|                 // FIXME: This is ming: the HS should be sending down the m.room.member
 | ||||
|                 // event for the invite in .state but it isn't, so fudge it for now.
 | ||||
|                 if (room.inviter && room.membership === "invite") { | ||||
|                     var me = matrixService.config().user_id; | ||||
|                     var fakeEvent = { | ||||
|                         event_id: "__FAKE__" + room.room_id, | ||||
|                         user_id: room.inviter, | ||||
|                         origin_server_ts: 0, | ||||
|                         room_id: room.room_id, | ||||
|                         state_key: me, | ||||
|                         type: "m.room.member", | ||||
|                         content: { | ||||
|                             membership: "invite" | ||||
|                         } | ||||
|                     }; | ||||
|                     if (!room.state) { | ||||
|                         room.state = []; | ||||
|                     } | ||||
|                     room.state.push(fakeEvent); | ||||
|                     console.log("RECV /initialSync invite >> "+room.room_id); | ||||
|                 } | ||||
|                  | ||||
|                 var newRoom = modelService.getRoom(room.room_id); | ||||
|                 newRoom.current_room_state.storeStateEvents(room.state); | ||||
|                 newRoom.old_room_state.storeStateEvents(room.state); | ||||
| 
 | ||||
|                 // this should be done AFTER storing state events since these
 | ||||
|                 // messages may make the old_room_state diverge.
 | ||||
|                 if ("messages" in room) { | ||||
|                     this.handleRoomMessages(room.room_id, room.messages, false); | ||||
|                     newRoom.current_room_state.pagination_token = room.messages.end; | ||||
|                     newRoom.old_room_state.pagination_token = room.messages.start; | ||||
|                 } | ||||
|             } | ||||
|             var presence = response.data.presence; | ||||
|             this.handleEvents(presence, false); | ||||
| 
 | ||||
|             initialSyncDeferred.resolve(response); | ||||
|         }, | ||||
| 
 | ||||
|         // Returns a promise that resolves when the initialSync request has been processed
 | ||||
| @ -571,17 +533,15 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService | ||||
|         getLastMessage: function(room_id, filterEcho) { | ||||
|             var lastMessage; | ||||
| 
 | ||||
|             var room = $rootScope.events.rooms[room_id]; | ||||
|             if (room) { | ||||
|                 for (var i = room.messages.length - 1; i >= 0; i--) { | ||||
|                     var message = room.messages[i]; | ||||
|             var events = modelService.getRoom(room_id).events; | ||||
|             for (var i = events.length - 1; i >= 0; i--) { | ||||
|                 var message = events[i]; | ||||
| 
 | ||||
|                 if (!filterEcho || undefined === message.echo_msg_state) { | ||||
|                     lastMessage = message; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|             } | ||||
| 
 | ||||
|             return lastMessage; | ||||
|         }, | ||||
| @ -594,32 +554,40 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService | ||||
|         getUsersCountInRoom: function(room_id) { | ||||
|             var memberCount; | ||||
| 
 | ||||
|             var room = $rootScope.events.rooms[room_id]; | ||||
|             if (room) { | ||||
|             var room = modelService.getRoom(room_id); | ||||
|             memberCount = 0; | ||||
|             for (var i in room.current_room_state.members) { | ||||
|                 if (!room.current_room_state.members.hasOwnProperty(i)) continue; | ||||
| 
 | ||||
|                 for (var i in room.members) { | ||||
|                     if (!room.members.hasOwnProperty(i)) continue; | ||||
|                 var member = room.current_room_state.members[i]; | ||||
| 
 | ||||
|                     var member = room.members[i]; | ||||
| 
 | ||||
|                     if ("join" === member.membership) { | ||||
|                 if ("join" === member.content.membership) { | ||||
|                     memberCount = memberCount + 1; | ||||
|                 } | ||||
|             } | ||||
|             } | ||||
| 
 | ||||
|             return memberCount; | ||||
|         }, | ||||
|          | ||||
|         /** | ||||
|          * Get the member object of a room member | ||||
|          * Return the power level of an user in a particular room | ||||
|          * @param {String} room_id the room id | ||||
|          * @param {String} user_id the id of the user | ||||
|          * @returns {undefined | Object} the member object of this user in this room if he is part of the room | ||||
|          * @param {String} user_id the user id | ||||
|          * @returns {Number} a value between 0 and 10 | ||||
|          */ | ||||
|         getMember: function(room_id, user_id) { | ||||
|             return getMember(room_id, user_id); | ||||
|         getUserPowerLevel: function(room_id, user_id) { | ||||
|             var powerLevel = 0; | ||||
|             var room = modelService.getRoom(room_id).current_room_state; | ||||
|             if (room.state("m.room.power_levels")) { | ||||
|                 if (user_id in room.state("m.room.power_levels").content) { | ||||
|                     powerLevel = room.state("m.room.power_levels").content[user_id]; | ||||
|                 } | ||||
|                 else { | ||||
|                     // Use the room default user power
 | ||||
|                     powerLevel = room.state("m.room.power_levels").content["default"]; | ||||
|                 } | ||||
|             } | ||||
|             return powerLevel; | ||||
|         }, | ||||
|          | ||||
|         /** | ||||
| @ -630,18 +598,6 @@ function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService | ||||
|          */ | ||||
|         getUserDisplayName: function(room_id, user_id) { | ||||
|             return getUserDisplayName(room_id, user_id); | ||||
|         }, | ||||
| 
 | ||||
|         setRoomVisibility: function(room_id, visible) { | ||||
|             if (!visible) { | ||||
|                 return; | ||||
|             } | ||||
|             initRoom(room_id); | ||||
|              | ||||
|             var room = $rootScope.events.rooms[room_id]; | ||||
|             if (room) { | ||||
|                 room.visibility = visible; | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| }]); | ||||
|  | ||||
| @ -109,25 +109,6 @@ angular.module('eventStreamService', []) | ||||
|         // without requiring to make an additional request
 | ||||
|         matrixService.initialSync(30, false).then( | ||||
|             function(response) { | ||||
|                 var rooms = response.data.rooms; | ||||
|                 for (var i = 0; i < rooms.length; ++i) { | ||||
|                     var room = rooms[i]; | ||||
|                      | ||||
|                     eventHandlerService.initRoom(room); | ||||
| 
 | ||||
|                     if ("messages" in room) { | ||||
|                         eventHandlerService.handleRoomMessages(room.room_id, room.messages, false); | ||||
|                     } | ||||
|                      | ||||
|                     if ("state" in room) { | ||||
|                         eventHandlerService.handleEvents(room.state, false, true); | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 var presence = response.data.presence; | ||||
|                 eventHandlerService.handleEvents(presence, false); | ||||
| 
 | ||||
|                 // Initial sync is done
 | ||||
|                 eventHandlerService.handleInitialSyncDone(response); | ||||
| 
 | ||||
|                 // Start event streaming from that point
 | ||||
|  | ||||
| @ -46,7 +46,7 @@ var isWebRTCSupported = function () { | ||||
| }; | ||||
| 
 | ||||
| angular.module('MatrixCall', []) | ||||
| .factory('MatrixCall', ['matrixService', 'matrixPhoneService', '$rootScope', '$timeout', function MatrixCallFactory(matrixService, matrixPhoneService, $rootScope, $timeout) { | ||||
| .factory('MatrixCall', ['matrixService', 'matrixPhoneService', 'modelService', '$rootScope', '$timeout', function MatrixCallFactory(matrixService, matrixPhoneService, modelService, $rootScope, $timeout) { | ||||
|     $rootScope.isWebRTCSupported = isWebRTCSupported(); | ||||
| 
 | ||||
|     var MatrixCall = function(room_id) { | ||||
| @ -213,7 +213,7 @@ angular.module('MatrixCall', []) | ||||
| 
 | ||||
|         var self = this; | ||||
| 
 | ||||
|         var roomMembers = $rootScope.events.rooms[this.room_id].members; | ||||
|         var roomMembers = modelService.getRoom(this.room_id).current_room_state.members; | ||||
|         if (roomMembers[matrixService.config().user_id].membership != 'join') { | ||||
|             console.log("We need to join the room before we can accept this call"); | ||||
|             matrixService.join(this.room_id).then(function() { | ||||
|  | ||||
| @ -19,23 +19,24 @@ | ||||
| angular.module('matrixFilter', []) | ||||
| 
 | ||||
| // Compute the room name according to information we have
 | ||||
| .filter('mRoomName', ['$rootScope', 'matrixService', 'eventHandlerService', function($rootScope, matrixService, eventHandlerService) { | ||||
| // TODO: It would be nice if this was stateless and had no dependencies. That would
 | ||||
| //       make the business logic here a lot easier to see.
 | ||||
| .filter('mRoomName', ['$rootScope', 'matrixService', 'eventHandlerService', 'modelService',  | ||||
| function($rootScope, matrixService, eventHandlerService, modelService) { | ||||
|     return function(room_id) { | ||||
|         var roomName; | ||||
| 
 | ||||
|         // If there is an alias, use it
 | ||||
|         // TODO: only one alias is managed for now
 | ||||
|         var alias = matrixService.getRoomIdToAliasMapping(room_id); | ||||
|         var room = modelService.getRoom(room_id).current_room_state; | ||||
|          | ||||
|         var room = $rootScope.events.rooms[room_id]; | ||||
|         if (room) { | ||||
|             // Get name from room state date
 | ||||
|             var room_name_event = room["m.room.name"]; | ||||
|         var room_name_event = room.state("m.room.name"); | ||||
| 
 | ||||
|         // Determine if it is a public room
 | ||||
|         var isPublicRoom = false; | ||||
|             if (room["m.room.join_rules"] && room["m.room.join_rules"].content) { | ||||
|                 isPublicRoom = ("public" === room["m.room.join_rules"].content.join_rule); | ||||
|         if (room.state("m.room.join_rules") && room.state("m.room.join_rules").content) { | ||||
|             isPublicRoom = ("public" === room.state("m.room.join_rules").content.join_rule); | ||||
|         } | ||||
| 
 | ||||
|         if (room_name_event) { | ||||
| @ -44,12 +45,11 @@ angular.module('matrixFilter', []) | ||||
|         else if (alias) { | ||||
|             roomName = alias; | ||||
|         } | ||||
|             else if (room.members && !isPublicRoom) {    // Do not rename public room
 | ||||
|              | ||||
|         else if (Object.keys(room.members).length > 0 && !isPublicRoom) { // Do not rename public room
 | ||||
|             var user_id = matrixService.config().user_id; | ||||
|                 // Else, build the name from its users
 | ||||
|                 // Limit the room renaming to 1:1 room
 | ||||
|                 if (2 === Object.keys(room.members).length) { | ||||
|              | ||||
|             // this is a "one to one" room and should have the name of the other user.
 | ||||
|             if (Object.keys(room.members).length === 2) { | ||||
|                 for (var i in room.members) { | ||||
|                     if (!room.members.hasOwnProperty(i)) continue; | ||||
| 
 | ||||
| @ -60,61 +60,31 @@ angular.module('matrixFilter', []) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|                 else if (Object.keys(room.members).length <= 1) { | ||||
|                      | ||||
|                     var otherUserId; | ||||
| 
 | ||||
|                     if (Object.keys(room.members)[0]) { | ||||
|                         otherUserId = Object.keys(room.members)[0]; | ||||
|                         // this could be an invite event (from event stream)
 | ||||
|                         if (otherUserId === user_id &&  | ||||
|                                 room.members[user_id].content.membership === "invite") { | ||||
|                             // this is us being invited to this room, so the
 | ||||
|                             // *user_id* is the other user ID and not the state
 | ||||
|                             // key.
 | ||||
|                             otherUserId = room.members[user_id].user_id; | ||||
|                         } | ||||
|             else if (Object.keys(room.members).length === 1) { | ||||
|                 // this could be just us (self-chat) or could be the other person
 | ||||
|                 // in a room if they have invited us to the room. Find out which.
 | ||||
|                 var otherUserId = Object.keys(room.members)[0]; | ||||
|                 if (otherUserId === user_id) { | ||||
|                     // it's us, we may have been invited to this room or it could
 | ||||
|                     // be a self chat.
 | ||||
|                     if (room.members[otherUserId].content.membership === "invite") { | ||||
|                         // someone invited us, use the right ID.
 | ||||
|                         roomName = eventHandlerService.getUserDisplayName(room_id, room.members[otherUserId].user_id); | ||||
|                     } | ||||
|                     else { | ||||
|                         // it's got to be an invite, or failing that a self-chat;
 | ||||
|                         otherUserId = room.inviter || user_id; | ||||
| /*                         | ||||
|                         // XXX: This should all be unnecessary now thanks to using the /rooms/<room>/roomid API
 | ||||
| 
 | ||||
|                         // The other member may be in the invite list, get all invited users
 | ||||
|                         var invitedUserIDs = []; | ||||
|                          | ||||
|                         // XXX: *SURELY* we shouldn't have to trawl through the whole messages list to
 | ||||
|                         // find invite - surely the other user should be in room.members with state invited? :/ --Matthew
 | ||||
|                         for (var i in room.messages) { | ||||
|                             var message = room.messages[i]; | ||||
|                             if ("m.room.member" === message.type && "invite" === message.content.membership) { | ||||
|                                 // Filter out the current user
 | ||||
|                                 var member_id = message.state_key; | ||||
|                                 if (member_id === user_id) { | ||||
|                                     member_id = message.user_id; | ||||
|                                 } | ||||
|                                 if (member_id !== user_id) { | ||||
|                                     // Make sure there is no duplicate user
 | ||||
|                                     if (-1 === invitedUserIDs.indexOf(member_id)) { | ||||
|                                         invitedUserIDs.push(member_id); | ||||
|                                     } | ||||
|                                 } | ||||
|                             }  | ||||
|                         } | ||||
| 
 | ||||
|                         // For now, only 1:1 room needs to be renamed. It means only 1 invited user
 | ||||
|                         if (1 === invitedUserIDs.length) { | ||||
|                             otherUserId = invitedUserIDs[0]; | ||||
|                         } | ||||
| */                         | ||||
|                     } | ||||
|                      | ||||
|                     // Get the user display name
 | ||||
|                         roomName = eventHandlerService.getUserDisplayName(room_id, otherUserId); | ||||
|                     } | ||||
|                 } | ||||
|                 else { // it isn't us, so use their name if we know it.
 | ||||
|                     roomName = eventHandlerService.getUserDisplayName(room_id, otherUserId); | ||||
|                 } | ||||
|             } | ||||
|             else if (Object.keys(room.members).length === 0) { | ||||
|                 // this shouldn't be possible
 | ||||
|                 console.error("0 members in room >> " + room_id); | ||||
|             } | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|         // Always show the alias in the room displayed name
 | ||||
|         if (roomName && alias && alias !== roomName) { | ||||
| @ -124,14 +94,6 @@ angular.module('matrixFilter', []) | ||||
|         if (undefined === roomName) { | ||||
|             // By default, use the room ID
 | ||||
|             roomName = room_id; | ||||
| 
 | ||||
|             // XXX: this is *INCREDIBLY* heavy logging for a function that calls every single
 | ||||
|             // time any kind of digest runs which refreshes a room name...
 | ||||
|             // commenting it out for now.
 | ||||
| 
 | ||||
|             // Log some information that lead to this leak
 | ||||
|             // console.log("Room ID leak for " + room_id);
 | ||||
|             // console.log("room object: " + JSON.stringify(room, undefined, 4));   
 | ||||
|         } | ||||
| 
 | ||||
|         return roomName; | ||||
|  | ||||
| @ -726,56 +726,29 @@ angular.module('matrixService', []) | ||||
|             return roomId; | ||||
|         }, | ||||
|              | ||||
|         /****** Power levels management ******/ | ||||
| 
 | ||||
|         /** | ||||
|          * Return the power level of an user in a particular room | ||||
|          * @param {String} room_id the room id | ||||
|          * @param {String} user_id the user id | ||||
|          * @returns {Number} a value between 0 and 10 | ||||
|          */ | ||||
|         getUserPowerLevel: function(room_id, user_id) { | ||||
|             var powerLevel = 0; | ||||
|             var room = $rootScope.events.rooms[room_id]; | ||||
|             if (room && room["m.room.power_levels"]) { | ||||
|                 if (user_id in room["m.room.power_levels"].content) { | ||||
|                     powerLevel = room["m.room.power_levels"].content[user_id]; | ||||
|                 } | ||||
|                 else { | ||||
|                     // Use the room default user power
 | ||||
|                     powerLevel = room["m.room.power_levels"].content["default"]; | ||||
|                 } | ||||
|             } | ||||
|             return powerLevel; | ||||
|         }, | ||||
|              | ||||
|         /** | ||||
|          * Change or reset the power level of a user | ||||
|          * @param {String} room_id the room id | ||||
|          * @param {String} user_id the user id | ||||
|          * @param {Number} powerLevel a value between 0 and 10 | ||||
|          * @param {Number} powerLevel The desired power level. | ||||
|          *    If undefined, the user power level will be reset, ie he will use the default room user power level | ||||
|          * @param event The existing m.room.power_levels event if one exists. | ||||
|          * @returns {promise} an $http promise | ||||
|          */ | ||||
|         setUserPowerLevel: function(room_id, user_id, powerLevel) { | ||||
|              | ||||
|             // Hack: currently, there is no home server API so do it by hand by updating
 | ||||
|             // the current m.room.power_levels of the room and send it to the server
 | ||||
|             var room = $rootScope.events.rooms[room_id]; | ||||
|             if (room && room["m.room.power_levels"]) { | ||||
|                 var content = angular.copy(room["m.room.power_levels"].content); | ||||
|         setUserPowerLevel: function(room_id, user_id, powerLevel, event) { | ||||
|             var content = {}; | ||||
|             if (event) { | ||||
|                 // if there is an existing event, copy the content as it contains
 | ||||
|                 // the power level values for other members which we do not want
 | ||||
|                 // to modify.
 | ||||
|                 content = angular.copy(event.content); | ||||
|             } | ||||
|             content[user_id] = powerLevel; | ||||
|                  | ||||
|             var path = "/rooms/$room_id/state/m.room.power_levels"; | ||||
|             path = path.replace("$room_id", encodeURIComponent(room_id)); | ||||
|                  | ||||
|             return doRequest("PUT", path, undefined, content); | ||||
|             } | ||||
|              | ||||
|             // The room does not exist or does not contain power_levels data
 | ||||
|             var deferred = $q.defer(); | ||||
|             deferred.reject({data:{error: "Invalid room: " + room_id}}); | ||||
|             return deferred.promise; | ||||
|         }, | ||||
| 
 | ||||
|         getTurnServer: function() { | ||||
|  | ||||
							
								
								
									
										170
									
								
								webclient/components/matrix/model-service.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								webclient/components/matrix/model-service.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,170 @@ | ||||
| /* | ||||
| Copyright 2014 OpenMarket Ltd | ||||
| 
 | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
| 
 | ||||
|     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| 
 | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| 'use strict'; | ||||
| 
 | ||||
| /* | ||||
| This service serves as the entry point for all models in the app. If access to | ||||
| underlying data in a room is required, then this service should be used as the | ||||
| dependency. | ||||
| */ | ||||
| // NB: This is more explicit than linking top-level models to $rootScope
 | ||||
| //     in that by adding this service as a dep you are clearly saying "this X
 | ||||
| //     needs access to the underlying data store", rather than polluting the
 | ||||
| //     $rootScope.
 | ||||
| angular.module('modelService', []) | ||||
| .factory('modelService', ['matrixService', function(matrixService) { | ||||
|      | ||||
|     /***** Room Object *****/ | ||||
|     var Room = function Room(room_id) { | ||||
|         this.room_id = room_id; | ||||
|         this.old_room_state = new RoomState(); | ||||
|         this.current_room_state = new RoomState(); | ||||
|         this.events = []; // events which can be displayed on the UI. TODO move?
 | ||||
|     }; | ||||
|     Room.prototype = { | ||||
|         addMessageEvents: function addMessageEvents(events, toFront) { | ||||
|             for (var i=0; i<events.length; i++) { | ||||
|                 this.addMessageEvent(events[i], toFront); | ||||
|             } | ||||
|         }, | ||||
|          | ||||
|         addMessageEvent: function addMessageEvent(event, toFront) { | ||||
|             // every message must reference the RoomMember which made it *at
 | ||||
|             // that time* so things like display names display correctly.
 | ||||
|             var stateAtTheTime = toFront ? this.old_room_state : this.current_room_state; | ||||
|             event.__room_member = stateAtTheTime.getStateEvent("m.room.member", event.user_id); | ||||
|             if (event.type === "m.room.member" && event.content.membership === "invite") { | ||||
|                 // give information on both the inviter and invitee
 | ||||
|                 event.__target_room_member = stateAtTheTime.getStateEvent("m.room.member", event.state_key); | ||||
|             } | ||||
|              | ||||
|             if (toFront) { | ||||
|                 this.events.unshift(event); | ||||
|             } | ||||
|             else { | ||||
|                 this.events.push(event); | ||||
|             } | ||||
|         }, | ||||
|          | ||||
|         addOrReplaceMessageEvent: function addOrReplaceMessageEvent(event, toFront) { | ||||
|             // Start looking from the tail since the first goal of this function 
 | ||||
|             // is to find a message among the latest ones
 | ||||
|             for (var i = this.events.length - 1; i >= 0; i--) { | ||||
|                 var storedEvent = this.events[i]; | ||||
|                 if (storedEvent.event_id === event.event_id) { | ||||
|                     // It's clobbering time!
 | ||||
|                     this.events[i] = event; | ||||
|                     return; | ||||
|                 } | ||||
|             } | ||||
|             this.addMessageEvent(event, toFront); | ||||
|         }, | ||||
|          | ||||
|         leave: function leave() { | ||||
|             return matrixService.leave(this.room_id); | ||||
|         } | ||||
|     }; | ||||
|      | ||||
|     /***** Room State Object *****/ | ||||
|     var RoomState = function RoomState() { | ||||
|         // list of RoomMember
 | ||||
|         this.members = {};  | ||||
|         // state events, the key is a compound of event type + state_key
 | ||||
|         this.state_events = {};  | ||||
|         this.pagination_token = "";  | ||||
|     }; | ||||
|     RoomState.prototype = { | ||||
|         // get a state event for this room from this.state_events. State events
 | ||||
|         // are unique per type+state_key tuple, with a lot of events using 0-len
 | ||||
|         // state keys. To make it not Really Annoying to access, this method is
 | ||||
|         // provided which can just be given the type and it will return the 
 | ||||
|         // 0-len event by default.
 | ||||
|         state: function state(type, state_key) { | ||||
|             if (!type) { | ||||
|                 return undefined; // event type MUST be specified
 | ||||
|             } | ||||
|             if (!state_key) { | ||||
|                 return this.state_events[type]; // treat as 0-len state key
 | ||||
|             } | ||||
|             return this.state_events[type + state_key]; | ||||
|         }, | ||||
|          | ||||
|         storeStateEvent: function storeState(event) { | ||||
|             this.state_events[event.type + event.state_key] = event; | ||||
|             if (event.type === "m.room.member") { | ||||
|                 this.members[event.state_key] = event; | ||||
|             } | ||||
|         }, | ||||
|          | ||||
|         storeStateEvents: function storeState(events) { | ||||
|             if (!events) { | ||||
|                 return; | ||||
|             } | ||||
|             for (var i=0; i<events.length; i++) { | ||||
|                 this.storeStateEvent(events[i]); | ||||
|             } | ||||
|         }, | ||||
|          | ||||
|         getStateEvent: function getStateEvent(event_type, state_key) { | ||||
|             return this.state_events[event_type + state_key]; | ||||
|         } | ||||
|     }; | ||||
|      | ||||
|     /***** Room Member Object *****/ | ||||
|     var RoomMember = function RoomMember() { | ||||
|         this.event = {}; // the m.room.member event representing the RoomMember.
 | ||||
|         this.user = undefined; // the User
 | ||||
|     }; | ||||
|      | ||||
|     /***** User Object *****/ | ||||
|     var User = function User() { | ||||
|         this.event = {}; // the m.presence event representing the User.
 | ||||
|     }; | ||||
|      | ||||
|     // rooms are stored here when they come in.
 | ||||
|     var rooms = { | ||||
|         // roomid: <Room>
 | ||||
|     }; | ||||
|      | ||||
|     console.log("Models inited."); | ||||
|      | ||||
|     return { | ||||
|      | ||||
|         getRoom: function(roomId) { | ||||
|             if(!rooms[roomId]) { | ||||
|                 rooms[roomId] = new Room(roomId); | ||||
|             } | ||||
|             return rooms[roomId]; | ||||
|         }, | ||||
|          | ||||
|         getRooms: function() { | ||||
|             return rooms; | ||||
|         }, | ||||
|          | ||||
|         /** | ||||
|          * Get the member object of a room member | ||||
|          * @param {String} room_id the room id | ||||
|          * @param {String} user_id the id of the user | ||||
|          * @returns {undefined | Object} the member object of this user in this room if he is part of the room | ||||
|          */ | ||||
|         getMember: function(room_id, user_id) { | ||||
|             var room = this.getRoom(room_id); | ||||
|             return room.current_room_state.members[user_id]; | ||||
|         } | ||||
|      | ||||
|     }; | ||||
| }]); | ||||
| @ -58,7 +58,6 @@ angular.module('HomeController', ['matrixService', 'eventHandlerService', 'Recen | ||||
|                     // Add room_alias & room_display_name members
 | ||||
|                     angular.extend(room, matrixService.getRoomAliasAndDisplayName(room)); | ||||
|                      | ||||
|                     eventHandlerService.setRoomVisibility(room.room_id, "public"); | ||||
|                 } | ||||
|             } | ||||
|         ); | ||||
|  | ||||
| @ -13,7 +13,7 @@ | ||||
|     | ||||
|     <script type='text/javascript' src='js/jquery-1.8.3.min.js'></script> | ||||
|     <script type="text/javascript" src="https://www.google.com/recaptcha/api/js/recaptcha_ajax.js"></script>  | ||||
|     <script src="js/angular.min.js"></script> | ||||
|     <script src="js/angular.js"></script> | ||||
|     <script src="js/angular-route.min.js"></script> | ||||
|     <script src="js/angular-sanitize.min.js"></script> | ||||
|     <script src="js/angular-animate.min.js"></script> | ||||
| @ -42,6 +42,7 @@ | ||||
|     <script src="components/matrix/event-stream-service.js"></script> | ||||
|     <script src="components/matrix/event-handler-service.js"></script> | ||||
|     <script src="components/matrix/notification-service.js"></script> | ||||
|     <script src="components/matrix/model-service.js"></script> | ||||
|     <script src="components/matrix/presence-service.js"></script> | ||||
|     <script src="components/fileInput/file-input-directive.js"></script> | ||||
|     <script src="components/fileUpload/file-upload-service.js"></script> | ||||
|  | ||||
| @ -17,12 +17,15 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| angular.module('RecentsController', ['matrixService', 'matrixFilter']) | ||||
| .controller('RecentsController', ['$rootScope', '$scope', 'eventHandlerService',  | ||||
|                                function($rootScope, $scope, eventHandlerService) { | ||||
| .controller('RecentsController', ['$rootScope', '$scope', 'eventHandlerService', 'modelService',  | ||||
|                                function($rootScope, $scope, eventHandlerService, modelService) { | ||||
| 
 | ||||
|     // Expose the service to the view
 | ||||
|     $scope.eventHandlerService = eventHandlerService; | ||||
|      | ||||
|     // retrieve all rooms and expose them
 | ||||
|     $scope.rooms = modelService.getRooms(); | ||||
| 
 | ||||
|     // $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; | ||||
|  | ||||
| @ -17,7 +17,7 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| angular.module('RecentsController') | ||||
| .filter('orderRecents', ["matrixService", "eventHandlerService", function(matrixService, eventHandlerService) { | ||||
| .filter('orderRecents', ["matrixService", "eventHandlerService", "modelService", function(matrixService, eventHandlerService, modelService) { | ||||
|     return function(rooms) { | ||||
|         var user_id = matrixService.config().user_id; | ||||
| 
 | ||||
| @ -25,26 +25,30 @@ angular.module('RecentsController') | ||||
|         // The key, room_id, is already in value objects
 | ||||
|         var filtered = []; | ||||
|         angular.forEach(rooms, function(room, room_id) { | ||||
|              | ||||
|             room.recent = {}; | ||||
|             var meEvent = room.current_room_state.state("m.room.member", user_id); | ||||
|             // Show the room only if the user has joined it or has been invited
 | ||||
|             // (ie, do not show it if he has been banned)
 | ||||
|             var member = eventHandlerService.getMember(room_id, user_id); | ||||
|             if (member && ("invite" === member.membership || "join" === member.membership)) { | ||||
|              | ||||
|             var member = modelService.getMember(room_id, user_id); | ||||
|             room.recent.me = member; | ||||
|             if (member && ("invite" === member.content.membership || "join" === member.content.membership)) { | ||||
|                 if ("invite" === member.content.membership) { | ||||
|                     room.recent.inviter = member.user_id; | ||||
|                 } | ||||
|                 // Count users here
 | ||||
|                 // TODO: Compute it directly in eventHandlerService
 | ||||
|                 room.numUsersInRoom = eventHandlerService.getUsersCountInRoom(room_id); | ||||
|                 room.recent.numUsersInRoom = eventHandlerService.getUsersCountInRoom(room_id); | ||||
| 
 | ||||
|                 filtered.push(room); | ||||
|             } | ||||
|             else if ("invite" === room.membership) { | ||||
|             else if (meEvent && "invite" === meEvent.content.membership) { | ||||
|                 // The only information we have about the room is that the user has been invited
 | ||||
|                 filtered.push(room); | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         // And time sort them
 | ||||
|         // The room with the lastest message at first
 | ||||
|         // The room with the latest message at first
 | ||||
|         filtered.sort(function (roomA, roomB) { | ||||
| 
 | ||||
|             var lastMsgRoomA = eventHandlerService.getLastMessage(roomA.room_id, true); | ||||
|  | ||||
| @ -1,16 +1,16 @@ | ||||
| <div ng-controller="RecentsController"> | ||||
|     <table class="recentsTable"> | ||||
|         <tbody ng-repeat="(index, room) in events.rooms | orderRecents"  | ||||
|         <tbody ng-repeat="(index, 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 ng-class="room['m.room.join_rules'].content.join_rule == 'public' ? 'recentsRoomName recentsPublicRoom' : 'recentsRoomName'"> | ||||
|                 <td ng-class="room.current_room_state.state('m.room.join_rules').content.join_rule == 'public' ? 'recentsRoomName recentsPublicRoom' : 'recentsRoomName'"> | ||||
|                     {{ room.room_id | mRoomName }} | ||||
|                 </td> | ||||
|                 <td class="recentsRoomSummaryUsersCount"> | ||||
|                     <span ng-show="undefined !== room.numUsersInRoom"> | ||||
|                         {{ room.numUsersInRoom || '1' }} {{ room.numUsersInRoom == 1 ? 'user' : 'users' }}                      | ||||
|                     <span ng-show="undefined !== room.recent.numUsersInRoom"> | ||||
|                         {{ room.recent.numUsersInRoom || '1' }} {{ room.recent.numUsersInRoom == 1 ? 'user' : 'users' }}                      | ||||
|                     </span> | ||||
|                 </td> | ||||
|                 <td class="recentsRoomSummaryTS"> | ||||
| @ -27,11 +27,11 @@ | ||||
|             <tr> | ||||
|                 <td colspan="3" class="recentsRoomSummary"> | ||||
| 
 | ||||
|                     <div ng-show="room.membership === 'invite'"> | ||||
|                         {{ room.inviter | mUserDisplayName: room.room_id }} invited you | ||||
|                     <div ng-show="room.recent.me.content.membership === 'invite'"> | ||||
|                         {{ room.recent.inviter | mUserDisplayName: room.room_id }} invited you | ||||
|                     </div> | ||||
|                      | ||||
|                     <div ng-hide="room.membership === 'invite'" ng-switch="lastMsg.type"> | ||||
|                     <div ng-hide="room.recent.me.membership === 'invite'" ng-switch="lastMsg.type"> | ||||
|                         <div ng-switch-when="m.room.member"> | ||||
|                             <span ng-switch="lastMsg.changedKey"> | ||||
|                                 <span ng-switch-when="membership"> | ||||
|  | ||||
| @ -15,8 +15,8 @@ limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) | ||||
| .controller('RoomController', ['$modal', '$filter', '$scope', '$timeout', '$routeParams', '$location', '$rootScope', 'matrixService', 'mPresence', 'eventHandlerService', 'mFileUpload', 'matrixPhoneService', 'MatrixCall', 'notificationService', | ||||
|                                function($modal, $filter, $scope, $timeout, $routeParams, $location, $rootScope, matrixService, mPresence, eventHandlerService, mFileUpload, matrixPhoneService, MatrixCall, notificationService) { | ||||
| .controller('RoomController', ['$modal', '$filter', '$scope', '$timeout', '$routeParams', '$location', '$rootScope', 'matrixService', 'mPresence', 'eventHandlerService', 'mFileUpload', 'matrixPhoneService', 'MatrixCall', 'notificationService', 'modelService', | ||||
|                                function($modal, $filter, $scope, $timeout, $routeParams, $location, $rootScope, matrixService, mPresence, eventHandlerService, mFileUpload, matrixPhoneService, MatrixCall, notificationService, modelService) { | ||||
|    'use strict'; | ||||
|     var MESSAGES_PER_PAGINATION = 30; | ||||
|     var THUMBNAIL_SIZE = 320; | ||||
| @ -64,7 +64,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) | ||||
|                 return; | ||||
|             }; | ||||
| 
 | ||||
|             var nameEvent = $rootScope.events.rooms[$scope.room_id]['m.room.name']; | ||||
|             var nameEvent = $scope.room.current_room_state.state_events['m.room.name']; | ||||
|             if (nameEvent) { | ||||
|                 $scope.name.newNameText = nameEvent.content.name; | ||||
|             } | ||||
| @ -105,7 +105,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) | ||||
|                 console.log("Warning: Already editing topic."); | ||||
|                 return; | ||||
|             } | ||||
|             var topicEvent = $rootScope.events.rooms[$scope.room_id]['m.room.topic']; | ||||
|             var topicEvent = $scope.room.current_room_state.state_events['m.room.topic']; | ||||
|             if (topicEvent) { | ||||
|                 $scope.topic.newTopicText = topicEvent.content.topic; | ||||
|             } | ||||
| @ -254,11 +254,11 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) | ||||
|             $scope.state.paginating = true; | ||||
|         } | ||||
|          | ||||
|         console.log("paginateBackMessages from " + $rootScope.events.rooms[$scope.room_id].pagination.earliest_token + " for " + numItems); | ||||
|         console.log("paginateBackMessages from " + $scope.room.old_room_state.pagination_token + " for " + numItems); | ||||
|         var originalTopRow = $("#messageTable>tbody>tr:first")[0]; | ||||
|          | ||||
|         // Paginate events from the point in cache
 | ||||
|         matrixService.paginateBackMessages($scope.room_id, $rootScope.events.rooms[$scope.room_id].pagination.earliest_token, numItems).then( | ||||
|         matrixService.paginateBackMessages($scope.room_id, $scope.room.old_room_state.pagination_token, numItems).then( | ||||
|             function(response) { | ||||
| 
 | ||||
|                 eventHandlerService.handleRoomMessages($scope.room_id, response.data, false, 'b'); | ||||
| @ -404,7 +404,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) | ||||
|     var updateUserPowerLevel = function(user_id) { | ||||
|         var member = $scope.members[user_id]; | ||||
|         if (member) { | ||||
|             member.powerLevel = matrixService.getUserPowerLevel($scope.room_id, user_id); | ||||
|             member.powerLevel = eventHandlerService.getUserPowerLevel($scope.room_id, user_id); | ||||
|              | ||||
|             normaliseMembersPowerLevels(); | ||||
|         } | ||||
| @ -492,7 +492,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) | ||||
|                              | ||||
|                             var room_id = matrixService.getAliasToRoomIdMapping(room_alias); | ||||
|                             console.log("joining " + room_alias + " id=" + room_id); | ||||
|                             if ($rootScope.events.rooms[room_id]) { | ||||
|                             if ($scope.room) { // TODO actually check that you = join
 | ||||
|                                 // don't send a join event for a room you're already in.
 | ||||
|                                 $location.url("room/" + room_alias); | ||||
|                             } | ||||
| @ -576,7 +576,8 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) | ||||
|                                 powerLevel = parseInt(matches[3]); | ||||
|                             } | ||||
|                             if (powerLevel !== NaN) { | ||||
|                                 promise = matrixService.setUserPowerLevel($scope.room_id, user_id, powerLevel); | ||||
|                                 var powerLevelEvent = $scope.room.current_room_state.state("m.room.power_levels"); | ||||
|                                 promise = matrixService.setUserPowerLevel($scope.room_id, user_id, powerLevel, powerLevelEvent); | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
| @ -591,7 +592,8 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) | ||||
|                     if (args) { | ||||
|                         var matches = args.match(/^(\S+)$/); | ||||
|                         if (matches) { | ||||
|                             promise = matrixService.setUserPowerLevel($scope.room_id, args, undefined); | ||||
|                             var powerLevelEvent = $scope.room.current_room_state.state("m.room.power_levels"); | ||||
|                             promise = matrixService.setUserPowerLevel($scope.room_id, args, undefined, powerLevelEvent); | ||||
|                         } | ||||
|                     } | ||||
|                      | ||||
| @ -629,7 +631,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) | ||||
|             }; | ||||
| 
 | ||||
|             $('#mainInput').val(''); | ||||
|             $rootScope.events.rooms[$scope.room_id].messages.push(echoMessage); | ||||
|             $scope.room.addMessageEvent(echoMessage); | ||||
|             scrollToBottom(); | ||||
|         } | ||||
| 
 | ||||
| @ -717,6 +719,9 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) | ||||
|      | ||||
|     var onInit2 = function() { | ||||
|         console.log("onInit2"); | ||||
|         // =============================
 | ||||
|         $scope.room = modelService.getRoom($scope.room_id); | ||||
|         // =============================
 | ||||
|          | ||||
|         // Scroll down as soon as possible so that we point to the last message
 | ||||
|         // if it already exists in memory
 | ||||
| @ -729,9 +734,9 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) | ||||
|                 var needsToJoin = true; | ||||
|                  | ||||
|                 // The room members is available in the data fetched by initialSync
 | ||||
|                 if ($rootScope.events.rooms[$scope.room_id]) { | ||||
|                 if ($scope.room) { | ||||
| 
 | ||||
|                     var messages = $rootScope.events.rooms[$scope.room_id].messages; | ||||
|                     var messages = $scope.room.events; | ||||
| 
 | ||||
|                     if (0 === messages.length | ||||
|                     || (1 === messages.length && "m.room.member" === messages[0].type && "invite" === messages[0].content.membership && $scope.state.user_id === messages[0].state_key)) { | ||||
| @ -743,7 +748,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) | ||||
|                         $scope.state.first_pagination = false; | ||||
|                     } | ||||
| 
 | ||||
|                     var members = $rootScope.events.rooms[$scope.room_id].members; | ||||
|                     var members = $scope.room.current_room_state.members; | ||||
| 
 | ||||
|                     // Update the member list
 | ||||
|                     for (var i in members) { | ||||
| @ -999,10 +1004,15 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) | ||||
|     }; | ||||
| 
 | ||||
|     $scope.openJson = function(content) { | ||||
|         $scope.event_selected = content; | ||||
|         $scope.event_selected = angular.copy(content); | ||||
|          | ||||
|         // FIXME: Pre-calculated event data should be stripped in a nicer way.
 | ||||
|         $scope.event_selected.__room_member = undefined; | ||||
|         $scope.event_selected.__target_room_member = undefined; | ||||
|          | ||||
|         // scope this so the template can check power levels and enable/disable
 | ||||
|         // buttons
 | ||||
|         $scope.pow = matrixService.getUserPowerLevel; | ||||
|         $scope.pow = eventHandlerService.getUserPowerLevel; | ||||
| 
 | ||||
|         var modalInstance = $modal.open({ | ||||
|             templateUrl: 'eventInfoTemplate.html', | ||||
| @ -1039,8 +1049,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) | ||||
|             state_key: "" | ||||
|         }; | ||||
| 
 | ||||
|         var stateFilter = $filter("stateEventsFilter"); | ||||
|         var stateEvents = stateFilter($scope.events.rooms[$scope.room_id]); | ||||
|         var stateEvents = $scope.room.current_room_state.state_events; | ||||
|         // The modal dialog will 2-way bind this field, so we MUST make a deep
 | ||||
|         // copy of the state events else we will be *actually adjusing our view
 | ||||
|         // of the world* when fiddling with the JSON!! Apparently parse/stringify
 | ||||
| @ -1059,7 +1068,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) | ||||
|     console.log("Displaying modal dialog for >>>> " + JSON.stringify($scope.event_selected)); | ||||
|     $scope.redact = function() { | ||||
|         console.log("User level = "+$scope.pow($scope.room_id, $scope.state.user_id)+ | ||||
|                     " Redact level = "+$scope.events.rooms[$scope.room_id]["m.room.ops_levels"].content.redact_level); | ||||
|                     " Redact level = "+$scope.room.current_room_state.state_events["m.room.ops_levels"].content.redact_level); | ||||
|         console.log("Redact event >> " + JSON.stringify($scope.event_selected)); | ||||
|         $modalInstance.close("redact"); | ||||
|     }; | ||||
|  | ||||
| @ -6,7 +6,7 @@ | ||||
|         </div> | ||||
|         <div class="modal-footer"> | ||||
|             <button ng-click="redact()" type="button" class="btn btn-danger"  | ||||
|              ng-disabled="!events.rooms[room_id]['m.room.ops_levels'].content.redact_level || !pow(room_id, state.user_id) || pow(room_id, state.user_id) < events.rooms[room_id]['m.room.ops_levels'].content.redact_level" | ||||
|              ng-disabled="!room.current_room_state.state('m.room.ops_levels').content.redact_level || !pow(room_id, state.user_id) || pow(room_id, state.user_id) < room.current_room_state.state('m.room.ops_levels').content.redact_level" | ||||
|              title="Delete this event on all home servers. This cannot be undone."> | ||||
|                 Redact | ||||
|             </button>         | ||||
| @ -18,7 +18,8 @@ | ||||
|             <table class="room-info"> | ||||
|             <tr ng-repeat="(key, event) in roomInfo.stateEvents" class="room-info-event"> | ||||
|                 <td class="room-info-event-meta" width="30%"> | ||||
|                     <span class="monospace">{{ key }}</span> | ||||
|                     <span class="monospace">{{ event.type }}</span> | ||||
|                     <span ng-show="event.state_key" class="monospace"> ({{event.state_key}})</span> | ||||
|                     <br/> | ||||
|                     {{ (event.origin_server_ts) | date:'MMM d HH:mm' }} | ||||
|                     <br/> | ||||
| @ -68,13 +69,13 @@ | ||||
|             </div> | ||||
| 
 | ||||
|             <div class="roomTopicSection"> | ||||
|                 <button ng-hide="events.rooms[room_id]['m.room.topic'].content.topic || topic.isEditing" | ||||
|                 <button ng-hide="room.current_room_state.state_events['m.room.topic'].content.topic || topic.isEditing" | ||||
|                     ng-click="topic.editTopic()" class="roomTopicSetNew"> | ||||
|                     Set Topic | ||||
|                 </button> | ||||
|                 <div ng-show="events.rooms[room_id]['m.room.topic'].content.topic || topic.isEditing"> | ||||
|                 <div ng-show="room.current_room_state.state_events['m.room.topic'].content.topic || topic.isEditing"> | ||||
|                     <div ng-hide="topic.isEditing" ng-dblclick="topic.editTopic()" id="roomTopic"> | ||||
|                         {{ events.rooms[room_id]['m.room.topic'].content.topic | limitTo: 200}} | ||||
|                         {{ room.current_room_state.state_events['m.room.topic'].content.topic | limitTo: 200}} | ||||
|                     </div> | ||||
|                     <form ng-submit="topic.updateTopic()" ng-show="topic.isEditing" class="roomTopicForm"> | ||||
|                         <input ng-model="topic.newTopicText" ng-blur="topic.cancelEdit()" class="roomTopicInput"  placeholder="Topic"/> | ||||
| @ -123,32 +124,34 @@ | ||||
|          ng-style="{ 'visibility': state.messages_visibility }" | ||||
|          keep-scroll> | ||||
|         <table id="messageTable" infinite-scroll="paginateMore()"> | ||||
|             <tr ng-repeat="msg in events.rooms[room_id].messages" | ||||
|                 ng-class="(events.rooms[room_id].messages[$index + 1].user_id !== msg.user_id ? 'differentUser' : '') + (msg.user_id === state.user_id ? ' mine' : '')" scroll-item> | ||||
|             <tr ng-repeat="msg in room.events" | ||||
|                 ng-class="(room.events[$index + 1].user_id !== msg.user_id ? 'differentUser' : '') + (msg.user_id === state.user_id ? ' mine' : '')" scroll-item> | ||||
|                 <td class="leftBlock"> | ||||
|                     <div class="sender" ng-hide="events.rooms[room_id].messages[$index - 1].user_id === msg.user_id"> {{ msg.user_id | mUserDisplayName: room_id }}</div> | ||||
|                     <div class="sender" ng-hide="room.events[$index - 1].user_id === msg.user_id"> {{ msg.__room_member.cnt.displayname || msg.user_id | mUserDisplayName: room_id }}</div> | ||||
|                     <div class="timestamp" | ||||
|                          ng-class="msg.echo_msg_state"> | ||||
|                         {{ (msg.origin_server_ts) | date:'MMM d HH:mm' }} | ||||
|                     </div> | ||||
|                 </td> | ||||
|                 <td class="avatar"> | ||||
|                     <img class="avatarImage" ng-src="{{ members[msg.user_id].avatar_url || 'img/default-profile.png' }}" width="32" height="32" title="{{msg.user_id}}" | ||||
|                          ng-hide="events.rooms[room_id].messages[$index - 1].user_id === msg.user_id || msg.user_id === state.user_id"/> | ||||
|                     <!-- msg.__room_member.avatar_url is just backwards compat, and can be removed in the future. --> | ||||
|                     <img class="avatarImage" ng-src="{{ msg.__room_member.cnt.avatar_url || msg.__room_member.avatar_url || 'img/default-profile.png' }}" width="32" height="32" title="{{msg.user_id}}" | ||||
|                          ng-hide="room.events[$index - 1].user_id === msg.user_id || msg.user_id === state.user_id"/> | ||||
|                 </td> | ||||
|                 <td ng-class="(!msg.content.membership && ('m.room.topic' !== msg.type && 'm.room.name' !== msg.type))? (msg.content.msgtype === 'm.emote' ? 'emote text' : 'text') : 'membership text'"> | ||||
|                     <div class="bubble" ng-dblclick="openJson(msg)"> | ||||
|                         <span ng-if="'join' === msg.content.membership && msg.changedKey === 'membership'"> | ||||
|                             {{ members[msg.state_key].displayname || msg.state_key }} joined | ||||
|                             {{ msg.content.displayname || members[msg.state_key].displayname || msg.state_key }} joined | ||||
|                         </span> | ||||
|                         <span ng-if="'leave' === msg.content.membership && msg.changedKey === 'membership'"> | ||||
|                             <span ng-if="msg.user_id === msg.state_key"> | ||||
|                                 {{ members[msg.state_key].displayname || msg.state_key }} left | ||||
|                                 <!-- FIXME: This seems like a synapse bug that the 'leave' content doesn't give the displayname... --> | ||||
|                                 {{ msg.__room_member.cnt.displayname || members[msg.state_key].displayname || msg.state_key }} left | ||||
|                             </span> | ||||
|                             <span ng-if="msg.user_id !== msg.state_key && msg.prev_content"> | ||||
|                                 {{ members[msg.user_id].displayname || msg.user_id }} | ||||
|                                 {{ msg.content.displayname || members[msg.user_id].displayname || msg.user_id }} | ||||
|                                 {{ {"invite": "kicked", "join": "kicked", "ban": "unbanned"}[msg.prev_content.membership] }} | ||||
|                                 {{ members[msg.state_key].displayname || msg.state_key }} | ||||
|                                 {{ msg.__target_room_member.content.displayname || msg.state_key }} | ||||
|                                 <span ng-if="'join' === msg.prev_content.membership && msg.content.reason"> | ||||
|                                     : {{ msg.content.reason }} | ||||
|                                 </span> | ||||
| @ -156,9 +159,9 @@ | ||||
|                         </span> | ||||
|                         <span ng-if="'invite' === msg.content.membership && msg.changedKey === 'membership' ||  | ||||
|                                      'ban' === msg.content.membership && msg.changedKey === 'membership'"> | ||||
|                             {{ members[msg.user_id].displayname || msg.user_id }} | ||||
|                             {{ msg.__room_member.cnt.displayname || msg.user_id }} | ||||
|                             {{ {"invite": "invited", "ban": "banned"}[msg.content.membership] }} | ||||
|                             {{ members[msg.state_key].displayname || msg.state_key }} | ||||
|                             {{ msg.__target_room_member.cnt.displayname || msg.state_key }} | ||||
|                             <span ng-if="msg.prev_content && 'ban' === msg.prev_content.membership && msg.content.reason"> | ||||
|                                 : {{ msg.content.reason }} | ||||
|                             </span> | ||||
| @ -204,7 +207,7 @@ | ||||
|                 </td> | ||||
|                 <td class="rightBlock"> | ||||
|                     <img class="avatarImage" ng-src="{{ members[msg.user_id].avatar_url || 'img/default-profile.png' }}" width="32" height="32" | ||||
|                          ng-hide="events.rooms[room_id].messages[$index - 1].user_id === msg.user_id || msg.user_id !== state.user_id"/> | ||||
|                          ng-hide="room.events[$index - 1].user_id === msg.user_id || msg.user_id !== state.user_id"/> | ||||
|                 </td> | ||||
|             </tr> | ||||
|         </table> | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user