diff options
Diffstat (limited to 'webclient')
-rw-r--r-- | webclient/app-controller.js | 10 | ||||
-rw-r--r-- | webclient/app-filter.js | 35 | ||||
-rw-r--r-- | webclient/components/matrix/event-handler-service.js | 96 | ||||
-rw-r--r-- | webclient/components/matrix/matrix-filter.js | 90 | ||||
-rw-r--r-- | webclient/components/matrix/matrix-service.js | 11 | ||||
-rw-r--r-- | webclient/home/home-controller.js | 29 | ||||
-rw-r--r-- | webclient/img/close.png | bin | 0 -> 397 bytes | |||
-rw-r--r-- | webclient/recents/recents.html | 53 | ||||
-rw-r--r-- | webclient/room/room-controller.js | 28 | ||||
-rw-r--r-- | webclient/room/room-directive.js | 26 | ||||
-rw-r--r-- | webclient/room/room.html | 15 |
11 files changed, 257 insertions, 136 deletions
diff --git a/webclient/app-controller.js b/webclient/app-controller.js index 0e823b43e7..7d61207554 100644 --- a/webclient/app-controller.js +++ b/webclient/app-controller.js @@ -67,6 +67,16 @@ angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'even } }; + $scope.leave = function(room_id) { + matrixService.leave(room_id).then( + function(response) { + console.log("Left room " + room_id); + }, + function(error) { + console.log("Failed to leave room " + room_id + ": " + error.data.error); + }); + }; + // Logs the user out $scope.logout = function() { diff --git a/webclient/app-filter.js b/webclient/app-filter.js index 0d059e1621..fc16492ef3 100644 --- a/webclient/app-filter.js +++ b/webclient/app-filter.js @@ -45,34 +45,19 @@ angular.module('matrixWebClient') angular.forEach(members, function(value, key) { value["id"] = key; filtered.push( value ); - if (value["displayname"]) { - if (!displayNames[value["displayname"]]) { - displayNames[value["displayname"]] = []; - } - displayNames[value["displayname"]].push(key); - } - }); - - // FIXME: we shouldn't disambiguate displayNames on every orderMembersList - // invocation but keep track of duplicates incrementally somewhere - angular.forEach(displayNames, function(value, key) { - if (value.length > 1) { - // console.log(key + ": " + value); - for (var i=0; i < value.length; i++) { - var v = value[i]; - // FIXME: this permenantly rewrites the displayname for a given - // room member. which means we can't reset their name if it is - // no longer ambiguous! - members[v].displayname += " (" + v + ")"; - // console.log(v + " " + members[v]); - }; - } }); filtered.sort(function (a, b) { - // Sort members on their last_active_ago value - if (undefined !== a.last_active_ago || undefined !== b.last_active_ago) { - return ((a.last_active_ago || 10e10) > (b.last_active_ago || 10e10) ? 1 : -1); + // Sort members on their last_active absolute time + var aLastActiveTS = 0, bLastActiveTS = 0; + if (undefined !== a.last_active_ago) { + aLastActiveTS = a.last_updated - a.last_active_ago; + } + if (undefined !== b.last_active_ago) { + bLastActiveTS = b.last_updated - b.last_active_ago; + } + if (aLastActiveTS || bLastActiveTS) { + return bLastActiveTS - aLastActiveTS; } else { // If they do not have last_active_ago, sort them according to their presence state diff --git a/webclient/components/matrix/event-handler-service.js b/webclient/components/matrix/event-handler-service.js index 9c19f306c0..e990d42d05 100644 --- a/webclient/components/matrix/event-handler-service.js +++ b/webclient/components/matrix/event-handler-service.js @@ -101,7 +101,7 @@ function(matrixService, $rootScope, $q, $timeout, mPresence) { var initRoom = function(room_id, room) { if (!(room_id in $rootScope.events.rooms)) { - console.log("Creating new handler entry for " + room_id); + console.log("Creating new rooms entry for " + room_id); $rootScope.events.rooms[room_id] = { room_id: room_id, messages: [], @@ -113,10 +113,12 @@ function(matrixService, $rootScope, $q, $timeout, mPresence) { }; } - if (room) { + 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 (-1 === ["room_id", "messages", "state"].indexOf(field)) { + 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]; } } @@ -211,11 +213,8 @@ function(matrixService, $rootScope, $q, $timeout, mPresence) { if (shouldBing && isIdle) { console.log("Displaying notification for "+JSON.stringify(event)); - var member = $rootScope.events.rooms[event.room_id].members[event.user_id]; - var displayname = undefined; - if (member) { - displayname = member.displayname; - } + var member = getMember(event.room_id, event.user_id); + var displayname = getUserDisplayName(event.room_id, event.user_id); var message = event.content.body; if (event.content.msgtype === "m.emote") { @@ -223,7 +222,7 @@ function(matrixService, $rootScope, $q, $timeout, mPresence) { } var notification = new window.Notification( - (displayname || event.user_id) + + displayname + " (" + (matrixService.getRoomIdToAliasMapping(event.room_id) || event.room_id) + ")", // FIXME: don't leak room_ids here { "body": message, @@ -345,6 +344,65 @@ function(matrixService, $rootScope, $q, $timeout, mPresence) { 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 + * @param {String} user_id the id of the user + * @returns {String} the user displayname or user_id if not available + */ + var getUserDisplayName = function(room_id, user_id) { + var displayName; + + // Get the user display name from the member list of the room + var member = 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]; + + for (var member_id in room.members) { + if (room.members.hasOwnProperty(member_id) && member_id !== user_id) { + var member2 = room.members[member_id]; + if (member2.content.displayname && member2.content.displayname === displayName) { + displayName = displayName + " (" + user_id + ")"; + break; + } + } + } + } + } + + // The user may not have joined the room yet. So try to resolve display name from presence data + // Note: This data may not be available + if (undefined === displayName && user_id in $rootScope.presence) { + displayName = $rootScope.presence[user_id].content.displayname; + } + + if (undefined === displayName) { + // By default, use the user ID + displayName = user_id; + } + return displayName; + }; + return { ROOM_CREATE_EVENT: ROOM_CREATE_EVENT, MSG_EVENT: MSG_EVENT, @@ -516,6 +574,8 @@ function(matrixService, $rootScope, $q, $timeout, mPresence) { memberCount = 0; for (var i in room.members) { + if (!room.members.hasOwnProperty(i)) continue; + var member = room.members[i]; if ("join" === member.membership) { @@ -534,15 +594,19 @@ function(matrixService, $rootScope, $q, $timeout, mPresence) { * @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 member; - - var room = $rootScope.events.rooms[room_id]; - if (room) { - member = room.members[user_id]; - } - return member; + return getMember(room_id, user_id); }, + /** + * Return the display name of an user acccording to data already downloaded + * @param {String} room_id the room id + * @param {String} user_id the id of the user + * @returns {String} the user displayname or user_id if not available + */ + getUserDisplayName: function(room_id, user_id) { + return getUserDisplayName(room_id, user_id); + }, + setRoomVisibility: function(room_id, visible) { if (!visible) { return; diff --git a/webclient/components/matrix/matrix-filter.js b/webclient/components/matrix/matrix-filter.js index 328e3a7086..e6f2acc5fd 100644 --- a/webclient/components/matrix/matrix-filter.js +++ b/webclient/components/matrix/matrix-filter.js @@ -19,7 +19,7 @@ angular.module('matrixFilter', []) // Compute the room name according to information we have -.filter('mRoomName', ['$rootScope', 'matrixService', function($rootScope, matrixService) { +.filter('mRoomName', ['$rootScope', 'matrixService', 'eventHandlerService', function($rootScope, matrixService, eventHandlerService) { return function(room_id) { var roomName; @@ -31,49 +31,57 @@ angular.module('matrixFilter', []) if (room) { // Get name from room state date var room_name_event = room["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_name_event) { roomName = room_name_event.content.name; } else if (alias) { roomName = alias; } - else if (room.members) { - + else if (room.members && !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) { for (var i in room.members) { + if (!room.members.hasOwnProperty(i)) continue; + var member = room.members[i]; if (member.state_key !== user_id) { - - if (member.state_key in $rootScope.presence) { - // If the user is available in presence, use the displayname there - // as it is the most uptodate - roomName = $rootScope.presence[member.state_key].content.displayname; - } - else if (member.content.displayname) { - roomName = member.content.displayname; - } - else { - roomName = member.state_key; - } + roomName = eventHandlerService.getUserDisplayName(room_id, member.state_key); + break; } } } - else if (1 === Object.keys(room.members).length) { + else if (Object.keys(room.members).length <= 1) { + var otherUserId; - if (Object.keys(room.members)[0] !== user_id) { + if (Object.keys(room.members)[0] && Object.keys(room.members)[0] !== user_id) { otherUserId = Object.keys(room.members)[0]; } 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.membership) { + 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) { @@ -92,15 +100,11 @@ angular.module('matrixFilter', []) if (1 === invitedUserIDs.length) { otherUserId = invitedUserIDs[0]; } +*/ } - - // Try to resolve his displayname in presence global data - if (otherUserId in $rootScope.presence) { - roomName = $rootScope.presence[otherUserId].content.displayname; - } - else { - roomName = otherUserId; - } + + // Get the user display name + roomName = eventHandlerService.getUserDisplayName(room_id, otherUserId); } } } @@ -127,37 +131,9 @@ angular.module('matrixFilter', []) }; }]) -// Compute the user display name in a room according to the data already downloaded -.filter('mUserDisplayName', ['$rootScope', function($rootScope) { +// Return the user display name +.filter('mUserDisplayName', ['eventHandlerService', function(eventHandlerService) { return function(user_id, room_id) { - var displayName; - - // Try to find the user name among presence data - // Warning: that means we have received before a presence event for this - // user which cannot be guaranted. - // However, if we get the info by this way, we are sure this is the latest user display name - // See FIXME comment below - if (user_id in $rootScope.presence) { - displayName = $rootScope.presence[user_id].content.displayname; - } - - // FIXME: Would like to use the display name as defined in room members of the room. - // But this information is the display name of the user when he has joined the room. - // It does not take into account user display name update - if (room_id) { - var room = $rootScope.events.rooms[room_id]; - if (room && (user_id in room.members)) { - var member = room.members[user_id]; - if (member.content.displayname) { - displayName = member.content.displayname; - } - } - } - - if (undefined === displayName) { - // By default, use the user ID - displayName = user_id; - } - return displayName; + return eventHandlerService.getUserDisplayName(room_id, user_id); }; }]); diff --git a/webclient/components/matrix/matrix-service.js b/webclient/components/matrix/matrix-service.js index cb827a0b4d..a4f0568bce 100644 --- a/webclient/components/matrix/matrix-service.js +++ b/webclient/components/matrix/matrix-service.js @@ -264,7 +264,13 @@ angular.module('matrixService', []) return doRequest("GET", path, params); }, - + + // get room state for a specific room + roomState: function(room_id) { + var path = "/rooms/" + room_id + "/state"; + return doRequest("GET", path); + }, + // Joins a room join: function(room_id) { return this.membershipChange(room_id, undefined, "join"); @@ -697,11 +703,10 @@ angular.module('matrixService', []) createRoomIdToAliasMapping: function(roomId, alias) { roomIdToAlias[roomId] = alias; aliasToRoomId[alias] = roomId; - // localStorage.setItem(MAPPING_PREFIX+roomId, alias); }, getRoomIdToAliasMapping: function(roomId) { - var alias = roomIdToAlias[roomId]; // was localStorage.getItem(MAPPING_PREFIX+roomId) + var alias = roomIdToAlias[roomId]; //console.log("looking for alias for " + roomId + "; found: " + alias); return alias; }, diff --git a/webclient/home/home-controller.js b/webclient/home/home-controller.js index a535d439d3..f1295560ef 100644 --- a/webclient/home/home-controller.js +++ b/webclient/home/home-controller.js @@ -86,18 +86,24 @@ angular.module('HomeController', ['matrixService', 'eventHandlerService', 'Recen // Go to a room $scope.goToRoom = function(room_id) { - // Simply open the room page on this room id - //$location.url("room/" + room_id); matrixService.join(room_id).then( function(response) { + var final_room_id = room_id; if (response.data.hasOwnProperty("room_id")) { - if (response.data.room_id != room_id) { - $location.url("room/" + response.data.room_id); - return; - } + final_room_id = response.data.room_id; } - $location.url("room/" + room_id); + // TODO: factor out the common housekeeping whenever we try to join a room or alias + matrixService.roomState(final_room_id).then( + function(response) { + eventHandlerService.handleEvents(response.data, false, true); + }, + function(error) { + $scope.feedback = "Failed to get room state for: " + final_room_id; + } + ); + + $location.url("room/" + final_room_id); }, function(error) { $scope.feedback = "Can't join room: " + JSON.stringify(error.data); @@ -108,6 +114,15 @@ angular.module('HomeController', ['matrixService', 'eventHandlerService', 'Recen $scope.joinAlias = function(room_alias) { matrixService.joinAlias(room_alias).then( function(response) { + // TODO: factor out the common housekeeping whenever we try to join a room or alias + matrixService.roomState(response.room_id).then( + function(response) { + eventHandlerService.handleEvents(response.data, false, true); + }, + function(error) { + $scope.feedback = "Failed to get room state for: " + response.room_id; + } + ); // Go to this room $location.url("room/" + room_alias); }, diff --git a/webclient/img/close.png b/webclient/img/close.png new file mode 100644 index 0000000000..fbcdb51e6b --- /dev/null +++ b/webclient/img/close.png Binary files differdiff --git a/webclient/recents/recents.html b/webclient/recents/recents.html index edfc1677eb..9cbdcd357a 100644 --- a/webclient/recents/recents.html +++ b/webclient/recents/recents.html @@ -2,7 +2,7 @@ <table class="recentsTable"> <tbody ng-repeat="(index, room) in events.rooms | orderRecents" ng-click="goToPage('room/' + (room.room_alias ? room.room_alias : room.room_id) )" - class ="recentsRoom" + 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'"> @@ -19,6 +19,8 @@ {{ lastMsg = eventHandlerService.getLastMessage(room.room_id, true);"" }} {{ (lastMsg.ts) | date:'MMM d HH:mm' }} + + <img ng-click="leave(room.room_id); $event.stopPropagation();" src="img/close.png" width="10" height="10" style="margin-bottom: -1px; margin-left: 2px;" alt="close"/> </td> </tr> @@ -31,28 +33,35 @@ <div ng-hide="room.membership === 'invite'" ng-switch="lastMsg.type"> <div ng-switch-when="m.room.member"> - <span ng-if="'join' === lastMsg.content.membership"> - {{ lastMsg.state_key | mUserDisplayName: room.room_id}} joined - </span> - <span ng-if="'leave' === lastMsg.content.membership"> - <span ng-if="lastMsg.user_id === lastMsg.state_key"> - {{lastMsg.state_key | mUserDisplayName: room.room_id }} left - </span> - <span ng-if="lastMsg.user_id !== lastMsg.state_key"> - {{ lastMsg.user_id | mUserDisplayName: room.room_id }} - {{ {"join": "kicked", "ban": "unbanned"}[lastMsg.content.prev] }} - {{ lastMsg.state_key | mUserDisplayName: room.room_id }} - </span> - <span ng-if="'join' === lastMsg.content.prev && lastMsg.content.reason"> - : {{ lastMsg.content.reason }} + <span ng-switch="lastMsg.changedKey"> + <span ng-switch-when="membership"> + <span ng-if="'join' === lastMsg.content.membership"> + {{ lastMsg.state_key | mUserDisplayName: room.room_id }} joined + </span> + <span ng-if="'leave' === lastMsg.content.membership"> + <span ng-if="lastMsg.user_id === lastMsg.state_key"> + {{lastMsg.state_key | mUserDisplayName: room.room_id }} left + </span> + <span ng-if="lastMsg.user_id !== lastMsg.state_key"> + {{ lastMsg.user_id | mUserDisplayName: room.room_id }} + {{ {"join": "kicked", "ban": "unbanned"}[lastMsg.content.prev] }} + {{ lastMsg.state_key | mUserDisplayName: room.room_id }} + </span> + <span ng-if="'join' === lastMsg.content.prev && lastMsg.content.reason"> + : {{ lastMsg.content.reason }} + </span> + </span> + <span ng-if="'invite' === lastMsg.content.membership || 'ban' === lastMsg.content.membership"> + {{ lastMsg.user_id | mUserDisplayName: room.room_id }} + {{ {"invite": "invited", "ban": "banned"}[lastMsg.content.membership] }} + {{ lastMsg.state_key | mUserDisplayName: room.room_id }} + <span ng-if="'ban' === lastMsg.content.prev && lastMsg.content.reason"> + : {{ lastMsg.content.reason }} + </span> + </span> </span> - </span> - <span ng-if="'invite' === lastMsg.content.membership || 'ban' === lastMsg.content.membership"> - {{ lastMsg.user_id | mUserDisplayName: room.room_id }} - {{ {"invite": "invited", "ban": "banned"}[lastMsg.content.membership] }} - {{ lastMsg.state_key | mUserDisplayName: room.room_id }} - <span ng-if="'ban' === lastMsg.content.prev && lastMsg.content.reason"> - : {{ lastMsg.content.reason }} + <span ng-switch-when="displayname"> + {{ lastMsg.user_id }} changed their display name from {{ lastMsg.prev_content.displayname }} to {{ lastMsg.content.displayname }} </span> </span> </div> diff --git a/webclient/room/room-controller.js b/webclient/room/room-controller.js index c8104e39e6..f188d92cea 100644 --- a/webclient/room/room-controller.js +++ b/webclient/room/room-controller.js @@ -400,6 +400,8 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) // Find the max power level var maxPowerLevel = 0; for (var i in $scope.members) { + if (!$scope.members.hasOwnProperty(i)) continue; + var member = $scope.members[i]; if (member.powerLevel) { maxPowerLevel = Math.max(maxPowerLevel, member.powerLevel); @@ -409,6 +411,8 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) // Normalized them on a 0..100% scale to be use in css width if (maxPowerLevel) { for (var i in $scope.members) { + if (!$scope.members.hasOwnProperty(i)) continue; + var member = $scope.members[i]; member.powerLevelNorm = (member.powerLevel * 100) / maxPowerLevel; } @@ -479,6 +483,15 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) else { promise = matrixService.joinAlias(room_alias).then( function(response) { + // TODO: factor out the common housekeeping whenever we try to join a room or alias + matrixService.roomState(response.room_id).then( + function(response) { + eventHandlerService.handleEvents(response.data, false, true); + }, + function(error) { + $scope.feedback = "Failed to get room state for: " + response.room_id; + } + ); $location.url("room/" + room_alias); }, function(error) { @@ -715,6 +728,8 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) // Update the member list for (var i in members) { + if (!members.hasOwnProperty(i)) continue; + var member = members[i]; updateMemberList(member); } @@ -732,6 +747,16 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) $scope.state.waiting_for_joined_event = true; matrixService.join($scope.room_id).then( function() { + // TODO: factor out the common housekeeping whenever we try to join a room or alias + matrixService.roomState($scope.room_id).then( + function(response) { + eventHandlerService.handleEvents(response.data, false, true); + }, + function(error) { + console.error("Failed to get room state for: " + $scope.room_id); + } + ); + // onInit3 will be called once the joined m.room.member event is received from the events stream // This avoids to get the joined information twice in parallel: // - one from the events stream @@ -740,6 +765,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) }, function(reason) { console.log("Can't join room: " + JSON.stringify(reason)); + // FIXME: what if it wasn't a perms problem? $scope.state.permission_denied = "You do not have permission to join this room"; }); } @@ -809,7 +835,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) matrixService.leave($scope.room_id).then( function(response) { - console.log("Left room "); + console.log("Left room " + $scope.room_id); $location.url("home"); }, function(error) { diff --git a/webclient/room/room-directive.js b/webclient/room/room-directive.js index 8db4cb5d9a..05382cfcd3 100644 --- a/webclient/room/room-directive.js +++ b/webclient/room/room-directive.js @@ -52,12 +52,21 @@ angular.module('RoomController') // console.log("targetIndex: " + targetIndex + ", // text=" + text); - // FIXME: use the correct regexp to recognise userIDs + // FIXME: use the correct regexp to recognise userIDs --M + // // XXX: I don't really know what the point of this is. You // WANT to match freeform text given you want to match display // names AND user IDs. Surely you just want to get the last // word out of the input text and that's that? // Am I missing something here? -- Kegan + // + // You're not missing anything - my point was that we should + // explicitly define the syntax for user IDs /somewhere/. + // Meanwhile as long as the delimeters are well defined, we + // could just pick "the last word". But to know what the + // correct delimeters are, we probably do need a formal + // syntax for user IDs to refer to... --Matthew + var search = /@?([a-zA-Z0-9_\-:\.]+)$/.exec(text); if (targetIndex === 0) { // 0 is always the original text @@ -135,6 +144,21 @@ angular.module('RoomController') }); }; }]) +.directive('commandHistory', [ function() { + return function (scope, element, attrs) { + element.bind("keydown", function (event) { + var keycodePressed = event.which; + var UP_ARROW = 38; + var DOWN_ARROW = 40; + if (keycodePressed === UP_ARROW) { + scope.history.goUp(event); + } + else if (keycodePressed === DOWN_ARROW) { + scope.history.goDown(event); + } + }); + } +}]) // A directive to anchor the scroller position at the bottom when the browser is resizing. // When the screen resizes, the bottom of the element remains the same, not the top. diff --git a/webclient/room/room.html b/webclient/room/room.html index c807e2afe1..b99413cbbf 100644 --- a/webclient/room/room.html +++ b/webclient/room/room.html @@ -48,7 +48,15 @@ width="80" height="80"/> <img class="userAvatarGradient" src="img/gradient.png" title="{{ member.id }}" width="80" height="24"/> <div class="userPowerLevel" ng-style="{'width': member.powerLevelNorm +'%'}"></div> - <div class="userName">{{ member.displayname || member.id.substr(0, member.id.indexOf(':')) }}<br/>{{ member.displayname ? "" : member.id.substr(member.id.indexOf(':')) }}</div> + <div class="userName"> + <div ng-show="member.displayname"> + {{ member.id | mUserDisplayName: room_id }} + </div> + <div ng-hide="member.displayname"> + {{ member.id.substr(0, member.id.indexOf(':')) }}<br/> + {{ member.id.substr(member.id.indexOf(':')) }} + </div> + </div> </td> <td class="userPresence" ng-class="(member.presence === 'online' ? 'online' : (member.presence === 'unavailable' ? 'unavailable' : '')) + ' ' + (member.membership == 'invite' ? 'invited' : '')"> <span ng-show="member.last_active_ago">{{ member.last_active_ago + (now - member.last_updated) | duration }}<br/>ago</span> @@ -65,7 +73,7 @@ <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> <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"> {{ msg.user_id | mUserDisplayName: room_id }}</div> <div class="timestamp" ng-class="msg.echo_msg_state"> {{ (msg.content.hsob_ts || msg.ts) | date:'MMM d HH:mm' }} @@ -163,8 +171,7 @@ <td width="*"> <textarea id="mainInput" rows="1" ng-enter="send()" ng-disabled="state.permission_denied" - ng-keydown="(38 === $event.which) ? history.goUp($event) : ((40 === $event.which) ? history.goDown($event) : 0)" - ng-focus="true" autocomplete="off" tab-complete/> + ng-focus="true" autocomplete="off" tab-complete command-history/> </td> <td id="buttonsCell"> <button ng-click="send()" ng-disabled="state.permission_denied">Send</button> |