summary refs log tree commit diff
path: root/webclient/room
diff options
context:
space:
mode:
Diffstat (limited to 'webclient/room')
-rw-r--r--webclient/room/room-controller.js41
-rw-r--r--webclient/room/room-directive.js85
-rw-r--r--webclient/room/room.html25
3 files changed, 118 insertions, 33 deletions
diff --git a/webclient/room/room-controller.js b/webclient/room/room-controller.js
index c8104e39e6..d8c62c231e 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) {
@@ -702,19 +715,24 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
                 // The room members is available in the data fetched by initialSync
                 if ($rootScope.events.rooms[$scope.room_id]) {
 
-                    // There is no need to do a 1st pagination (initialSync provided enough to fill a page)
-                    if ($rootScope.events.rooms[$scope.room_id].messages.length) {
-                        $scope.state.first_pagination = false;
+                    var messages = $rootScope.events.rooms[$scope.room_id].messages;
+
+                    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)) {
+                        // If we just joined a room, we won't have this history from initial sync, so we should try to paginate it anyway    
+                        $scope.state.first_pagination = true;
                     }
                     else {
-                        // except if we just joined a room, we won't have this history from initial sync, so we should try to paginate it anyway                        
-                        $scope.state.first_pagination = true;
+                        // There is no need to do a 1st pagination (initialSync provided enough to fill a page)
+                        $scope.state.first_pagination = false;
                     }
 
                     var members = $rootScope.events.rooms[$scope.room_id].members;
 
                     // Update the member list
                     for (var i in members) {
+                        if (!members.hasOwnProperty(i)) continue;
+
                         var member = members[i];
                         updateMemberList(member);
                     }
@@ -732,6 +750,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 +768,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 +838,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 e033b003e1..05382cfcd3 100644
--- a/webclient/room/room-directive.js
+++ b/webclient/room/room-directive.js
@@ -21,39 +21,62 @@ angular.module('RoomController')
     return function (scope, element, attrs) {
         element.bind("keydown keypress", function (event) {
             // console.log("event: " + event.which);
-            if (event.which === 9) {
+            var TAB = 9;
+            var SHIFT = 16;
+            var keypressCode = event.which;
+            if (keypressCode === TAB) {
                 if (!scope.tabCompleting) { // cache our starting text
-                    // console.log("caching " + element[0].value);
                     scope.tabCompleteOriginal = element[0].value;
                     scope.tabCompleting = true;
+                    scope.tabCompleteIndex = 0;
                 }
                 
+                // loop in the right direction
                 if (event.shiftKey) {
                     scope.tabCompleteIndex--;
                     if (scope.tabCompleteIndex < 0) {
-                        scope.tabCompleteIndex = 0;
+                        // wrap to the last search match, and fix up to a real
+                        // index value after we've matched
+                        scope.tabCompleteIndex = Number.MAX_VALUE;
                     }
                 }
                 else {
                     scope.tabCompleteIndex++;
                 }
                 
+
                 var searchIndex = 0;
                 var targetIndex = scope.tabCompleteIndex;
                 var text = scope.tabCompleteOriginal;
                 
-                // console.log("targetIndex: " + targetIndex + ", text=" + text);
+                // 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) {
-                    element[0].value = text;
-                    
-                    // Force angular to wake up and update the input ng-model by firing up input event
+
+                if (targetIndex === 0) { // 0 is always the original text
+                    element[0].value = text;                    
+                    // Force angular to wake up and update the input ng-model 
+                    // by firing up input event
                     angular.element(element[0]).triggerHandler('input');
                 }
                 else if (search && search[1]) {
-                    // console.log("search found: " + search);
+                    // console.log("search found: " + search+" from "+text);
                     var expansion;
                     
                     // FIXME: could do better than linear search here
@@ -68,6 +91,7 @@ angular.module('RoomController')
                     if (searchIndex < targetIndex) { // then search raw mxids
                         angular.forEach(scope.members, function(item, name) {
                             if (searchIndex < targetIndex) {
+                                // === 1 because mxids are @username
                                 if (name.toLowerCase().indexOf(search[1].toLowerCase()) === 1) {
                                     expansion = name;
                                     searchIndex++;
@@ -76,18 +100,22 @@ angular.module('RoomController')
                         });
                     }
                     
-                    if (searchIndex === targetIndex) {
-                        // xchat-style tab complete
+                    if (searchIndex === targetIndex || 
+                            targetIndex === Number.MAX_VALUE) {
+                        // xchat-style tab complete, add a colon if tab 
+                        // completing at the start of the text
                         if (search[0].length === text.length)
-                            expansion += " : ";
+                            expansion += ": ";
                         else
                             expansion += " ";
                         element[0].value = text.replace(/@?([a-zA-Z0-9_\-:\.]+)$/, expansion);
                         // cancel blink
                         element[0].className = "";     
-                        
-                        // Force angular to wake up and update the input ng-model by firing up input event
-                        angular.element(element[0]).triggerHandler('input');
+                        if (targetIndex === Number.MAX_VALUE) {
+                            // wrap the index around to the last index found
+                            scope.tabCompleteIndex = searchIndex;
+                            targetIndex = searchIndex;
+                        }
                     }
                     else {
                         // console.log("wrapped!");
@@ -97,23 +125,40 @@ angular.module('RoomController')
                         }, 150);
                         element[0].value = text;
                         scope.tabCompleteIndex = 0;
-                        
-                        // Force angular to wake up and update the input ng-model by firing up input event
-                        angular.element(element[0]).triggerHandler('input');
                     }
+
+                    // Force angular to wak up and update the input ng-model by
+                    // firing up input event
+                    angular.element(element[0]).triggerHandler('input');
                 }
                 else {
                     scope.tabCompleteIndex = 0;
                 }
+                // prevent the default TAB operation (typically focus shifting)
                 event.preventDefault();
             }
-            else if (event.which !== 16 && scope.tabCompleting) {
+            else if (keypressCode !== SHIFT && scope.tabCompleting) {
                 scope.tabCompleting = false;
                 scope.tabCompleteIndex = 0;
             }
         });
     };
 }])
+.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 db3aa193c5..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' }}
@@ -77,10 +85,10 @@
                 </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">
-                        <span ng-if="'join' === msg.content.membership">
+                        <span ng-if="'join' === msg.content.membership && msg.changedKey === 'membership'">
                             {{ members[msg.state_key].displayname || msg.state_key }} joined
                         </span>
-                        <span ng-if="'leave' === msg.content.membership">
+                        <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
                             </span>
@@ -93,7 +101,8 @@
                                 </span>
                             </span>
                         </span>
-                        <span ng-if="'invite' === msg.content.membership || 'ban' === msg.content.membership">
+                        <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 }}
                             {{ {"invite": "invited", "ban": "banned"}[msg.content.membership] }}
                             {{ members[msg.state_key].displayname || msg.state_key }}
@@ -101,6 +110,9 @@
                                 : {{ msg.content.reason }}
                             </span>
                         </span>                        
+                        <span ng-if="msg.changedKey === 'displayname'">
+                            {{ msg.user_id }} changed their display name from {{ msg.prev_content.displayname }} to {{ msg.content.displayname }}
+                        </span>
                         
                         <span ng-show='msg.content.msgtype === "m.emote"'
                               ng-class="msg.echo_msg_state"
@@ -159,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>