mirror of
https://mau.dev/maunium/synapse.git
synced 2024-08-25 03:35:28 +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…
Reference in New Issue
Block a user