forked from MirrorHub/synapse
Merge branch 'master' of github.com:matrix-org/synapse into sql_refactor
This commit is contained in:
commit
7f5c7ddea9
|
@ -694,6 +694,16 @@ Invite/Joining/Leaving a room
|
||||||
Required keys:
|
Required keys:
|
||||||
membership : [join|invite] - The membership state of $user_id in room
|
membership : [join|invite] - The membership state of $user_id in room
|
||||||
$room_id.
|
$room_id.
|
||||||
|
Optional keys:
|
||||||
|
displayname,
|
||||||
|
avatar_url : String fields from the member user's profile
|
||||||
|
state,
|
||||||
|
status_msg,
|
||||||
|
mtime_age : Presence information
|
||||||
|
|
||||||
|
These optional keys provide extra information that the client is likely to
|
||||||
|
be interested in so it doesn't have to perform an additional profile or
|
||||||
|
presence information fetch.
|
||||||
|
|
||||||
Where:
|
Where:
|
||||||
join - Indicate you ($user_id) are joining the room $room_id.
|
join - Indicate you ($user_id) are joining the room $room_id.
|
||||||
|
|
|
@ -380,14 +380,23 @@ class PresenceHandler(BaseHandler):
|
||||||
logger.debug("Start polling for presence from %s", user)
|
logger.debug("Start polling for presence from %s", user)
|
||||||
|
|
||||||
if target_user:
|
if target_user:
|
||||||
target_users = [target_user]
|
target_users = set(target_user)
|
||||||
else:
|
else:
|
||||||
presence = yield self.store.get_presence_list(
|
presence = yield self.store.get_presence_list(
|
||||||
user.localpart, accepted=True
|
user.localpart, accepted=True
|
||||||
)
|
)
|
||||||
target_users = [
|
target_users = set([
|
||||||
self.hs.parse_userid(x["observed_user_id"]) for x in presence
|
self.hs.parse_userid(x["observed_user_id"]) for x in presence
|
||||||
]
|
])
|
||||||
|
|
||||||
|
# Also include people in all my rooms
|
||||||
|
|
||||||
|
rm_handler = self.homeserver.get_handlers().room_member_handler
|
||||||
|
room_ids = yield rm_handler.get_rooms_for_user(user)
|
||||||
|
|
||||||
|
for room_id in room_ids:
|
||||||
|
for member in (yield rm_handler.get_room_members(room_id)):
|
||||||
|
target_users.add(member)
|
||||||
|
|
||||||
if state is None:
|
if state is None:
|
||||||
state = yield self.store.get_presence_state(user.localpart)
|
state = yield self.store.get_presence_state(user.localpart)
|
||||||
|
|
|
@ -22,7 +22,8 @@ var matrixWebClient = angular.module('matrixWebClient', [
|
||||||
'RoomsController',
|
'RoomsController',
|
||||||
'matrixService',
|
'matrixService',
|
||||||
'eventStreamService',
|
'eventStreamService',
|
||||||
'eventHandlerService'
|
'eventHandlerService',
|
||||||
|
'infinite-scroll'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
matrixWebClient.config(['$routeProvider', '$provide', '$httpProvider',
|
matrixWebClient.config(['$routeProvider', '$provide', '$httpProvider',
|
||||||
|
|
|
@ -33,23 +33,24 @@ angular.module('eventHandlerService', [])
|
||||||
var PRESENCE_EVENT = "PRESENCE_EVENT";
|
var PRESENCE_EVENT = "PRESENCE_EVENT";
|
||||||
|
|
||||||
$rootScope.events = {
|
$rootScope.events = {
|
||||||
rooms: {}, // will contain roomId: { messages:[], members:[] }
|
rooms: {}, // will contain roomId: { messages:[], members:{userid1: event} }
|
||||||
};
|
};
|
||||||
|
|
||||||
var initRoom = function(room_id) {
|
var initRoom = function(room_id) {
|
||||||
console.log("Creating new handler entry for " + room_id);
|
if (!(room_id in $rootScope.events.rooms)) {
|
||||||
$rootScope.events.rooms[room_id] = {};
|
console.log("Creating new handler entry for " + room_id);
|
||||||
$rootScope.events.rooms[room_id].messages = [];
|
$rootScope.events.rooms[room_id] = {};
|
||||||
$rootScope.events.rooms[room_id].members = [];
|
$rootScope.events.rooms[room_id].messages = [];
|
||||||
|
$rootScope.events.rooms[room_id].members = {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var handleMessage = function(event, isLiveEvent) {
|
var handleMessage = function(event, isLiveEvent) {
|
||||||
if ("membership_target" in event.content) {
|
if ("membership_target" in event.content) {
|
||||||
event.user_id = event.content.membership_target;
|
event.user_id = event.content.membership_target;
|
||||||
}
|
}
|
||||||
if (!(event.room_id in $rootScope.events.rooms)) {
|
|
||||||
initRoom(event.room_id);
|
initRoom(event.room_id);
|
||||||
}
|
|
||||||
|
|
||||||
if (isLiveEvent) {
|
if (isLiveEvent) {
|
||||||
$rootScope.events.rooms[event.room_id].messages.push(event);
|
$rootScope.events.rooms[event.room_id].messages.push(event);
|
||||||
|
@ -67,6 +68,8 @@ angular.module('eventHandlerService', [])
|
||||||
};
|
};
|
||||||
|
|
||||||
var handleRoomMember = function(event, isLiveEvent) {
|
var handleRoomMember = function(event, isLiveEvent) {
|
||||||
|
initRoom(event.room_id);
|
||||||
|
$rootScope.events.rooms[event.room_id].members[event.user_id] = event;
|
||||||
$rootScope.$broadcast(MEMBER_EVENT, event, isLiveEvent);
|
$rootScope.$broadcast(MEMBER_EVENT, event, isLiveEvent);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,11 @@
|
||||||
|
|
||||||
<link rel="stylesheet" href="app.css">
|
<link rel="stylesheet" href="app.css">
|
||||||
<link rel="icon" href="favicon.ico">
|
<link rel="icon" href="favicon.ico">
|
||||||
|
|
||||||
|
<script type='text/javascript' src='js/jquery-1.8.3.min.js'></script>
|
||||||
<script src="js/angular.js"></script>
|
<script src="js/angular.js"></script>
|
||||||
<script src="js/angular-route.js"></script>
|
<script src="js/angular-route.js"></script>
|
||||||
|
<script type='text/javascript' src='js/ng-infinite-scroll-matrix.js'></script>
|
||||||
<script src="app.js"></script>
|
<script src="app.js"></script>
|
||||||
<script src="app-controller.js"></script>
|
<script src="app-controller.js"></script>
|
||||||
<script src="login/login-controller.js"></script>
|
<script src="login/login-controller.js"></script>
|
||||||
|
|
2
webclient/js/jquery-1.8.3.min.js
vendored
Normal file
2
webclient/js/jquery-1.8.3.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
63
webclient/js/ng-infinite-scroll-matrix.js
Normal file
63
webclient/js/ng-infinite-scroll-matrix.js
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
/* ng-infinite-scroll - v1.0.0 - 2013-02-23
|
||||||
|
Matrix: Modified to support scrolling UP to get infinite loading and to listen
|
||||||
|
to scroll events on the PARENT element, not the window.
|
||||||
|
*/
|
||||||
|
var mod;
|
||||||
|
|
||||||
|
mod = angular.module('infinite-scroll', []);
|
||||||
|
|
||||||
|
mod.directive('infiniteScroll', [
|
||||||
|
'$rootScope', '$window', '$timeout', function($rootScope, $window, $timeout) {
|
||||||
|
return {
|
||||||
|
link: function(scope, elem, attrs) {
|
||||||
|
var checkWhenEnabled, handler, scrollDistance, scrollEnabled;
|
||||||
|
$window = angular.element($window);
|
||||||
|
scrollDistance = 0;
|
||||||
|
if (attrs.infiniteScrollDistance != null) {
|
||||||
|
scope.$watch(attrs.infiniteScrollDistance, function(value) {
|
||||||
|
return scrollDistance = parseInt(value, 10);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
scrollEnabled = true;
|
||||||
|
checkWhenEnabled = false;
|
||||||
|
if (attrs.infiniteScrollDisabled != null) {
|
||||||
|
scope.$watch(attrs.infiniteScrollDisabled, function(value) {
|
||||||
|
scrollEnabled = !value;
|
||||||
|
if (scrollEnabled && checkWhenEnabled) {
|
||||||
|
checkWhenEnabled = false;
|
||||||
|
return handler();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
handler = function() {
|
||||||
|
var elementTop, remaining, shouldScroll, windowTop;
|
||||||
|
windowTop = 0;
|
||||||
|
elementTop = elem.offset().top;
|
||||||
|
shouldScroll = elementTop >= 0; // top of list is at the top of the window or further down the page
|
||||||
|
if (shouldScroll && scrollEnabled) {
|
||||||
|
if ($rootScope.$$phase) {
|
||||||
|
return scope.$eval(attrs.infiniteScroll);
|
||||||
|
} else {
|
||||||
|
return scope.$apply(attrs.infiniteScroll);
|
||||||
|
}
|
||||||
|
} else if (shouldScroll) {
|
||||||
|
return checkWhenEnabled = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
elem.parent().on('scroll', handler);
|
||||||
|
scope.$on('$destroy', function() {
|
||||||
|
return elem.parent().off('scroll', handler);
|
||||||
|
});
|
||||||
|
return $timeout((function() {
|
||||||
|
if (attrs.infiniteScrollImmediateCheck) {
|
||||||
|
if (scope.$eval(attrs.infiniteScrollImmediateCheck)) {
|
||||||
|
return handler();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return handler();
|
||||||
|
}
|
||||||
|
}), 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
]);
|
|
@ -15,6 +15,32 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
angular.module('RoomController', [])
|
angular.module('RoomController', [])
|
||||||
|
// FIXME move directives outta here!
|
||||||
|
.directive("keepScroll", function(){
|
||||||
|
return {
|
||||||
|
controller : function($scope){
|
||||||
|
var element = 0;
|
||||||
|
this.setElement = function(el){
|
||||||
|
element = el;
|
||||||
|
}
|
||||||
|
this.addItem = function(item){
|
||||||
|
element.scrollTop = (element.scrollTop+item.clientHeight+1); //1px for margin
|
||||||
|
};
|
||||||
|
},
|
||||||
|
link : function(scope,el,attr, ctrl) {
|
||||||
|
ctrl.setElement(el[0]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})
|
||||||
|
// FIXME move directives outta here!
|
||||||
|
.directive("scrollItem", function(){
|
||||||
|
return{
|
||||||
|
require : "^keepScroll",
|
||||||
|
link : function(scope, el, att, scrCtrl){
|
||||||
|
scrCtrl.addItem(el[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
.controller('RoomController', ['$scope', '$http', '$timeout', '$routeParams', '$location', 'matrixService', 'eventStreamService', 'eventHandlerService',
|
.controller('RoomController', ['$scope', '$http', '$timeout', '$routeParams', '$location', 'matrixService', 'eventStreamService', 'eventHandlerService',
|
||||||
function($scope, $http, $timeout, $routeParams, $location, matrixService, eventStreamService, eventHandlerService) {
|
function($scope, $http, $timeout, $routeParams, $location, matrixService, eventStreamService, eventHandlerService) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
@ -54,7 +80,14 @@ angular.module('RoomController', [])
|
||||||
updatePresence(event);
|
updatePresence(event);
|
||||||
});
|
});
|
||||||
|
|
||||||
var paginate = function(numItems) {
|
$scope.paginateMore = function() {
|
||||||
|
if ($scope.state.can_paginate) {
|
||||||
|
console.log("Paginating more.");
|
||||||
|
paginate(MESSAGES_PER_PAGINATION, false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var paginate = function(numItems, toBottom) {
|
||||||
matrixService.paginateBackMessages($scope.room_id, $scope.state.earliest_token, numItems).then(
|
matrixService.paginateBackMessages($scope.room_id, $scope.state.earliest_token, numItems).then(
|
||||||
function(response) {
|
function(response) {
|
||||||
eventHandlerService.handleEvents(response.data.chunk, false);
|
eventHandlerService.handleEvents(response.data.chunk, false);
|
||||||
|
@ -63,6 +96,11 @@ angular.module('RoomController', [])
|
||||||
// no more messages to paginate :(
|
// no more messages to paginate :(
|
||||||
$scope.state.can_paginate = false;
|
$scope.state.can_paginate = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (toBottom) {
|
||||||
|
console.log("Scrolling to bottom");
|
||||||
|
scrollToBottom();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
function(error) {
|
function(error) {
|
||||||
console.log("Failed to paginateBackMessages: " + JSON.stringify(error));
|
console.log("Failed to paginateBackMessages: " + JSON.stringify(error));
|
||||||
|
@ -73,6 +111,10 @@ angular.module('RoomController', [])
|
||||||
var updateMemberList = function(chunk) {
|
var updateMemberList = function(chunk) {
|
||||||
var isNewMember = !(chunk.target_user_id in $scope.members);
|
var isNewMember = !(chunk.target_user_id in $scope.members);
|
||||||
if (isNewMember) {
|
if (isNewMember) {
|
||||||
|
if ("state" in chunk.content) {
|
||||||
|
chunk.presenceState = chunk.content.state;
|
||||||
|
}
|
||||||
|
|
||||||
$scope.members[chunk.target_user_id] = chunk;
|
$scope.members[chunk.target_user_id] = chunk;
|
||||||
// get their display name and profile picture and set it to their
|
// get their display name and profile picture and set it to their
|
||||||
// member entry in $scope.members. We HAVE to use $timeout with 0 delay
|
// member entry in $scope.members. We HAVE to use $timeout with 0 delay
|
||||||
|
@ -181,7 +223,7 @@ angular.module('RoomController', [])
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
paginate(MESSAGES_PER_PAGINATION);
|
paginate(MESSAGES_PER_PAGINATION, true);
|
||||||
},
|
},
|
||||||
function(reason) {
|
function(reason) {
|
||||||
$scope.feedback = "Can't join room: " + reason;
|
$scope.feedback = "Can't join room: " + reason;
|
||||||
|
@ -223,6 +265,6 @@ angular.module('RoomController', [])
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.loadMoreHistory = function() {
|
$scope.loadMoreHistory = function() {
|
||||||
paginate(MESSAGES_PER_PAGINATION);
|
paginate(MESSAGES_PER_PAGINATION, false);
|
||||||
};
|
};
|
||||||
}]);
|
}]);
|
||||||
|
|
|
@ -20,9 +20,9 @@
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="messageTableWrapper">
|
<div class="messageTableWrapper" keep-scroll>
|
||||||
<table class="messageTable">
|
<table class="messageTable" infinite-scroll="paginateMore()">
|
||||||
<tr ng-repeat="msg in events.rooms[room_id].messages" ng-class="msg.user_id === state.user_id ? 'mine' : ''">
|
<tr ng-repeat="msg in events.rooms[room_id].messages" ng-class="msg.user_id === state.user_id ? 'mine' : ''" scroll-item>
|
||||||
<td class="leftBlock">
|
<td class="leftBlock">
|
||||||
<div class="sender" ng-hide="events.rooms[room_id].messages[$index - 1].user_id === msg.user_id">{{ members[msg.user_id].displayname || msg.user_id }}</div>
|
<div class="sender" ng-hide="events.rooms[room_id].messages[$index - 1].user_id === msg.user_id">{{ members[msg.user_id].displayname || msg.user_id }}</div>
|
||||||
<div class="timestamp">{{ msg.content.hsob_ts | date:'MMM d HH:mm:ss' }}</div>
|
<div class="timestamp">{{ msg.content.hsob_ts | date:'MMM d HH:mm:ss' }}</div>
|
||||||
|
|
|
@ -16,11 +16,11 @@ limitations under the License.
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('RoomsController', ['matrixService', 'mFileInput', 'mFileUpload'])
|
angular.module('RoomsController', ['matrixService', 'mFileInput', 'mFileUpload', 'eventHandlerService'])
|
||||||
.controller('RoomsController', ['$scope', '$location', 'matrixService', 'mFileUpload',
|
.controller('RoomsController', ['$scope', '$location', 'matrixService', 'mFileUpload', 'eventHandlerService',
|
||||||
function($scope, $location, matrixService, mFileUpload) {
|
function($scope, $location, matrixService, mFileUpload, eventHandlerService) {
|
||||||
|
|
||||||
$scope.rooms = [];
|
$scope.rooms = {};
|
||||||
$scope.public_rooms = [];
|
$scope.public_rooms = [];
|
||||||
$scope.newRoomId = "";
|
$scope.newRoomId = "";
|
||||||
$scope.feedback = "";
|
$scope.feedback = "";
|
||||||
|
@ -52,6 +52,18 @@ angular.module('RoomsController', ['matrixService', 'mFileInput', 'mFileUpload']
|
||||||
linkedEmailList: matrixService.config().emailList // linked email list
|
linkedEmailList: matrixService.config().emailList // linked email list
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.$on(eventHandlerService.MEMBER_EVENT, function(ngEvent, event, isLive) {
|
||||||
|
var config = matrixService.config();
|
||||||
|
if (event.target_user_id === config.user_id && event.content.membership === "invite") {
|
||||||
|
console.log("Invited to room " + event.room_id);
|
||||||
|
// FIXME push membership to top level key to match /im/sync
|
||||||
|
event.membership = event.content.membership;
|
||||||
|
// FIXME bodge a nicer name than the room ID for this invite.
|
||||||
|
event.room_alias = event.user_id + "'s room";
|
||||||
|
$scope.rooms[event.room_id] = event;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
var assignRoomAliases = function(data) {
|
var assignRoomAliases = function(data) {
|
||||||
for (var i=0; i<data.length; i++) {
|
for (var i=0; i<data.length; i++) {
|
||||||
var alias = matrixService.getRoomIdToAliasMapping(data[i].room_id);
|
var alias = matrixService.getRoomIdToAliasMapping(data[i].room_id);
|
||||||
|
@ -73,12 +85,13 @@ angular.module('RoomsController', ['matrixService', 'mFileInput', 'mFileUpload']
|
||||||
|
|
||||||
$scope.refresh = function() {
|
$scope.refresh = function() {
|
||||||
// List all rooms joined or been invited to
|
// List all rooms joined or been invited to
|
||||||
$scope.rooms = matrixService.rooms();
|
|
||||||
matrixService.rooms().then(
|
matrixService.rooms().then(
|
||||||
function(response) {
|
function(response) {
|
||||||
var data = assignRoomAliases(response.data);
|
var data = assignRoomAliases(response.data);
|
||||||
$scope.feedback = "Success";
|
$scope.feedback = "Success";
|
||||||
$scope.rooms = data;
|
for (var i=0; i<data.length; i++) {
|
||||||
|
$scope.rooms[data[i].room_id] = data[i];
|
||||||
|
}
|
||||||
},
|
},
|
||||||
function(error) {
|
function(error) {
|
||||||
$scope.feedback = "Failure: " + error.data;
|
$scope.feedback = "Failure: " + error.data;
|
||||||
|
|
|
@ -61,9 +61,9 @@
|
||||||
|
|
||||||
<h3>My rooms</h3>
|
<h3>My rooms</h3>
|
||||||
|
|
||||||
<div class="rooms" ng-repeat="room in rooms">
|
<div class="rooms" ng-repeat="(rm_id, room) in rooms">
|
||||||
<div>
|
<div>
|
||||||
<a href="#/room/{{ room.room_id }}" >{{ room.room_alias }}</a>
|
<a href="#/room/{{ rm_id }}" >{{ room.room_alias }}</a> {{room.membership === 'invite' ? ' (invited)' : ''}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br/>
|
<br/>
|
||||||
|
@ -86,7 +86,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<form>
|
<form>
|
||||||
<input size="40" ng-model="joinAlias.room_alias" ng-enter="joinAlias(joinAlias.room_alias)" placeholder="(e.g. #foo_channe:example.org)"/>
|
<input size="40" ng-model="joinAlias.room_alias" ng-enter="joinAlias(joinAlias.room_alias)" placeholder="(e.g. #foo_channel:example.org)"/>
|
||||||
<button ng-disabled="!joinAlias.room_alias" ng-click="joinAlias(joinAlias.room_alias)">Join room</button>
|
<button ng-disabled="!joinAlias.room_alias" ng-click="joinAlias(joinAlias.room_alias)">Join room</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue