diff options
Diffstat (limited to 'webclient')
27 files changed, 434 insertions, 97 deletions
diff --git a/webclient/app-controller.js b/webclient/app-controller.js index 42c45f7c31..ea48cbb011 100644 --- a/webclient/app-controller.js +++ b/webclient/app-controller.js @@ -1,5 +1,5 @@ /* -Copyright 2014 matrix.org +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. @@ -85,7 +85,7 @@ angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'even $scope.logout(); }); - $scope.updateHeader = function() { + $rootScope.updateHeader = function() { $scope.user_id = matrixService.config().user_id; }; diff --git a/webclient/app-directive.js b/webclient/app-directive.js index eee0d3842f..75283598ab 100644 --- a/webclient/app-directive.js +++ b/webclient/app-directive.js @@ -1,5 +1,5 @@ /* - Copyright 2014 matrix.org + 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. diff --git a/webclient/app-filter.js b/webclient/app-filter.js index b8d3d2a0d8..27f435674f 100644 --- a/webclient/app-filter.js +++ b/webclient/app-filter.js @@ -1,5 +1,5 @@ /* - Copyright 2014 matrix.org + 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. @@ -97,13 +97,55 @@ angular.module('matrixWebClient') // Else, build the name from its users var room = $rootScope.events.rooms[room_id]; if (room) { - if (room.members) { + var room_name_event = room["m.room.name"]; + + if (room_name_event) { + roomName = room_name_event.content.name; + } + else if (room.members) { // Limit the room renaming to 1:1 room if (2 === Object.keys(room.members).length) { for (var i in room.members) { var member = room.members[i]; - if (member.user_id !== matrixService.config().user_id) { - roomName = member.content.displayname ? member.content.displayname : member.user_id; + if (member.state_key !== matrixService.config().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; + } + } + } + } + else if (1 === Object.keys(room.members).length) { + // The other member may be in the invite list, get all invited users + var invitedUserIDs = []; + for (var i in room.messages) { + var message = room.messages[i]; + if ("m.room.member" === message.type && "invite" === message.membership) { + // Make sure there is no duplicate user + if (-1 === invitedUserIDs.indexOf(message.state_key)) { + invitedUserIDs.push(message.state_key); + } + } + } + + // For now, only 1:1 room needs to be renamed. It means only 1 invited user + if (1 === invitedUserIDs.length) { + var userID = invitedUserIDs[0]; + + // Try to resolve his displayname in presence global data + if (userID in $rootScope.presence) { + roomName = $rootScope.presence[userID].content.displayname; + } + else { + roomName = userID; } } } diff --git a/webclient/app.css b/webclient/app.css index c27ec797a4..425d5bb11a 100755 --- a/webclient/app.css +++ b/webclient/app.css @@ -270,9 +270,9 @@ a:active { color: #000; } .userAvatar .userPowerLevel { position: absolute; - bottom: 20px; - height: 1px; - background-color: red; + bottom: 0px; + height: 2px; + background-color: #f00; } .userPresence { @@ -525,3 +525,13 @@ a:active { color: #000; } font-size: 24px; } +#user-displayname-input { + width: 160px; + max-width: 155px; +} + +#user-save-button { + width: 160px; + font-size: 14px; +} + diff --git a/webclient/app.js b/webclient/app.js index dac4f048cd..d25e2a6234 100644 --- a/webclient/app.js +++ b/webclient/app.js @@ -1,5 +1,5 @@ /* -Copyright 2014 matrix.org +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. diff --git a/webclient/components/fileInput/file-input-directive.js b/webclient/components/fileInput/file-input-directive.js index c5e4ae07a8..14e2f772f7 100644 --- a/webclient/components/fileInput/file-input-directive.js +++ b/webclient/components/fileInput/file-input-directive.js @@ -1,5 +1,5 @@ /* - Copyright 2014 matrix.org + 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. diff --git a/webclient/components/fileUpload/file-upload-service.js b/webclient/components/fileUpload/file-upload-service.js index 699a3cbffc..e0f67b2c6c 100644 --- a/webclient/components/fileUpload/file-upload-service.js +++ b/webclient/components/fileUpload/file-upload-service.js @@ -1,5 +1,5 @@ /* - Copyright 2014 matrix.org + 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. diff --git a/webclient/components/matrix/event-handler-service.js b/webclient/components/matrix/event-handler-service.js index d6a0600132..ee478d2eb0 100644 --- a/webclient/components/matrix/event-handler-service.js +++ b/webclient/components/matrix/event-handler-service.js @@ -1,5 +1,5 @@ /* -Copyright 2014 matrix.org +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. @@ -32,7 +32,9 @@ angular.module('eventHandlerService', []) var MSG_EVENT = "MSG_EVENT"; var MEMBER_EVENT = "MEMBER_EVENT"; var PRESENCE_EVENT = "PRESENCE_EVENT"; + var POWERLEVEL_EVENT = "POWERLEVEL_EVENT"; var CALL_EVENT = "CALL_EVENT"; + var NAME_EVENT = "NAME_EVENT"; var InitialSyncDeferred = $q.defer(); @@ -95,7 +97,7 @@ angular.module('eventHandlerService', []) } } - $rootScope.events.rooms[event.room_id].members[event.user_id] = event; + $rootScope.events.rooms[event.room_id].members[event.state_key] = event; $rootScope.$broadcast(MEMBER_EVENT, event, isLiveEvent); }; @@ -107,10 +109,20 @@ angular.module('eventHandlerService', []) var handlePowerLevels = function(event, isLiveEvent) { initRoom(event.room_id); - $rootScope.events.rooms[event.room_id][event.type] = event; + // 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; + $rootScope.$broadcast(POWERLEVEL_EVENT, event, isLiveEvent); + } + }; - //TODO - //$rootScope.$broadcast(PRESENCE_EVENT, event, isLiveEvent); + var handleRoomName = function(event, isLiveEvent) { + console.log("handleRoomName " + isLiveEvent); + + initRoom(event.room_id); + + $rootScope.events.rooms[event.room_id][event.type] = event; + $rootScope.$broadcast(NAME_EVENT, event, isLiveEvent); }; var handleCallEvent = function(event, isLiveEvent) { @@ -122,7 +134,9 @@ angular.module('eventHandlerService', []) MSG_EVENT: MSG_EVENT, MEMBER_EVENT: MEMBER_EVENT, PRESENCE_EVENT: PRESENCE_EVENT, + POWERLEVEL_EVENT: POWERLEVEL_EVENT, CALL_EVENT: CALL_EVENT, + NAME_EVENT: NAME_EVENT, handleEvent: function(event, isLiveEvent) { @@ -146,7 +160,9 @@ angular.module('eventHandlerService', []) case 'm.room.power_levels': handlePowerLevels(event, isLiveEvent); break; - + case 'm.room.name': + handleRoomName(event, isLiveEvent); + break; default: console.log("Unable to handle event type " + event.type); console.log(JSON.stringify(event, undefined, 4)); diff --git a/webclient/components/matrix/event-stream-service.js b/webclient/components/matrix/event-stream-service.js index 441148670e..1c0f7712b4 100644 --- a/webclient/components/matrix/event-stream-service.js +++ b/webclient/components/matrix/event-stream-service.js @@ -1,5 +1,5 @@ /* -Copyright 2014 matrix.org +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. diff --git a/webclient/components/matrix/matrix-call.js b/webclient/components/matrix/matrix-call.js index 47b63d7f2f..3e13e4e81f 100644 --- a/webclient/components/matrix/matrix-call.js +++ b/webclient/components/matrix/matrix-call.js @@ -1,5 +1,5 @@ /* -Copyright 2014 matrix.org +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. diff --git a/webclient/components/matrix/matrix-phone-service.js b/webclient/components/matrix/matrix-phone-service.js index d9e2e8baa3..ca86b473e7 100644 --- a/webclient/components/matrix/matrix-phone-service.js +++ b/webclient/components/matrix/matrix-phone-service.js @@ -1,5 +1,5 @@ /* -Copyright 2014 matrix.org +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. diff --git a/webclient/components/matrix/matrix-service.js b/webclient/components/matrix/matrix-service.js index 2ae55bea9f..7c6d4ae50f 100644 --- a/webclient/components/matrix/matrix-service.js +++ b/webclient/components/matrix/matrix-service.js @@ -1,5 +1,5 @@ /* -Copyright 2014 matrix.org +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. @@ -84,13 +84,14 @@ angular.module('matrixService', []) prefix: prefixPath, // Register an user - register: function(user_name, password) { + register: function(user_name, password, threepidCreds) { // The REST path spec var path = "/register"; return doRequest("POST", path, undefined, { user_id: user_name, - password: password + password: password, + threepidCreds: threepidCreds }); }, @@ -166,6 +167,29 @@ angular.module('matrixService', []) return doRequest("POST", path, undefined, data); }, + // Change the membership of an another user + setMembership: function(room_id, user_id, membershipValue) { + // The REST path spec + var path = "/rooms/$room_id/state/m.room.member/$user_id"; + path = path.replace("$room_id", encodeURIComponent(room_id)); + path = path.replace("$user_id", user_id); + + return doRequest("PUT", path, undefined, { + membership: membershipValue + }); + }, + + // Bans a user from from a room + ban: function(room_id, user_id, reason) { + var path = "/rooms/$room_id/ban"; + path = path.replace("$room_id", encodeURIComponent(room_id)); + + return doRequest("POST", path, undefined, { + user_id: user_id, + reason: reason + }); + }, + // Retrieves the room ID corresponding to a room alias resolveRoomAlias:function(room_alias) { var path = "/_matrix/client/api/v1/directory/room/$room_alias"; @@ -252,7 +276,7 @@ angular.module('matrixService', []) // get a list of public rooms on your home server publicRooms: function() { - var path = "/publicRooms" + var path = "/publicRooms"; return doRequest("GET", path); }, @@ -308,16 +332,16 @@ angular.module('matrixService', []) // hit the Identity Server for a 3PID request. linkEmail: function(email, clientSecret, sendAttempt) { - var path = "/_matrix/identity/api/v1/validate/email/requestToken" + var path = "/_matrix/identity/api/v1/validate/email/requestToken"; var data = "clientSecret="+clientSecret+"&email=" + encodeURIComponent(email)+"&sendAttempt="+sendAttempt; var headers = {}; headers["Content-Type"] = "application/x-www-form-urlencoded"; return doBaseRequest(config.identityServer, "POST", path, {}, data, headers); }, - authEmail: function(clientSecret, tokenId, code) { + authEmail: function(clientSecret, sid, code) { var path = "/_matrix/identity/api/v1/validate/email/submitToken"; - var data = "token="+code+"&sid="+tokenId+"&clientSecret="+clientSecret; + var data = "token="+code+"&sid="+sid+"&clientSecret="+clientSecret; var headers = {}; headers["Content-Type"] = "application/x-www-form-urlencoded"; return doBaseRequest(config.identityServer, "POST", path, {}, data, headers); @@ -330,6 +354,11 @@ angular.module('matrixService', []) headers["Content-Type"] = "application/x-www-form-urlencoded"; return doBaseRequest(config.identityServer, "POST", path, {}, data, headers); }, + + lookup3pid: function(medium, address) { + var path = "/_matrix/identity/api/v1/lookup?medium="+encodeURIComponent(medium)+"&address="+encodeURIComponent(address); + return doBaseRequest(config.identityServer, "GET", path, {}, undefined, {}); + }, uploadContent: function(file) { var path = "/_matrix/content"; @@ -408,7 +437,8 @@ angular.module('matrixService', []) state: presence }); }, - + + /****** Permanent storage of user information ******/ // Returns the current config @@ -508,6 +538,35 @@ angular.module('matrixService', []) } } 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 + * If undefined, the user power level will be reset, ie he will use the default room user power level + * @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); + 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; } }; diff --git a/webclient/components/matrix/presence-service.js b/webclient/components/matrix/presence-service.js index 555118133b..952c8ec8a9 100644 --- a/webclient/components/matrix/presence-service.js +++ b/webclient/components/matrix/presence-service.js @@ -1,5 +1,5 @@ /* - Copyright 2014 matrix.org + 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. diff --git a/webclient/components/utilities/utilities-service.js b/webclient/components/utilities/utilities-service.js index 3df2f04458..b417cc5b39 100644 --- a/webclient/components/utilities/utilities-service.js +++ b/webclient/components/utilities/utilities-service.js @@ -1,5 +1,5 @@ /* - Copyright 2014 matrix.org + 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. diff --git a/webclient/home/home-controller.js b/webclient/home/home-controller.js index f4ce3053ea..85e8990c29 100644 --- a/webclient/home/home-controller.js +++ b/webclient/home/home-controller.js @@ -1,5 +1,5 @@ /* -Copyright 2014 matrix.org +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. @@ -74,7 +74,7 @@ angular.module('HomeController', ['matrixService', 'eventHandlerService', 'Recen response.data.room_id, response.data.room_alias); }, function(error) { - $scope.feedback = "Failure: " + error.data; + $scope.feedback = "Failure: " + JSON.stringify(error.data); }); }; @@ -94,7 +94,7 @@ angular.module('HomeController', ['matrixService', 'eventHandlerService', 'Recen $location.url("room/" + room_id); }, function(error) { - $scope.feedback = "Can't join room: " + error.data; + $scope.feedback = "Can't join room: " + JSON.stringify(error.data); } ); }; @@ -106,7 +106,7 @@ angular.module('HomeController', ['matrixService', 'eventHandlerService', 'Recen $location.url("room/" + room_alias); }, function(error) { - $scope.feedback = "Can't join room: " + error.data; + $scope.feedback = "Can't join room: " + JSON.stringify(error.data); } ); }; diff --git a/webclient/login/login-controller.js b/webclient/login/login-controller.js index 7369a28ef0..5ef39a7122 100644 --- a/webclient/login/login-controller.js +++ b/webclient/login/login-controller.js @@ -1,5 +1,5 @@ /* - Copyright 2014 matrix.org + 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. @@ -15,8 +15,8 @@ */ angular.module('LoginController', ['matrixService']) -.controller('LoginController', ['$scope', '$location', 'matrixService', 'eventStreamService', - function($scope, $location, matrixService, eventStreamService) { +.controller('LoginController', ['$scope', '$rootScope', '$location', 'matrixService', 'eventStreamService', + function($scope, $rootScope, $location, matrixService, eventStreamService) { 'use strict'; @@ -51,10 +51,36 @@ angular.module('LoginController', ['matrixService']) matrixService.setConfig({ homeserver: $scope.account.homeserver, identityServer: $scope.account.identityServer, + }); + switch ($scope.login_type) { + case 'mxid': + $scope.login_with_mxid($scope.account.user_id, $scope.account.password); + break; + case 'email': + matrixService.lookup3pid('email', $scope.account.user_id).then( + function(response) { + if (response.data['address'] == undefined) { + $scope.login_error_msg = "Invalid email address / password"; + } else { + console.log("Got address "+response.data['mxid']+" for email "+$scope.account.user_id); + $scope.login_with_mxid(response.data['mxid'], $scope.account.password); + } + }, + function() { + $scope.login_error_msg = "Couldn't look up email address. Is your identity server set correctly?"; + } + ); + } + }; + + $scope.login_with_mxid = function(mxid, password) { + matrixService.setConfig({ + homeserver: $scope.account.homeserver, + identityServer: $scope.account.identityServer, user_id: $scope.account.user_id }); // try to login - matrixService.login($scope.account.user_id, $scope.account.password).then( + matrixService.login(mxid, password).then( function(response) { if ("access_token" in response.data) { $scope.feedback = "Login successful."; @@ -65,6 +91,7 @@ angular.module('LoginController', ['matrixService']) access_token: response.data.access_token }); matrixService.saveConfig(); + $rootScope.updateHeader(); eventStreamService.resume(); $location.url("home"); } diff --git a/webclient/login/register-controller.js b/webclient/login/register-controller.js index 0ece57502b..b7584a7d33 100644 --- a/webclient/login/register-controller.js +++ b/webclient/login/register-controller.js @@ -1,5 +1,5 @@ /* - Copyright 2014 matrix.org + 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. @@ -15,8 +15,8 @@ */ angular.module('RegisterController', ['matrixService']) -.controller('RegisterController', ['$scope', '$location', 'matrixService', 'eventStreamService', - function($scope, $location, matrixService, eventStreamService) { +.controller('RegisterController', ['$scope', '$rootScope', '$location', 'matrixService', 'eventStreamService', + function($scope, $rootScope, $location, matrixService, eventStreamService) { 'use strict'; // FIXME: factor out duplication with login-controller.js @@ -30,6 +30,17 @@ angular.module('RegisterController', ['matrixService']) { hs_url += ":" + $location.port(); } + + var generateClientSecret = function() { + var ret = ""; + var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + + for (var i = 0; i < 32; i++) { + ret += chars.charAt(Math.floor(Math.random() * chars.length)); + } + + return ret; + }; $scope.account = { homeserver: hs_url, @@ -43,7 +54,6 @@ angular.module('RegisterController', ['matrixService']) }; $scope.register = function() { - // Set the urls matrixService.setConfig({ homeserver: $scope.account.homeserver, @@ -59,7 +69,25 @@ angular.module('RegisterController', ['matrixService']) return; } - matrixService.register($scope.account.desired_user_id, $scope.account.pwd1).then( + if ($scope.account.email) { + $scope.clientSecret = generateClientSecret(); + matrixService.linkEmail($scope.account.email, $scope.clientSecret, 1).then( + function(response) { + $scope.wait_3pid_code = true; + $scope.sid = response.data.sid; + $scope.feedback = ""; + }, + function(response) { + $scope.feedback = "Couldn't request verification email!"; + } + ); + } else { + registerWithMxidAndPassword($scope.account.desired_user_id, $scope.account.pwd1); + } + }; + + $scope.registerWithMxidAndPassword = function(mxid, password, threepidCreds) { + matrixService.register(mxid, password, threepidCreds).then( function(response) { $scope.feedback = "Success"; // Update the current config @@ -74,7 +102,7 @@ angular.module('RegisterController', ['matrixService']) matrixService.saveConfig(); // Update the global scoped used_id var (used in the app header) - $scope.updateHeader(); + $rootScope.updateHeader(); eventStreamService.resume(); @@ -87,15 +115,32 @@ angular.module('RegisterController', ['matrixService']) $location.url("home"); }, function(error) { + console.trace("Registration error: "+error); if (error.data) { if (error.data.errcode === "M_USER_IN_USE") { $scope.feedback = "Username already taken."; + $scope.reenter_username = true; } } else if (error.status === 0) { $scope.feedback = "Unable to talk to the server."; } }); + } + + $scope.verifyToken = function() { + matrixService.authEmail($scope.clientSecret, $scope.sid, $scope.account.threepidtoken).then( + function(response) { + if (!response.data.success) { + $scope.feedback = "Unable to verify code."; + } else { + $scope.registerWithMxidAndPassword($scope.account.desired_user_id, $scope.account.pwd1, [{'sid':$scope.sid, 'clientSecret':$scope.clientSecret, 'idServer': $scope.account.identityServer.split('//')[1]}]); + } + }, + function(error) { + $scope.feedback = "Unable to verify code."; + } + ); }; }]); diff --git a/webclient/login/register.html b/webclient/login/register.html index 81995f1ae0..06a6526b70 100644 --- a/webclient/login/register.html +++ b/webclient/login/register.html @@ -12,26 +12,34 @@ <div style="text-align: center"> <br/> - <input id="email" size="32" type="text" ng-focus="true" ng-model="account.email" placeholder="Email address (optional)" style="display: none"/> - <div class="smallPrint" style="display: none;">Specifying an email address lets other users find you on Matrix more easily,<br/> - and gives you a way to reset your password</div> - <input id="desired_user_id" size="32" type="text" ng-model="account.desired_user_id" placeholder="Matrix ID (e.g. bob)"/> - <br/> - <input id="pwd1" size="32" type="password" ng-model="account.pwd1" placeholder="Type a password"/> - <br/> - <input id="pwd2" size="32" type="password" ng-model="account.pwd2" placeholder="Confirm your password"/> - <br/> - <input id="displayName" size="32" type="text" ng-model="account.displayName" placeholder="Display name (e.g. Bob Obson)"/> - <br/> - <br/> + + <input ng-show="!wait_3pid_code" id="email" size="32" type="text" ng-focus="true" ng-model="account.email" placeholder="Email address (optional)"/> + <div ng-show="!wait_3pid_code" class="smallPrint">Specifying an email address lets other users find you on Matrix more easily,<br/> + and will give you a way to reset your password in the future</div> + <span ng-show="reenter_username">Choose another username:</span> + <input ng-show="!wait_3pid_code || reenter_username" id="desired_user_id" size="32" type="text" ng-model="account.desired_user_id" placeholder="Matrix ID (e.g. bob)"/> + <br ng-show="!wait_3pid_code" /> + <input ng-show="!wait_3pid_code" id="pwd1" size="32" type="password" ng-model="account.pwd1" placeholder="Type a password"/> + <br ng-show="!wait_3pid_code" /> + <input ng-show="!wait_3pid_code" id="pwd2" size="32" type="password" ng-model="account.pwd2" placeholder="Confirm your password"/> + <br ng-show="!wait_3pid_code" /> + <input ng-show="!wait_3pid_code" id="displayName" size="32" type="text" ng-model="account.displayName" placeholder="Display name (e.g. Bob Obson)"/> + <br ng-show="!wait_3pid_code" /> + <br ng-show="!wait_3pid_code" /> - <button ng-click="register()" ng-disabled="!account.desired_user_id || !account.homeserver || !account.pwd1 || !account.pwd2 || account.pwd1 !== account.pwd2">Sign up</button> + <button ng-show="!wait_3pid_code" ng-click="register()" ng-disabled="!account.desired_user_id || !account.homeserver || !account.pwd1 || !account.pwd2 || account.pwd1 !== account.pwd2">Sign up</button> + + <div ng-show="wait_3pid_code"> + <span>Please enter the verification code sent to {{ account.email }}</span><br /> + <input id="threepidtoken" size="32" type="text" ng-focus="true" ng-model="account.threepidtoken" placeholder="Verification Code"/><br /> + <button ng-click="verifyToken()" ng-disabled="!account.threepidtoken">Validate</button> + </div> <br/><br/> </div> <div class="feedback">{{ feedback }} {{ login_error_msg }}</div> - <div id="serverConfig"> + <div id="serverConfig" ng-show="!wait_3pid_code"> <label for="homeserver">Home Server:</label> <input id="homeserver" size="32" type="text" ng-model="account.homeserver" placeholder="URL (e.g. http://matrix.org:8080)"/> <div class="smallPrint">Your home server stores all your conversation and account data.</div> diff --git a/webclient/recents/recents-controller.js b/webclient/recents/recents-controller.js index d7d3bf4053..3209f2cbdf 100644 --- a/webclient/recents/recents-controller.js +++ b/webclient/recents/recents-controller.js @@ -1,5 +1,5 @@ /* - Copyright 2014 matrix.org + 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. diff --git a/webclient/recents/recents-filter.js b/webclient/recents/recents-filter.js index 45653fca96..d80de6fbeb 100644 --- a/webclient/recents/recents-filter.js +++ b/webclient/recents/recents-filter.js @@ -1,5 +1,5 @@ /* - Copyright 2014 matrix.org + 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. diff --git a/webclient/recents/recents.html b/webclient/recents/recents.html index db3b0fb32f..9978e08b13 100644 --- a/webclient/recents/recents.html +++ b/webclient/recents/recents.html @@ -23,8 +23,8 @@ <div ng-hide="room.membership === 'invite'" ng-switch="room.lastMsg.type" > <div ng-switch-when="m.room.member"> {{ room.lastMsg.user_id }} - {{ {"join": "joined", "leave": "left", "invite": "invited"}[room.lastMsg.content.membership] }} - {{ room.lastMsg.content.membership === "invite" ? (room.lastMsg.state_key || '') : '' }} + {{ {"join": "joined", "leave": "left", "invite": "invited", "ban": "banned"}[msg.content.membership] }} + {{ (msg.content.membership === "invite" || msg.content.membership === "ban") ? (msg.state_key || '') : '' }} </div> <div ng-switch-when="m.room.message"> diff --git a/webclient/room/room-controller.js b/webclient/room/room-controller.js index 1f90472c67..c3f72c9d25 100644 --- a/webclient/room/room-controller.js +++ b/webclient/room/room-controller.js @@ -1,5 +1,5 @@ /* -Copyright 2014 matrix.org +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. @@ -85,6 +85,14 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput']) updatePresence(event); } }); + + $scope.$on(eventHandlerService.POWERLEVEL_EVENT, function(ngEvent, event, isLive) { + if (isLive && event.room_id === $scope.room_id) { + for (var user_id in event.content) { + updateUserPowerLevel(user_id); + } + } + }); $scope.memberCount = function() { return Object.keys($scope.members).length; @@ -161,10 +169,13 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput']) var updateMemberList = function(chunk) { if (chunk.room_id != $scope.room_id) return; + // Ignore banned and kicked (leave) people + if ("ban" === chunk.membership || "leave" === chunk.membership) { + return; + } + // set target_user_id to keep things clear var target_user_id = chunk.state_key; - - var now = new Date().getTime(); var isNewMember = !(target_user_id in $scope.members); if (isNewMember) { @@ -174,6 +185,8 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput']) } if ("last_active_ago" in chunk.content) { chunk.last_active_ago = chunk.content.last_active_ago; + $scope.now = new Date().getTime(); + chunk.last_updated = $scope.now; } if ("displayname" in chunk.content) { chunk.displayname = chunk.content.displayname; @@ -181,7 +194,6 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput']) if ("avatar_url" in chunk.content) { chunk.avatar_url = chunk.content.avatar_url; } - chunk.last_updated = now; $scope.members[target_user_id] = chunk; if (target_user_id in $rootScope.presence) { @@ -197,6 +209,8 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput']) } if ("last_active_ago" in chunk.content) { member.last_active_ago = chunk.content.last_active_ago; + $scope.now = new Date().getTime(); + member.last_updated = $scope.now; } } }; @@ -221,6 +235,8 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput']) if ("last_active_ago" in chunk.content) { member.last_active_ago = chunk.content.last_active_ago; + $scope.now = new Date().getTime(); + member.last_updated = $scope.now; } // this may also contain a new display name or avatar url, so check. @@ -237,6 +253,29 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput']) var member = $scope.members[user_id]; if (member) { member.powerLevel = matrixService.getUserPowerLevel($scope.room_id, user_id); + + normaliseMembersPowerLevels(); + } + } + + // Normalise users power levels so that the user with the higher power level + // will have a bar covering 100% of the width of his avatar + var normaliseMembersPowerLevels = function() { + // Find the max power level + var maxPowerLevel = 0; + for (var i in $scope.members) { + var member = $scope.members[i]; + if (member.powerLevel) { + maxPowerLevel = Math.max(maxPowerLevel, member.powerLevel); + } + } + + // Normalized them on a 0..100% scale to be use in css width + if (maxPowerLevel) { + for (var i in $scope.members) { + var member = $scope.members[i]; + member.powerLevelNorm = (member.powerLevel * 100) / maxPowerLevel; + } } } @@ -247,28 +286,93 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput']) $scope.state.sending = true; - // Send the text message var promise; - // FIXME: handle other commands too - if ($scope.textInput.indexOf("/me") === 0) { - promise = matrixService.sendEmoteMessage($scope.room_id, $scope.textInput.substr(4)); - } - else if ($scope.textInput.indexOf("/nick ") === 0) { - // Change user display name - promise = matrixService.setDisplayName($scope.textInput.substr(6)); + + // Check for IRC style commands first + if ($scope.textInput.indexOf("/") === 0) { + var args = $scope.textInput.split(' '); + var cmd = args[0]; + + switch (cmd) { + case "/me": + var emoteMsg = args.slice(1).join(' '); + promise = matrixService.sendEmoteMessage($scope.room_id, emoteMsg); + break; + + case "/nick": + // Change user display name + if (2 === args.length) { + promise = matrixService.setDisplayName(args[1]); + } + break; + + case "/kick": + // Kick a user from the room + if (2 === args.length) { + var user_id = args[1]; + + // Set his state in the room as leave + promise = matrixService.setMembership($scope.room_id, user_id, "leave"); + } + break; + + case "/ban": + // Ban a user from the room + if (2 <= args.length) { + // TODO: The user may have entered the display name + // Need display name -> user_id resolution. Pb: how to manage user with same display names? + var user_id = args[1]; + + // Does the user provide a reason? + if (3 <= args.length) { + var reason = args.slice(2).join(' '); + } + promise = matrixService.ban($scope.room_id, user_id, reason); + } + break; + + case "/unban": + // Unban a user from the room + if (2 === args.length) { + var user_id = args[1]; + + // Reset the user membership to leave to unban him + promise = matrixService.setMembership($scope.room_id, user_id, "leave"); + } + break; + + case "/op": + // Define the power level of a user + if (3 === args.length) { + var user_id = args[1]; + var powerLevel = parseInt(args[2]); + promise = matrixService.setUserPowerLevel($scope.room_id, user_id, powerLevel); + } + break; + + case "/deop": + // Reset the power level of a user + if (2 === args.length) { + var user_id = args[1]; + promise = matrixService.setUserPowerLevel($scope.room_id, user_id, undefined); + } + break; + } } - else { + + if (!promise) { + // Send the text message promise = matrixService.sendTextMessage($scope.room_id, $scope.textInput); } promise.then( function() { - console.log("Sent message"); + console.log("Request successfully sent"); $scope.textInput = ""; $scope.state.sending = false; }, function(error) { - $scope.feedback = "Failed to send: " + error.data.error; + $scope.feedback = "Request failed: " + error.data.error; $scope.state.sending = false; }); }; @@ -332,10 +436,6 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput']) eventHandlerService.waitForInitialSyncCompletion().then( function() { - // Some data has been retrieved from the iniialSync request - // So, the relative time starts here - $scope.now = new Date().getTime(); - var needsToJoin = true; // The room members is available in the data fetched by initialSync @@ -364,7 +464,8 @@ angular.module('RoomController', ['ngSanitize', 'mFileInput']) onInit3(); }, function(reason) { - $scope.feedback = "Can't join room: " + reason; + console.log("Can't join room: " + JSON.stringify(reason)); + $scope.feedback = "You do not have permission to join this room"; }); } else { diff --git a/webclient/room/room-directive.js b/webclient/room/room-directive.js index 1a99a37abb..659bcbc60f 100644 --- a/webclient/room/room-directive.js +++ b/webclient/room/room-directive.js @@ -1,5 +1,5 @@ /* - Copyright 2014 matrix.org + 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. diff --git a/webclient/room/room.html b/webclient/room/room.html index e672b1d7e2..6732a7b3ae 100644 --- a/webclient/room/room.html +++ b/webclient/room/room.html @@ -24,7 +24,7 @@ title="{{ member.id }}" width="80" height="80"/> <img class="userAvatarGradient" src="img/gradient.png" title="{{ member.id }}" width="80" height="24"/> - <div class="userPowerLevel" ng-style="{'width': (10 * member.powerLevel) +'%'}"></div> + <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> </td> <td class="userPresence" ng-class="(member.presence === 'online' ? 'online' : (member.presence === 'unavailable' ? 'unavailable' : '')) + ' ' + (member.membership == 'invite' ? 'invited' : '')"> @@ -48,10 +48,23 @@ </td> <td ng-class="!msg.content.membership ? (msg.content.msgtype === 'm.emote' ? 'emote text' : 'text') : 'membership text'"> <div class="bubble"> - <span ng-show='msg.type === "m.room.member"'> + <span ng-if="'join' === msg.content.membership"> + {{ members[msg.state_key].displayname || msg.state_key }} joined + </span> + <span ng-if="'leave' === msg.content.membership"> + <span ng-if="msg.user_id === msg.state_key"> + {{ members[msg.state_key].displayname || msg.state_key }} left + </span> + <span ng-if="msg.user_id !== msg.state_key"> + {{ members[msg.user_id].displayname || msg.user_id }} + {{ {"join": "kicked", "ban": "unbanned"}[msg.content.prev] }} + {{ members[msg.state_key].displayname || msg.state_key }} + </span> + </span> + <span ng-if="'invite' === msg.content.membership || 'ban' === msg.content.membership"> {{ members[msg.user_id].displayname || msg.user_id }} - {{ {"join": "joined", "leave": "left", "invite": "invited"}[msg.content.membership] }} - {{ msg.content.membership === "invite" ? (msg.state_key || '') : '' }} + {{ {"invite": "invited", "ban": "banned"}[msg.content.membership] }} + {{ members[msg.state_key].displayname || msg.state_key }} </span> <span ng-show='msg.content.msgtype === "m.emote"' ng-bind-html="'* ' + (members[msg.user_id].displayname || msg.user_id) + ' ' + msg.content.body | linky:'_blank'"/> <span ng-show='msg.content.msgtype === "m.text"' ng-bind-html="((msg.content.msgtype === 'm.text') ? msg.content.body : '') | linky:'_blank'"/> diff --git a/webclient/settings/settings-controller.js b/webclient/settings/settings-controller.js index dc680ef075..7a26367a1b 100644 --- a/webclient/settings/settings-controller.js +++ b/webclient/settings/settings-controller.js @@ -1,5 +1,5 @@ /* -Copyright 2014 matrix.org +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. diff --git a/webclient/settings/settings.html b/webclient/settings/settings.html index a69a8de300..b7fd5dfb50 100644 --- a/webclient/settings/settings.html +++ b/webclient/settings/settings.html @@ -12,18 +12,19 @@ <div class="profile-avatar"> <img ng-src="{{ (null !== profile.avatarUrl) ? profile.avatarUrl : 'img/default-profile.png' }}" m-file-input="profile.avatarFile"/> </div> - <div id="user-ids"> - <input size="40" ng-model="profile.displayName" placeholder="Your display name"/> + <div> + <input id="user-displayname-input" size="40" ng-model="profile.displayName" placeholder="Your display name"/> <br/> - <button ng-disabled="(profile.displayName == profileOnServer.displayName) && (profile.avatarUrl == profileOnServer.avatarUrl)" - ng-click="saveProfile()">Save</button> + <button id="user-save-button" + ng-disabled="(profile.displayName === profileOnServer.displayName) && (profile.avatarUrl === profileOnServer.avatarUrl)" + ng-click="saveProfile()">Save changes</button> </div> </form> </div> <br/> - <h3>Linked emails</h3> - <div class="section"> + <h3 style="display: none; ">Linked emails</h3> + <div class="section" style="display: none; "> <form> <input size="40" ng-model="linkedEmails.linkNewEmail" ng-enter="linkEmail(linkedEmails.linkNewEmail)" /> <button ng-disabled="!linkedEmails.linkNewEmail" ng-click="linkEmail(linkedEmails.linkNewEmail)"> @@ -73,6 +74,21 @@ <div>Access token: {{ config.access_token }} </div> </div> <br/> + + <h3>Commands</h3> + <div class="section"> + The following commands are available in the room chat: + <ul> + <li>/nick <display_name>: change your display name</li> + <li>/me <action>: send the action you are doing. /me will be replaced by your display name</li> + <li>/kick <user_id>: kick the user</li> + <li>/ban <user_id> [<reason>]: ban the user</li> + <li>/unban <user_id>: unban the user</li> + <li>/op <user_id> <power_level>: set user power level</li> + <li>/deop <user_id>: reset user power level to the room default value</li> + </ul> + </div> + <br/> {{ feedback }} diff --git a/webclient/user/user-controller.js b/webclient/user/user-controller.js index b5b2d439a2..3940db6683 100644 --- a/webclient/user/user-controller.js +++ b/webclient/user/user-controller.js @@ -1,5 +1,5 @@ /* -Copyright 2014 matrix.org +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. |