summary refs log tree commit diff
path: root/webclient
diff options
context:
space:
mode:
authorMark Haines <mark.haines@matrix.org>2014-11-14 11:16:50 +0000
committerMark Haines <mark.haines@matrix.org>2014-11-14 11:16:50 +0000
commite903c941cb1bed18026f00ed1d3495a8d172f13a (patch)
tree894da7441d913361b70da4cc13cd73ead86d2e67 /webclient
parentRemove unused 'context' variables to appease pyflakes (diff)
parentAdd notification-service unit tests. (diff)
downloadsynapse-e903c941cb1bed18026f00ed1d3495a8d172f13a.tar.xz
Merge branch 'develop' into request_logging
Conflicts:
	setup.py
	synapse/storage/_base.py
	synapse/util/async.py
Diffstat (limited to '')
-rw-r--r--syweb/webclient/CAPTCHA_SETUP (renamed from webclient/CAPTCHA_SETUP)0
-rw-r--r--syweb/webclient/README (renamed from webclient/README)0
-rw-r--r--syweb/webclient/app-controller.js (renamed from webclient/app-controller.js)20
-rw-r--r--syweb/webclient/app-directive.js (renamed from webclient/app-directive.js)43
-rw-r--r--syweb/webclient/app-filter.js (renamed from webclient/app-filter.js)4
-rwxr-xr-xsyweb/webclient/app.css (renamed from webclient/app.css)227
-rw-r--r--syweb/webclient/app.js (renamed from webclient/app.js)8
-rw-r--r--syweb/webclient/bootstrap.css (renamed from webclient/bootstrap.css)0
-rw-r--r--syweb/webclient/components/fileInput/file-input-directive.js (renamed from webclient/components/fileInput/file-input-directive.js)0
-rw-r--r--syweb/webclient/components/fileUpload/file-upload-service.js (renamed from webclient/components/fileUpload/file-upload-service.js)7
-rw-r--r--syweb/webclient/components/matrix/event-stream-service.js (renamed from webclient/components/matrix/event-stream-service.js)19
-rw-r--r--syweb/webclient/components/matrix/matrix-call.js (renamed from webclient/components/matrix/matrix-call.js)186
-rw-r--r--syweb/webclient/components/matrix/matrix-phone-service.js (renamed from webclient/components/matrix/matrix-phone-service.js)2
-rw-r--r--syweb/webclient/components/matrix/matrix-service.js (renamed from webclient/components/matrix/matrix-service.js)140
-rw-r--r--syweb/webclient/components/matrix/presence-service.js (renamed from webclient/components/matrix/presence-service.js)0
-rw-r--r--syweb/webclient/components/utilities/utilities-service.js (renamed from webclient/components/utilities/utilities-service.js)0
-rw-r--r--syweb/webclient/favicon.ico (renamed from webclient/favicon.ico)bin198 -> 198 bytes
-rw-r--r--syweb/webclient/home/home-controller.js (renamed from webclient/home/home-controller.js)22
-rw-r--r--syweb/webclient/home/home.html (renamed from webclient/home/home.html)0
-rw-r--r--syweb/webclient/img/close.png (renamed from webclient/img/close.png)bin397 -> 397 bytes
-rw-r--r--syweb/webclient/img/default-profile.png (renamed from webclient/img/default-profile.png)bin1722 -> 1722 bytes
-rw-r--r--syweb/webclient/img/gradient.png (renamed from webclient/img/gradient.png)bin194 -> 194 bytes
-rw-r--r--syweb/webclient/img/green_phone.png (renamed from webclient/img/green_phone.png)bin434 -> 434 bytes
-rw-r--r--syweb/webclient/img/logo-small.png (renamed from webclient/img/logo-small.png)bin910 -> 910 bytes
-rw-r--r--syweb/webclient/img/logo.png (renamed from webclient/img/logo.png)bin4060 -> 4060 bytes
-rw-r--r--syweb/webclient/index.html (renamed from webclient/index.html)19
-rw-r--r--syweb/webclient/js/angular-animate.js (renamed from webclient/js/angular-animate.js)0
-rw-r--r--syweb/webclient/js/angular-animate.min.js (renamed from webclient/js/angular-animate.min.js)0
-rwxr-xr-xsyweb/webclient/js/angular-mocks.js (renamed from webclient/js/angular-mocks.js)308
-rw-r--r--syweb/webclient/js/angular-route.js (renamed from webclient/js/angular-route.js)0
-rw-r--r--syweb/webclient/js/angular-route.min.js (renamed from webclient/js/angular-route.min.js)0
-rw-r--r--syweb/webclient/js/angular-sanitize.js (renamed from webclient/js/angular-sanitize.js)0
-rw-r--r--syweb/webclient/js/angular-sanitize.min.js (renamed from webclient/js/angular-sanitize.min.js)0
-rw-r--r--syweb/webclient/js/angular.js (renamed from webclient/js/angular.js)0
-rw-r--r--syweb/webclient/js/angular.min.js (renamed from webclient/js/angular.min.js)0
-rwxr-xr-xsyweb/webclient/js/autofill-event.js (renamed from webclient/js/autofill-event.js)0
-rw-r--r--syweb/webclient/js/jquery-1.8.3.min.js (renamed from webclient/js/jquery-1.8.3.min.js)0
-rw-r--r--syweb/webclient/js/ng-infinite-scroll-matrix.js (renamed from webclient/js/ng-infinite-scroll-matrix.js)0
-rw-r--r--syweb/webclient/js/ui-bootstrap-tpls-0.11.2.js (renamed from webclient/js/ui-bootstrap-tpls-0.11.2.js)0
-rw-r--r--syweb/webclient/login/login-controller.js (renamed from webclient/login/login-controller.js)0
-rw-r--r--syweb/webclient/login/login.html (renamed from webclient/login/login.html)0
-rw-r--r--syweb/webclient/login/register-controller.js (renamed from webclient/login/register-controller.js)2
-rw-r--r--syweb/webclient/login/register.html (renamed from webclient/login/register.html)0
-rw-r--r--syweb/webclient/media/busy.mp3 (renamed from webclient/media/busy.mp3)bin24834 -> 24834 bytes
-rw-r--r--syweb/webclient/media/busy.ogg (renamed from webclient/media/busy.ogg)bin13960 -> 13960 bytes
-rw-r--r--syweb/webclient/media/callend.mp3 (renamed from webclient/media/callend.mp3)bin12971 -> 12971 bytes
-rw-r--r--syweb/webclient/media/callend.ogg (renamed from webclient/media/callend.ogg)bin13932 -> 13932 bytes
-rw-r--r--syweb/webclient/media/ring.mp3 (renamed from webclient/media/ring.mp3)bin19662 -> 19662 bytes
-rw-r--r--syweb/webclient/media/ring.ogg (renamed from webclient/media/ring.ogg)bin20636 -> 20636 bytes
-rw-r--r--syweb/webclient/media/ringback.mp3 (renamed from webclient/media/ringback.mp3)bin18398 -> 18398 bytes
-rw-r--r--syweb/webclient/media/ringback.ogg (renamed from webclient/media/ringback.ogg)bin8352 -> 8352 bytes
-rw-r--r--syweb/webclient/mobile.css (renamed from webclient/mobile.css)18
-rw-r--r--syweb/webclient/recents/recents-filter.js (renamed from webclient/recents/recents-filter.js)23
-rw-r--r--syweb/webclient/recents/recents.html (renamed from webclient/recents/recents.html)20
-rw-r--r--syweb/webclient/room/room-controller.js (renamed from webclient/room/room-controller.js)427
-rw-r--r--syweb/webclient/room/room-directive.js (renamed from webclient/room/room-directive.js)109
-rw-r--r--syweb/webclient/settings/settings-controller.js (renamed from webclient/settings/settings-controller.js)0
-rw-r--r--syweb/webclient/settings/settings.html (renamed from webclient/settings/settings.html)0
-rw-r--r--syweb/webclient/test/README (renamed from webclient/test/README)30
-rw-r--r--syweb/webclient/test/e2e/home.spec.js (renamed from webclient/test/e2e/home.spec.js)0
-rw-r--r--syweb/webclient/test/karma.conf.js (renamed from webclient/test/karma.conf.js)45
-rw-r--r--syweb/webclient/test/protractor.conf.js (renamed from webclient/test/protractor.conf.js)0
-rw-r--r--syweb/webclient/test/unit/user-controller.spec.js (renamed from webclient/test/unit/user-controller.spec.js)0
-rw-r--r--syweb/webclient/user/user-controller.js (renamed from webclient/user/user-controller.js)0
-rw-r--r--syweb/webclient/user/user.html (renamed from webclient/user/user.html)0
-rw-r--r--webclient/components/matrix/event-handler-service.js704
-rw-r--r--webclient/components/matrix/matrix-filter.js146
-rw-r--r--webclient/img/red_phone.pngbin378 -> 0 bytes
-rw-r--r--webclient/recents/recents-controller.js31
-rw-r--r--webclient/room/room.html232
70 files changed, 939 insertions, 1853 deletions
diff --git a/webclient/CAPTCHA_SETUP b/syweb/webclient/CAPTCHA_SETUP

index ebc8a5f3b0..ebc8a5f3b0 100644 --- a/webclient/CAPTCHA_SETUP +++ b/syweb/webclient/CAPTCHA_SETUP
diff --git a/webclient/README b/syweb/webclient/README
index ef79b25708..ef79b25708 100644 --- a/webclient/README +++ b/syweb/webclient/README
diff --git a/webclient/app-controller.js b/syweb/webclient/app-controller.js
index e4b7cd286f..582c075e3d 100644 --- a/webclient/app-controller.js +++ b/syweb/webclient/app-controller.js
@@ -21,18 +21,12 @@ limitations under the License. 'use strict'; angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'eventStreamService']) -.controller('MatrixWebClientController', ['$scope', '$location', '$rootScope', '$timeout', '$animate', 'matrixService', 'mPresence', 'eventStreamService', 'eventHandlerService', 'matrixPhoneService', - function($scope, $location, $rootScope, $timeout, $animate, matrixService, mPresence, eventStreamService, eventHandlerService, matrixPhoneService) { +.controller('MatrixWebClientController', ['$scope', '$location', '$rootScope', '$timeout', 'matrixService', 'mPresence', 'eventStreamService', 'eventHandlerService', 'matrixPhoneService', 'modelService', + function($scope, $location, $rootScope, $timeout, matrixService, mPresence, eventStreamService, eventHandlerService, matrixPhoneService, modelService) { // Check current URL to avoid to display the logout button on the login page $scope.location = $location.path(); - // disable nganimate for the local and remote video elements because ngAnimate appears - // to be buggy and leaves animation classes on the video elements causing them to show - // when they should not (their animations are pure CSS3) - $animate.enabled(false, angular.element('#localVideo')); - $animate.enabled(false, angular.element('#remoteVideo')); - // Update the location state when the ng location changed $rootScope.$on('$routeChangeSuccess', function (event, current, previous) { $scope.location = $location.path(); @@ -112,12 +106,12 @@ angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'even if (!$rootScope.currentCall) { // This causes the still frame to be flushed out of the video elements, // avoiding a flash of the last frame of the previous call when starting the next - angular.element('#localVideo')[0].load(); - angular.element('#remoteVideo')[0].load(); + if (angular.element('#localVideo')[0].load) angular.element('#localVideo')[0].load(); + if (angular.element('#remoteVideo')[0].load) angular.element('#remoteVideo')[0].load(); return; } - var roomMembers = angular.copy($rootScope.events.rooms[$rootScope.currentCall.room_id].members); + var roomMembers = angular.copy(modelService.getRoom($rootScope.currentCall.room_id).current_room_state.members); delete roomMembers[matrixService.config().user_id]; $rootScope.currentCall.user_id = Object.keys(roomMembers)[0]; @@ -187,8 +181,8 @@ angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'even } call.onError = $scope.onCallError; call.onHangup = $scope.onCallHangup; - call.localVideoElement = angular.element('#localVideo')[0]; - call.remoteVideoElement = angular.element('#remoteVideo')[0]; + call.localVideoSelector = '#localVideo'; + call.remoteVideoSelector = '#remoteVideo'; $rootScope.currentCall = call; }); diff --git a/webclient/app-directive.js b/syweb/webclient/app-directive.js
index 75283598ab..c1ba0af3a9 100644 --- a/webclient/app-directive.js +++ b/syweb/webclient/app-directive.js
@@ -40,4 +40,45 @@ angular.module('matrixWebClient') } } }; -}]); \ No newline at end of file +}]) +.directive('asjson', function() { + return { + restrict: 'A', + require: 'ngModel', + link: function (scope, element, attrs, ngModelCtrl) { + function isValidJson(model) { + var flag = true; + try { + angular.fromJson(model); + } catch (err) { + flag = false; + } + return flag; + }; + + function string2JSON(text) { + try { + var j = angular.fromJson(text); + ngModelCtrl.$setValidity('json', true); + return j; + } catch (err) { + //returning undefined results in a parser error as of angular-1.3-rc.0, and will not go through $validators + //return undefined + ngModelCtrl.$setValidity('json', false); + return text; + } + }; + + function JSON2String(object) { + return angular.toJson(object, true); + }; + + //$validators is an object, where key is the error + //ngModelCtrl.$validators.json = isValidJson; + + //array pipelines + ngModelCtrl.$parsers.push(string2JSON); + ngModelCtrl.$formatters.push(JSON2String); + } + } +}); diff --git a/webclient/app-filter.js b/syweb/webclient/app-filter.js
index 39ea1d637d..65da0d312d 100644 --- a/webclient/app-filter.js +++ b/syweb/webclient/app-filter.js
@@ -29,10 +29,10 @@ angular.module('matrixWebClient') return s + "s"; } if (t < 60 * 60) { - return m + "m "; // + s + "s"; + return m + "m"; // + s + "s"; } if (t < 24 * 60 * 60) { - return h + "h "; // + m + "m"; + return h + "h"; // + m + "m"; } return d + "d "; // + h + "h"; }; diff --git a/webclient/app.css b/syweb/webclient/app.css
index 20a13aad81..25f7208a11 100755 --- a/webclient/app.css +++ b/syweb/webclient/app.css
@@ -66,18 +66,15 @@ textarea, input { margin-left: 4px; margin-right: 4px; margin-top: 8px; + transition: transform linear 0.5s; + transition: -webkit-transform linear 0.5s; } -#callEndedIcon { - transition:all linear 0.5s; -} - -#callEndedIcon { +.callIcon.ended { transform: rotateZ(45deg); -} - -#callEndedIcon.ng-hide { - transform: rotateZ(0deg); + -webkit-transform: rotateZ(45deg); + filter: hue-rotate(-90deg); + -webkit-filter: hue-rotate(-90deg); } #callPeerImage { @@ -136,17 +133,17 @@ textarea, input { transition: left linear 500ms, top linear 500ms, width linear 500ms, height linear 500ms; } -#localVideo.mini { +.mini #localVideo { top: 0px; left: 130px; } -#localVideo.large { +.large #localVideo { top: 70px; left: 20px; } -#localVideo.ended { +.ended #localVideo { -webkit-filter: grayscale(1); filter: grayscale(1); } @@ -157,19 +154,19 @@ textarea, input { transition: left linear 500ms, top linear 500ms, width linear 500ms, height linear 500ms; } -#remoteVideo.mini { +.mini #remoteVideo { left: 260px; top: 0px; width: 128px; } -#remoteVideo.large { +.large #remoteVideo { left: 0px; top: 50px; width: 100%; } -#remoteVideo.ended { +.ended #remoteVideo { -webkit-filter: grayscale(1); filter: grayscale(1); } @@ -318,7 +315,7 @@ textarea, input { position: absolute; bottom: 0px; width: 100%; - height: 100px; + height: 70px; background-color: #f8f8f8; border-top: #aaa 1px solid; } @@ -326,7 +323,9 @@ textarea, input { #controls { max-width: 1280px; padding: 12px; + padding-right: 42px; margin: auto; + position: relative; } #buttonsCell { @@ -343,7 +342,19 @@ textarea, input { #mainInput { width: 100%; - resize: none; + padding: 5px; + resize: vertical; +} + +#attachButton { + position: absolute; + cursor: pointer; + margin-top: 3px; + right: 0px; + background: url('img/attach.png'); + width: 25px; + height: 25px; + border: 0px; } .blink { @@ -415,18 +426,72 @@ textarea, input { .roomHeaderInfo { text-align: right; float: right; - margin-top: 15px; + margin-top: 0px; + margin-right: 30px; +} + +/*** Room Info Dialog ***/ + +.room-info { + border-collapse: collapse; + width: 100%; +} + +.room-info-event { + border-bottom: 1pt solid black; +} + +.room-info-event-meta { + padding-top: 1em; + padding-bottom: 1em; +} + +.room-info-event-content { + padding-top: 1em; + padding-bottom: 1em; +} + +.monospace { + font-family: monospace; +} + +.redact-button { + float: left +} + +.room-info-textarea-content { + height: auto; + width: 100%; + resize: vertical; +} + +/*** Control Buttons ***/ +#controlButtons { + float: right; + margin-right: -4px; + padding-bottom: 6px; +} + +.controlButton { + cursor: pointer; + border: 0px; + width: 30px; + height: 30px; + margin-left: 3px; + margin-right: 3px; } /*** Participant list ***/ #usersTableWrapper { float: right; - width: 120px; + clear: right; + width: 101px; height: 100%; overflow-y: auto; } +/* #usersTable { width: 100%; border-collapse: collapse; @@ -442,36 +507,66 @@ textarea, input { position: relative; background-color: #000; } +*/ -.userAvatar .userAvatarImage { - position: absolute; - top: 0px; +.userAvatar { +} + +.userAvatarFrame { + border-radius: 46px; + width: 80px; + margin: auto; + position: relative; + border: 3px solid #aaa; + background-color: #aaa; +} + +.userAvatarImage { + border-radius: 40px; + text-align: center; object-fit: cover; - width: 100%; + display: block; } +/* .userAvatar .userAvatarGradient { position: absolute; bottom: 20px; width: 100%; } +*/ -.userAvatar .userName { - position: absolute; - color: #fff; - margin: 2px; - bottom: 0px; +.userName { + margin-top: 3px; + margin-bottom: 6px; + text-align: center; font-size: 12px; - word-break: break-all; + word-wrap: break-word; } -.userAvatar .userPowerLevel { +.userPowerLevel { position: absolute; + bottom: -1px; + height: 1px; + background-color: #f00; +} + +.userPowerLevelBar { + display: inline; + position: absolute; + width: 2px; + height: 10px; +/* border: 1px solid #000; +*/ background-color: #aaa; +} + +.userPowerLevelMeter { + position: relative; bottom: 0px; - height: 2px; background-color: #f00; } +/* .userPresence { text-align: center; font-size: 12px; @@ -479,12 +574,15 @@ textarea, input { background-color: #aaa; border-bottom: 1px #ddd solid; } +*/ .online { + border-color: #38AF00; background-color: #38AF00; } .unavailable { + border-color: #FFCC00; background-color: #FFCC00; } @@ -507,18 +605,21 @@ textarea, input { #messageTable td { padding: 0px; +/* border: 1px solid #888; */ } .leftBlock { - width: 14em; + width: 7em; word-wrap: break-word; vertical-align: top; background-color: #fff; - color: #888; + color: #aaa; font-weight: medium; font-size: 12px; text-align: right; +/* border-top: 1px #ddd solid; +*/ } .rightBlock { @@ -529,13 +630,24 @@ textarea, input { } .sender, .timestamp { - padding-right: 1em; - padding-left: 1em; - padding-top: 3px; +/* padding-top: 3px; +*/} + +.timestamp { + font-size: 10px; + color: #ccc; + height: 13px; + margin-top: 4px; + transition-property: opacity; + transition-duration: 0.3s; } .sender { - margin-bottom: -3px; + font-size: 12px; +/* + margin-top: 5px; + margin-bottom: -9px; +*/ } .avatar { @@ -546,7 +658,11 @@ textarea, input { } .avatarImage { + position: relative; + top: 5px; object-fit: cover; + border-radius: 32px; + margin-top: 4px; } .emote { @@ -560,6 +676,7 @@ textarea, input { } .image { + border: 1px solid #888; display: block; max-width:320px; max-height:320px; @@ -572,19 +689,23 @@ textarea, input { } .bubble { +/* background-color: #eee; border: 1px solid #d8d8d8; - display: inline-block; margin-bottom: -1px; - max-width: 90%; - font-size: 14px; - word-wrap: break-word; padding-top: 7px; padding-bottom: 5px; + -webkit-text-size-adjust:100% + vertical-align: middle; +*/ + display: inline-block; + max-width: 90%; padding-left: 1em; padding-right: 1em; - vertical-align: middle; - -webkit-text-size-adjust:100% + padding-top: 2px; + padding-bottom: 2px; + font-size: 14px; + word-wrap: break-word; } .bubble img { @@ -592,8 +713,8 @@ textarea, input { max-height: auto; } -.differentUser td { - padding-bottom: 5px ! important; +.differentUser .msg { + padding-top: 14px ! important; } .mine { @@ -604,13 +725,15 @@ textarea, input { .text.membership .bubble, .mine .text.emote .bubble, .mine .text.membership .bubble - { +{ background-color: transparent ! important; border: 0px ! important; } .mine .text .bubble { +/* background-color: #f8f8ff ! important; +*/ text-align: left ! important; } @@ -670,6 +793,8 @@ textarea, input { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; + padding-left: 0.5em; + padding-right: 0.5em; } .recentsRoom { @@ -684,6 +809,14 @@ textarea, input { background-color: #eee; } +.recentsRoomUnread { + background-color: #fee; +} + +.recentsRoomBing { + background-color: #eef; +} + .recentsRoomName { font-size: 16px; padding-top: 7px; @@ -720,7 +853,7 @@ textarea, input { padding-right: 10px; margin-right: 10px; height: 100%; - border-right: 1px solid #ddd; +/* border-right: 1px solid #ddd; */ overflow-y: auto; } diff --git a/webclient/app.js b/syweb/webclient/app.js
index 099e2170a0..9e5b85820d 100644 --- a/webclient/app.js +++ b/syweb/webclient/app.js
@@ -16,7 +16,6 @@ limitations under the License. var matrixWebClient = angular.module('matrixWebClient', [ 'ngRoute', - 'ngAnimate', 'MatrixWebClientController', 'LoginController', 'RegisterController', @@ -30,8 +29,13 @@ var matrixWebClient = angular.module('matrixWebClient', [ 'MatrixCall', 'eventStreamService', 'eventHandlerService', + 'notificationService', + 'recentsService', + 'modelService', + 'commandsService', 'infinite-scroll', - 'ui.bootstrap' + 'ui.bootstrap', + 'monospaced.elastic' ]); matrixWebClient.config(['$routeProvider', '$provide', '$httpProvider', diff --git a/webclient/bootstrap.css b/syweb/webclient/bootstrap.css
index 7ebcb2a007..7ebcb2a007 100644 --- a/webclient/bootstrap.css +++ b/syweb/webclient/bootstrap.css
diff --git a/webclient/components/fileInput/file-input-directive.js b/syweb/webclient/components/fileInput/file-input-directive.js
index 9c849a140f..9c849a140f 100644 --- a/webclient/components/fileInput/file-input-directive.js +++ b/syweb/webclient/components/fileInput/file-input-directive.js
diff --git a/webclient/components/fileUpload/file-upload-service.js b/syweb/webclient/components/fileUpload/file-upload-service.js
index e0f67b2c6c..b544e29509 100644 --- a/webclient/components/fileUpload/file-upload-service.js +++ b/syweb/webclient/components/fileUpload/file-upload-service.js
@@ -64,7 +64,8 @@ angular.module('mFileUpload', ['matrixService', 'mUtilities']) var imageMessage = { msgtype: "m.image", url: undefined, - body: { + body: "Image", + info: { size: undefined, w: undefined, h: undefined, @@ -90,7 +91,7 @@ angular.module('mFileUpload', ['matrixService', 'mUtilities']) function(url) { // Update message metadata imageMessage.url = url; - imageMessage.body = { + imageMessage.info = { size: imageFile.size, w: size.width, h: size.height, @@ -101,7 +102,7 @@ angular.module('mFileUpload', ['matrixService', 'mUtilities']) // reuse the original image info for thumbnail data if (!imageMessage.thumbnail_url) { imageMessage.thumbnail_url = imageMessage.url; - imageMessage.thumbnail_info = imageMessage.body; + imageMessage.thumbnail_info = imageMessage.info; } // We are done diff --git a/webclient/components/matrix/event-stream-service.js b/syweb/webclient/components/matrix/event-stream-service.js
index 05469a3ded..c03f0b953b 100644 --- a/webclient/components/matrix/event-stream-service.js +++ b/syweb/webclient/components/matrix/event-stream-service.js
@@ -109,25 +109,6 @@ angular.module('eventStreamService', []) // without requiring to make an additional request matrixService.initialSync(30, false).then( function(response) { - var rooms = response.data.rooms; - for (var i = 0; i < rooms.length; ++i) { - var room = rooms[i]; - - eventHandlerService.initRoom(room); - - if ("messages" in room) { - eventHandlerService.handleRoomMessages(room.room_id, room.messages, false); - } - - if ("state" in room) { - eventHandlerService.handleEvents(room.state, false, true); - } - } - - var presence = response.data.presence; - eventHandlerService.handleEvents(presence, false); - - // Initial sync is done eventHandlerService.handleInitialSyncDone(response); // Start event streaming from that point diff --git a/webclient/components/matrix/matrix-call.js b/syweb/webclient/components/matrix/matrix-call.js
index 3e8811e5fc..56431817d9 100644 --- a/webclient/components/matrix/matrix-call.js +++ b/syweb/webclient/components/matrix/matrix-call.js
@@ -35,19 +35,16 @@ var forAllTracksOnStream = function(s, f) { forAllAudioTracksOnStream(s, f); } -navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia; -window.RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection; // but not mozRTCPeerConnection because its interface is not compatible -window.RTCSessionDescription = window.RTCSessionDescription || window.webkitRTCSessionDescription || window.mozRTCSessionDescription; -window.RTCIceCandidate = window.RTCIceCandidate || window.webkitRTCIceCandidate || window.mozRTCIceCandidate; - -// Returns true if the browser supports all required features to make WebRTC call -var isWebRTCSupported = function () { - return !!(navigator.getUserMedia || window.RTCPeerConnection || window.RTCSessionDescription || window.RTCIceCandidate); -}; - angular.module('MatrixCall', []) -.factory('MatrixCall', ['matrixService', 'matrixPhoneService', '$rootScope', '$timeout', function MatrixCallFactory(matrixService, matrixPhoneService, $rootScope, $timeout) { - $rootScope.isWebRTCSupported = isWebRTCSupported(); +.factory('MatrixCall', ['matrixService', 'matrixPhoneService', 'modelService', '$rootScope', '$timeout', function MatrixCallFactory(matrixService, matrixPhoneService, modelService, $rootScope, $timeout) { + $rootScope.isWebRTCSupported = function () { + navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia; + window.RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection; // but not mozRTCPeerConnection because its interface is not compatible + window.RTCSessionDescription = window.RTCSessionDescription || window.webkitRTCSessionDescription || window.mozRTCSessionDescription; + window.RTCIceCandidate = window.RTCIceCandidate || window.webkitRTCIceCandidate || window.mozRTCIceCandidate; + + return !!(navigator.getUserMedia || window.RTCPeerConnection || window.RTCSessionDescription || window.RTCIceCandidate); + }; var MatrixCall = function(room_id) { this.room_id = room_id; @@ -60,7 +57,7 @@ angular.module('MatrixCall', []) this.candidateSendTries = 0; var self = this; - $rootScope.$watch(this.remoteVideoElement, function (oldValue, newValue) { + $rootScope.$watch(this.getRemoteVideoElement(), function (oldValue, newValue) { self.tryPlayRemoteStream(); }); @@ -85,7 +82,7 @@ angular.module('MatrixCall', []) }); } - // FIXME: we should prevent any class from being placed or accepted before this has finished + // FIXME: we should prevent any calls from being placed or accepted before this has finished MatrixCall.getTurnServer(); MatrixCall.CALL_TIMEOUT = 60000; @@ -95,7 +92,8 @@ angular.module('MatrixCall', []) var pc; if (window.mozRTCPeerConnection) { var iceServers = []; - if (MatrixCall.turnServer) { + // https://github.com/EricssonResearch/openwebrtc/issues/85 + if (MatrixCall.turnServer /*&& !this.isOpenWebRTC()*/) { if (MatrixCall.turnServer.uris) { for (var i = 0; i < MatrixCall.turnServer.uris.length; i++) { iceServers.push({ @@ -113,7 +111,8 @@ angular.module('MatrixCall', []) pc = new window.mozRTCPeerConnection({"iceServers":iceServers}); } else { var iceServers = []; - if (MatrixCall.turnServer) { + // https://github.com/EricssonResearch/openwebrtc/issues/85 + if (MatrixCall.turnServer && !this.isOpenWebRTC()) { if (MatrixCall.turnServer.uris) { iceServers.push({ 'urls': MatrixCall.turnServer.uris, @@ -178,7 +177,8 @@ angular.module('MatrixCall', []) this.state = 'ringing'; this.direction = 'inbound'; - if (window.mozRTCPeerConnection) { + // This also applied to the Safari OpenWebRTC extension so let's just do this all the time at least for now + //if (window.mozRTCPeerConnection) { // firefox's RTCPeerConnection doesn't add streams until it starts getting media on them // so we need to figure out whether a video channel has been offered by ourselves. if (this.msg.offer.sdp.indexOf('m=video') > -1) { @@ -186,7 +186,7 @@ angular.module('MatrixCall', []) } else { this.type = 'voice'; } - } + //} var self = this; $timeout(function() { @@ -213,8 +213,8 @@ angular.module('MatrixCall', []) var self = this; - var roomMembers = $rootScope.events.rooms[this.room_id].members; - if (roomMembers[matrixService.config().user_id].membership != 'join') { + var roomMembers = modelService.getRoom(this.room_id).current_room_state.members; + if (roomMembers[matrixService.config().user_id].event.content.membership != 'join') { console.log("We need to join the room before we can accept this call"); matrixService.join(this.room_id).then(function() { self.answer(); @@ -254,8 +254,8 @@ angular.module('MatrixCall', []) // pausing now keeps the last frame (ish) of the video call in the video element // rather than it just turning black straight away - if (this.remoteVideoElement) this.remoteVideoElement.pause(); - if (this.localVideoElement) this.localVideoElement.pause(); + if (this.getRemoteVideoElement() && this.getRemoteVideoElement().pause) this.getRemoteVideoElement().pause(); + if (this.getLocalVideoElement() && this.getLocalVideoElement().pause) this.getLocalVideoElement().pause(); this.stopAllMedia(); if (this.peerConn) this.peerConn.close(); @@ -280,11 +280,18 @@ angular.module('MatrixCall', []) } if (this.state == 'ended') return; - if (this.localVideoElement && this.type == 'video') { + var videoEl = this.getLocalVideoElement(); + + if (videoEl && this.type == 'video') { var vidTrack = stream.getVideoTracks()[0]; - this.localVideoElement.src = URL.createObjectURL(stream); - this.localVideoElement.muted = true; - this.localVideoElement.play(); + videoEl.autoplay = true; + videoEl.src = URL.createObjectURL(stream); + videoEl.muted = true; + var self = this; + $timeout(function() { + var vel = self.getLocalVideoElement(); + if (vel.play) vel.play(); + }); } this.localAVStream = stream; @@ -308,11 +315,18 @@ angular.module('MatrixCall', []) MatrixCall.prototype.gotUserMediaForAnswer = function(stream) { if (this.state == 'ended') return; - if (this.localVideoElement && this.type == 'video') { + var localVidEl = this.getLocalVideoElement(); + + if (localVidEl && this.type == 'video') { + localVidEl.autoplay = true; var vidTrack = stream.getVideoTracks()[0]; - this.localVideoElement.src = URL.createObjectURL(stream); - this.localVideoElement.muted = true; - this.localVideoElement.play(); + localVidEl.src = URL.createObjectURL(stream); + localVidEl.muted = true; + var self = this; + $timeout(function() { + var vel = self.getLocalVideoElement(); + if (vel.play) vel.play(); + }); } this.localAVStream = stream; @@ -341,11 +355,11 @@ angular.module('MatrixCall', []) } MatrixCall.prototype.gotRemoteIceCandidate = function(cand) { - console.log("Got remote ICE "+cand.sdpMid+" candidate: "+cand.candidate); if (this.state == 'ended') { - console.log("Ignoring remote ICE candidate because call has ended"); + //console.log("Ignoring remote ICE candidate because call has ended"); return; } + console.log("Got remote ICE "+cand.sdpMid+" candidate: "+cand.candidate); this.peerConn.addIceCandidate(new RTCIceCandidate(cand), function() {}, function(e) {}); }; @@ -365,41 +379,46 @@ angular.module('MatrixCall', []) return; } - this.peerConn.setLocalDescription(description); - - var content = { - version: 0, - call_id: this.call_id, - offer: description, - lifetime: MatrixCall.CALL_TIMEOUT - }; - this.sendEventWithRetry('m.call.invite', content); - var self = this; - $timeout(function() { - if (self.state == 'invite_sent') { - self.hangup('invite_timeout'); - } - }, MatrixCall.CALL_TIMEOUT); + this.peerConn.setLocalDescription(description, function() { + var content = { + version: 0, + call_id: self.call_id, + // OpenWebRTC appears to add extra stuff (like the DTLS fingerprint) to the description + // when setting it on the peerconnection. According to the spec it should only add ICE + // candidates. Any ICE candidates that have already been generated at this point will + // probably be sent both in the offer and separately. Ho hum. + offer: self.peerConn.localDescription, + lifetime: MatrixCall.CALL_TIMEOUT + }; + self.sendEventWithRetry('m.call.invite', content); + + $timeout(function() { + if (self.state == 'invite_sent') { + self.hangup('invite_timeout'); + } + }, MatrixCall.CALL_TIMEOUT); - $rootScope.$apply(function() { - self.state = 'invite_sent'; - }); + $rootScope.$apply(function() { + self.state = 'invite_sent'; + }); + }, function() { console.log("Error setting local description!"); }); }; MatrixCall.prototype.createdAnswer = function(description) { console.log("Created answer: "+description); - this.peerConn.setLocalDescription(description); - var content = { - version: 0, - call_id: this.call_id, - answer: description - }; - this.sendEventWithRetry('m.call.answer', content); var self = this; - $rootScope.$apply(function() { - self.state = 'connecting'; - }); + this.peerConn.setLocalDescription(description, function() { + var content = { + version: 0, + call_id: self.call_id, + answer: self.peerConn.localDescription + }; + self.sendEventWithRetry('m.call.answer', content); + $rootScope.$apply(function() { + self.state = 'connecting'; + }); + }, function() { console.log("Error setting local description!"); } ); }; MatrixCall.prototype.getLocalOfferFailed = function(error) { @@ -467,10 +486,17 @@ angular.module('MatrixCall', []) }; MatrixCall.prototype.tryPlayRemoteStream = function(event) { - if (this.remoteVideoElement && this.remoteAVStream) { - var player = this.remoteVideoElement; + if (this.getRemoteVideoElement() && this.remoteAVStream) { + var player = this.getRemoteVideoElement(); + player.autoplay = true; player.src = URL.createObjectURL(this.remoteAVStream); - player.play(); + var self = this; + $timeout(function() { + var vel = self.getRemoteVideoElement(); + if (vel.play) vel.play(); + // OpenWebRTC does not support oniceconnectionstatechange yet + if (self.isOpenWebRTC()) self.state = 'connected'; + }); } }; @@ -502,8 +528,8 @@ angular.module('MatrixCall', []) MatrixCall.prototype.onHangupReceived = function(msg) { console.log("Hangup received"); - if (this.remoteVideoElement) this.remoteVideoElement.pause(); - if (this.localVideoElement) this.localVideoElement.pause(); + if (this.getRemoteVideoElement() && this.getRemoteVideoElement().pause) this.getRemoteVideoElement().pause(); + if (this.getLocalVideoElement() && this.getLocalVideoElement().pause) this.getLocalVideoElement().pause(); this.state = 'ended'; this.hangupParty = 'remote'; this.hangupReason = msg.reason; @@ -526,8 +552,8 @@ angular.module('MatrixCall', []) newCall.gotUserMediaForAnswer(this.localAVStream); delete(this.localAVStream); } - newCall.localVideoElement = this.localVideoElement; - newCall.remoteVideoElement = this.remoteVideoElement; + newCall.localVideoSelector = this.localVideoSelector; + newCall.remoteVideoSelector = this.remoteVideoSelector; this.successor = newCall; this.hangup(true); }; @@ -603,5 +629,31 @@ angular.module('MatrixCall', []) }, delayMs); }; + MatrixCall.prototype.getLocalVideoElement = function() { + if (this.localVideoSelector) { + var t = angular.element(this.localVideoSelector); + if (t.length) return t[0]; + } + return null; + }; + + MatrixCall.prototype.getRemoteVideoElement = function() { + if (this.remoteVideoSelector) { + var t = angular.element(this.remoteVideoSelector); + if (t.length) return t[0]; + } + return null; + }; + + MatrixCall.prototype.isOpenWebRTC = function() { + var scripts = angular.element('script'); + for (var i = 0; i < scripts.length; i++) { + if (scripts[i].src.indexOf("owr.js") > -1) { + return true; + } + } + return false; + }; + return MatrixCall; }]); diff --git a/webclient/components/matrix/matrix-phone-service.js b/syweb/webclient/components/matrix/matrix-phone-service.js
index 06465ed821..55dbbf522e 100644 --- a/webclient/components/matrix/matrix-phone-service.js +++ b/syweb/webclient/components/matrix/matrix-phone-service.js
@@ -60,7 +60,7 @@ angular.module('matrixPhoneService', []) var MatrixCall = $injector.get('MatrixCall'); var call = new MatrixCall(event.room_id); - if (!isWebRTCSupported()) { + if (!$rootScope.isWebRTCSupported()) { console.log("Incoming call ID "+msg.call_id+" but this browser doesn't support WebRTC"); // don't hang up the call: there could be other clients connected that do support WebRTC and declining the // the call on their behalf would be really annoying. diff --git a/webclient/components/matrix/matrix-service.js b/syweb/webclient/components/matrix/matrix-service.js
index 1840cf46c0..cfe8691f85 100644 --- a/webclient/components/matrix/matrix-service.js +++ b/syweb/webclient/components/matrix/matrix-service.js
@@ -23,7 +23,7 @@ This serves to isolate the caller from changes to the underlying url paths, as well as attach common params (e.g. access_token) to requests. */ angular.module('matrixService', []) -.factory('matrixService', ['$http', '$q', '$rootScope', function($http, $q, $rootScope) { +.factory('matrixService', ['$http', '$q', function($http, $q) { /* * Permanent storage of user information @@ -36,13 +36,9 @@ angular.module('matrixService', []) */ var config; - var roomIdToAlias = {}; - var aliasToRoomId = {}; - // Current version of permanent storage var configVersion = 0; var prefixPath = "/_matrix/client/api/v1"; - var MAPPING_PREFIX = "alias_for_"; var doRequest = function(method, path, params, data, $httpParams) { if (!config) { @@ -267,7 +263,7 @@ angular.module('matrixService', []) // get room state for a specific room roomState: function(room_id) { - var path = "/rooms/" + room_id + "/state"; + var path = "/rooms/" + encodeURIComponent(room_id) + "/state"; return doRequest("GET", path); }, @@ -375,9 +371,11 @@ angular.module('matrixService', []) sendStateEvent: function(room_id, eventType, content, state_key) { - var path = "/rooms/$room_id/state/"+eventType; + var path = "/rooms/$room_id/state/"+ eventType; + // TODO: uncomment this when matrix.org is updated, else all state events 500. + // var path = "/rooms/$room_id/state/"+ encodeURIComponent(eventType); if (state_key !== undefined) { - path += "/" + state_key; + path += "/" + encodeURIComponent(state_key); } room_id = encodeURIComponent(room_id); path = path.replace("$room_id", room_id); @@ -422,7 +420,8 @@ angular.module('matrixService', []) var content = { msgtype: "m.image", url: image_url, - body: image_body + info: image_body, + body: "Image" }; return this.sendMessage(room_id, msg_id, content); @@ -440,7 +439,8 @@ angular.module('matrixService', []) redactEvent: function(room_id, event_id) { var path = "/rooms/$room_id/redact/$event_id"; - path = path.replace("$room_id", room_id); + path = path.replace("$room_id", encodeURIComponent(room_id)); + // TODO: encodeURIComponent when HS updated. path = path.replace("$event_id", event_id); var content = {}; return doRequest("POST", path, undefined, content); @@ -458,7 +458,7 @@ angular.module('matrixService', []) paginateBackMessages: function(room_id, from_token, limit) { var path = "/rooms/$room_id/messages"; - path = path.replace("$room_id", room_id); + path = path.replace("$room_id", encodeURIComponent(room_id)); var params = { from: from_token, limit: limit, @@ -506,12 +506,12 @@ angular.module('matrixService', []) setProfileInfo: function(data, info_segment) { var path = "/profile/$user/" + info_segment; - path = path.replace("$user", config.user_id); + path = path.replace("$user", encodeURIComponent(config.user_id)); return doRequest("PUT", path, undefined, data); }, getProfileInfo: function(userId, info_segment) { - var path = "/profile/"+userId + var path = "/profile/"+encodeURIComponent(userId); if (info_segment) path += '/' + info_segment; return doRequest("GET", path); }, @@ -630,7 +630,7 @@ angular.module('matrixService', []) // Set the logged in user presence state setUserPresence: function(presence) { var path = "/presence/$user_id/status"; - path = path.replace("$user_id", config.user_id); + path = path.replace("$user_id", encodeURIComponent(config.user_id)); return doRequest("PUT", path, undefined, { presence: presence }); @@ -667,114 +667,30 @@ angular.module('matrixService', []) config.version = configVersion; localStorage.setItem("config", JSON.stringify(config)); }, - - - /****** Room aliases management ******/ - - /** - * Get the room_alias & room_display_name which are computed from data - * already retrieved from the server. - * @param {Room object} room one element of the array returned by the response - * of rooms() and publicRooms() - * @returns {Object} {room_alias: "...", room_display_name: "..."} - */ - getRoomAliasAndDisplayName: function(room) { - var result = { - room_alias: undefined, - room_display_name: undefined - }; - var alias = this.getRoomIdToAliasMapping(room.room_id); - if (alias) { - // use the existing alias from storage - result.room_alias = alias; - result.room_display_name = alias; - } - // XXX: this only lets us learn aliases from our local HS - we should - // make the client stop returning this if we can trust m.room.aliases state events - else if (room.aliases && room.aliases[0]) { - // save the mapping - // TODO: select the smarter alias from the array - this.createRoomIdToAliasMapping(room.room_id, room.aliases[0]); - result.room_display_name = room.aliases[0]; - result.room_alias = room.aliases[0]; - } - else if (room.membership === "invite" && "inviter" in room) { - result.room_display_name = room.inviter + "'s room"; - } - else { - // last resort use the room id - result.room_display_name = room.room_id; - } - return result; - }, - - createRoomIdToAliasMapping: function(roomId, alias) { - roomIdToAlias[roomId] = alias; - aliasToRoomId[alias] = roomId; - }, - - getRoomIdToAliasMapping: function(roomId) { - var alias = roomIdToAlias[roomId]; - //console.log("looking for alias for " + roomId + "; found: " + alias); - return alias; - }, - - getAliasToRoomIdMapping: function(alias) { - var roomId = aliasToRoomId[alias]; - //console.log("looking for roomId for " + alias + "; found: " + roomId); - return roomId; - }, - - /****** Power levels management ******/ - - /** - * Return the power level of an user in a particular room - * @param {String} room_id the room id - * @param {String} user_id the user id - * @returns {Number} a value between 0 and 10 - */ - getUserPowerLevel: function(room_id, user_id) { - var powerLevel = 0; - var room = $rootScope.events.rooms[room_id]; - if (room && room["m.room.power_levels"]) { - if (user_id in room["m.room.power_levels"].content) { - powerLevel = room["m.room.power_levels"].content[user_id]; - } - else { - // Use the room default user power - powerLevel = room["m.room.power_levels"].content["default"]; - } - } - return powerLevel; - }, /** * Change or reset the power level of a user * @param {String} room_id the room id * @param {String} user_id the user id - * @param {Number} powerLevel a value between 0 and 10 + * @param {Number} powerLevel The desired power level. * If undefined, the user power level will be reset, ie he will use the default room user power level + * @param event The existing m.room.power_levels event if one exists. * @returns {promise} an $http promise */ - setUserPowerLevel: function(room_id, user_id, powerLevel) { - - // Hack: currently, there is no home server API so do it by hand by updating - // the current m.room.power_levels of the room and send it to the server - var room = $rootScope.events.rooms[room_id]; - if (room && room["m.room.power_levels"]) { - var content = angular.copy(room["m.room.power_levels"].content); - content[user_id] = powerLevel; + setUserPowerLevel: function(room_id, user_id, powerLevel, event) { + var content = {}; + if (event) { + // if there is an existing event, copy the content as it contains + // the power level values for other members which we do not want + // to modify. + content = angular.copy(event.content); + } + content[user_id] = powerLevel; - var path = "/rooms/$room_id/state/m.room.power_levels"; - path = path.replace("$room_id", encodeURIComponent(room_id)); + 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; + return doRequest("PUT", path, undefined, content); }, getTurnServer: function() { diff --git a/webclient/components/matrix/presence-service.js b/syweb/webclient/components/matrix/presence-service.js
index b487e3d3bd..b487e3d3bd 100644 --- a/webclient/components/matrix/presence-service.js +++ b/syweb/webclient/components/matrix/presence-service.js
diff --git a/webclient/components/utilities/utilities-service.js b/syweb/webclient/components/utilities/utilities-service.js
index b417cc5b39..b417cc5b39 100644 --- a/webclient/components/utilities/utilities-service.js +++ b/syweb/webclient/components/utilities/utilities-service.js
diff --git a/webclient/favicon.ico b/syweb/webclient/favicon.ico
index ba193fabc8..ba193fabc8 100644 --- a/webclient/favicon.ico +++ b/syweb/webclient/favicon.ico
Binary files differdiff --git a/webclient/home/home-controller.js b/syweb/webclient/home/home-controller.js
index f1295560ef..a9538a0309 100644 --- a/webclient/home/home-controller.js +++ b/syweb/webclient/home/home-controller.js
@@ -17,8 +17,8 @@ limitations under the License. 'use strict'; angular.module('HomeController', ['matrixService', 'eventHandlerService', 'RecentsController']) -.controller('HomeController', ['$scope', '$location', 'matrixService', 'eventHandlerService', - function($scope, $location, matrixService, eventHandlerService) { +.controller('HomeController', ['$scope', '$location', 'matrixService', 'eventHandlerService', 'modelService', 'recentsService', + function($scope, $location, matrixService, eventHandlerService, modelService, recentsService) { $scope.config = matrixService.config(); $scope.public_rooms = []; @@ -46,6 +46,8 @@ angular.module('HomeController', ['matrixService', 'eventHandlerService', 'Recen $scope.newChat = { user: "" }; + + recentsService.setSelectedRoomId(undefined); var refresh = function() { @@ -54,11 +56,17 @@ angular.module('HomeController', ['matrixService', 'eventHandlerService', 'Recen $scope.public_rooms = response.data.chunk; for (var i = 0; i < $scope.public_rooms.length; i++) { var room = $scope.public_rooms[i]; - - // Add room_alias & room_display_name members - angular.extend(room, matrixService.getRoomAliasAndDisplayName(room)); - eventHandlerService.setRoomVisibility(room.room_id, "public"); + if (room.aliases && room.aliases.length > 0) { + room.room_display_name = room.aliases[0]; + room.room_alias = room.aliases[0]; + } + else if (room.name) { + room.room_display_name = room.name; + } + else { + room.room_display_name = room.room_id; + } } } ); @@ -76,7 +84,7 @@ angular.module('HomeController', ['matrixService', 'eventHandlerService', 'Recen // This room has been created. Refresh the rooms list console.log("Created room " + response.data.room_alias + " with id: "+ response.data.room_id); - matrixService.createRoomIdToAliasMapping( + modelService.createRoomIdToAliasMapping( response.data.room_id, response.data.room_alias); }, function(error) { diff --git a/webclient/home/home.html b/syweb/webclient/home/home.html
index 0af382916e..0af382916e 100644 --- a/webclient/home/home.html +++ b/syweb/webclient/home/home.html
diff --git a/webclient/img/close.png b/syweb/webclient/img/close.png
index fbcdb51e6b..fbcdb51e6b 100644 --- a/webclient/img/close.png +++ b/syweb/webclient/img/close.png
Binary files differdiff --git a/webclient/img/default-profile.png b/syweb/webclient/img/default-profile.png
index 6f81a3c417..6f81a3c417 100644 --- a/webclient/img/default-profile.png +++ b/syweb/webclient/img/default-profile.png
Binary files differdiff --git a/webclient/img/gradient.png b/syweb/webclient/img/gradient.png
index 8ac9e2193f..8ac9e2193f 100644 --- a/webclient/img/gradient.png +++ b/syweb/webclient/img/gradient.png
Binary files differdiff --git a/webclient/img/green_phone.png b/syweb/webclient/img/green_phone.png
index 28807c749b..28807c749b 100644 --- a/webclient/img/green_phone.png +++ b/syweb/webclient/img/green_phone.png
Binary files differdiff --git a/webclient/img/logo-small.png b/syweb/webclient/img/logo-small.png
index 411206dcdc..411206dcdc 100644 --- a/webclient/img/logo-small.png +++ b/syweb/webclient/img/logo-small.png
Binary files differdiff --git a/webclient/img/logo.png b/syweb/webclient/img/logo.png
index c4b53a8487..c4b53a8487 100644 --- a/webclient/img/logo.png +++ b/syweb/webclient/img/logo.png
Binary files differdiff --git a/webclient/index.html b/syweb/webclient/index.html
index 35c8051298..d9c67333af 100644 --- a/webclient/index.html +++ b/syweb/webclient/index.html
@@ -13,13 +13,15 @@ <script type='text/javascript' src='js/jquery-1.8.3.min.js'></script> <script type="text/javascript" src="https://www.google.com/recaptcha/api/js/recaptcha_ajax.js"></script> - <script src="js/angular.min.js"></script> + <script src="js/angular.js"></script> <script src="js/angular-route.min.js"></script> <script src="js/angular-sanitize.min.js"></script> - <script src="js/angular-animate.min.js"></script> + <script src="js/jquery.peity.min.js"></script> + <script src="js/angular-peity.js"></script> <script type='text/javascript' src="js/ui-bootstrap-tpls-0.11.2.js"></script> <script type='text/javascript' src='js/ng-infinite-scroll-matrix.js'></script> <script type='text/javascript' src='js/autofill-event.js'></script> + <script type='text/javascript' src='js/elastic.js'></script> <script src="app.js"></script> <script src="config.js"></script> <script src="app-controller.js"></script> @@ -40,6 +42,10 @@ <script src="components/matrix/matrix-phone-service.js"></script> <script src="components/matrix/event-stream-service.js"></script> <script src="components/matrix/event-handler-service.js"></script> + <script src="components/matrix/notification-service.js"></script> + <script src="components/matrix/recents-service.js"></script> + <script src="components/matrix/commands-service.js"></script> + <script src="components/matrix/model-service.js"></script> <script src="components/matrix/presence-service.js"></script> <script src="components/fileInput/file-input-directive.js"></script> <script src="components/fileUpload/file-upload-service.js"></script> @@ -50,8 +56,8 @@ <div id="videoBackground" ng-class="videoMode"> <div id="videoContainer" ng-class="videoMode"> <div id="videoContainerPadding"></div> - <video id="localVideo" ng-class="[videoMode, currentCall.state]" ng-show="currentCall && currentCall.type == 'video' && (currentCall.state == 'connected' || currentCall.state == 'connecting' || currentCall.state == 'invite_sent' || currentCall.state == 'ended')"></video> - <video id="remoteVideo" ng-class="[videoMode, currentCall.state]" ng-show="currentCall && currentCall.type == 'video' && (currentCall.state == 'connected' || (currentCall.state == 'ended' && currentCall.didConnect))"></video> + <div ng-class="[videoMode, currentCall.state]" ng-show="currentCall && currentCall.type == 'video' && (currentCall.state == 'connected' || currentCall.state == 'connecting' || currentCall.state == 'invite_sent' || currentCall.state == 'ended')"><video id="localVideo"></video></div> + <div ng-class="[videoMode, currentCall.state]" ng-show="currentCall && currentCall.type == 'video' && (currentCall.state == 'connected' || (currentCall.state == 'ended' && currentCall.didConnect))"><video id="remoteVideo"></video></div> </div> </div> @@ -60,8 +66,7 @@ <div id="headerContent" ng-hide="'/login' == location || '/register' == location"> <div id="callBar" ng-show="currentCall"> <img id="callPeerImage" ng-show="currentCall.userProfile.avatar_url" ngSrc="{{ currentCall.userProfile.avatar_url }}" /> - <img class="callIcon" src="img/green_phone.png" ng-show="currentCall.state != 'ended'" /> - <img class="callIcon" id="callEndedIcon" src="img/red_phone.png" ng-show="currentCall.state == 'ended'" /> + <img class="callIcon" src="img/green_phone.png" ng-show="!!currentCall" ng-class="currentCall.state" /> <div id="callPeerNameAndState"> <span id="callPeerName">{{ currentCall.userProfile.displayname }}</span> <br /> @@ -82,7 +87,7 @@ </span> </div> <span ng-show="currentCall.state == 'ringing'"> - <button ng-click="answerCall()" ng-disabled="!isWebRTCSupported" title="{{isWebRTCSupported ? '' : 'Your browser does not support VoIP' }}">Answer {{ currentCall.type }} call</button> + <button ng-click="answerCall()" ng-disabled="!isWebRTCSupported()" title="{{isWebRTCSupported() ? '' : 'Your browser does not support VoIP' }}">Answer {{ currentCall.type }} call</button> <button ng-click="hangupCall()">Reject</button> </span> <button ng-click="hangupCall()" ng-show="currentCall && currentCall.state != 'ringing' && currentCall.state != 'ended' && currentCall.state != 'fledgling'">Hang up</button> diff --git a/webclient/js/angular-animate.js b/syweb/webclient/js/angular-animate.js
index c15f793c1b..c15f793c1b 100644 --- a/webclient/js/angular-animate.js +++ b/syweb/webclient/js/angular-animate.js
diff --git a/webclient/js/angular-animate.min.js b/syweb/webclient/js/angular-animate.min.js
index 1ce2a93ac7..1ce2a93ac7 100644 --- a/webclient/js/angular-animate.min.js +++ b/syweb/webclient/js/angular-animate.min.js
diff --git a/webclient/js/angular-mocks.js b/syweb/webclient/js/angular-mocks.js
index 48c0b5decb..24bbcd4137 100755 --- a/webclient/js/angular-mocks.js +++ b/syweb/webclient/js/angular-mocks.js
@@ -1,10 +1,3 @@ -/** - * @license AngularJS v1.2.22 - * (c) 2010-2014 Google, Inc. http://angularjs.org - * License: MIT - */ -(function(window, angular, undefined) { - 'use strict'; /** @@ -63,6 +56,8 @@ angular.mock.$Browser = function() { return listener; }; + self.$$checkUrlChange = angular.noop; + self.cookieHash = {}; self.lastCookieHash = {}; self.deferredFns = []; @@ -125,7 +120,7 @@ angular.mock.$Browser = function() { } }; - self.$$baseHref = ''; + self.$$baseHref = '/'; self.baseHref = function() { return this.$$baseHref; }; @@ -774,13 +769,22 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng']) }; }); - $provide.decorator('$animate', function($delegate, $$asyncCallback) { + $provide.decorator('$animate', ['$delegate', '$$asyncCallback', '$timeout', '$browser', + function($delegate, $$asyncCallback, $timeout, $browser) { var animate = { queue : [], + cancel : $delegate.cancel, enabled : $delegate.enabled, - triggerCallbacks : function() { + triggerCallbackEvents : function() { $$asyncCallback.flush(); }, + triggerCallbackPromise : function() { + $timeout.flush(0); + }, + triggerCallbacks : function() { + this.triggerCallbackEvents(); + this.triggerCallbackPromise(); + }, triggerReflow : function() { angular.forEach(reflowQueue, function(fn) { fn(); @@ -797,12 +801,12 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng']) element : arguments[0], args : arguments }); - $delegate[method].apply($delegate, arguments); + return $delegate[method].apply($delegate, arguments); }; }); return animate; - }); + }]); }]); @@ -888,7 +892,7 @@ angular.mock.dump = function(object) { * development please see {@link ngMockE2E.$httpBackend e2e $httpBackend mock}. * * During unit testing, we want our unit tests to run quickly and have no external dependencies so - * we don’t want to send [XHR](https://developer.mozilla.org/en/xmlhttprequest) or + * we don’t want to send [XHR](https://developer.mozilla.org/en/xmlhttprequest) or * [JSONP](http://en.wikipedia.org/wiki/JSONP) requests to a real server. All we really need is * to verify whether a certain request has been sent or not, or alternatively just let the * application make requests, respond with pre-trained responses and assert that the end result is @@ -1007,13 +1011,14 @@ angular.mock.dump = function(object) { ```js // testing controller describe('MyController', function() { - var $httpBackend, $rootScope, createController; + var $httpBackend, $rootScope, createController, authRequestHandler; beforeEach(inject(function($injector) { // Set up the mock http service responses $httpBackend = $injector.get('$httpBackend'); // backend definition common for all tests - $httpBackend.when('GET', '/auth.py').respond({userId: 'userX'}, {'A-Token': 'xxx'}); + authRequestHandler = $httpBackend.when('GET', '/auth.py') + .respond({userId: 'userX'}, {'A-Token': 'xxx'}); // Get hold of a scope (i.e. the root scope) $rootScope = $injector.get('$rootScope'); @@ -1039,11 +1044,23 @@ angular.mock.dump = function(object) { }); + it('should fail authentication', function() { + + // Notice how you can change the response even after it was set + authRequestHandler.respond(401, ''); + + $httpBackend.expectGET('/auth.py'); + var controller = createController(); + $httpBackend.flush(); + expect($rootScope.status).toBe('Failed...'); + }); + + it('should send msg to server', function() { var controller = createController(); $httpBackend.flush(); - // now you don’t care about the authentication, but + // now you don’t care about the authentication, but // the controller will still send the request and // $httpBackend will respond without you having to // specify the expectation and response for this request @@ -1186,32 +1203,39 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { * Creates a new backend definition. * * @param {string} method HTTP method. - * @param {string|RegExp} url HTTP url. + * @param {string|RegExp|function(string)} url HTTP url or function that receives the url + * and returns true if the url match the current definition. * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives * data string and returns true if the data is as expected. * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header * object and returns true if the headers match the current definition. * @returns {requestHandler} Returns an object with `respond` method that controls how a matched - * request is handled. + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. * - * - respond – + * - respond – * `{function([status,] data[, headers, statusText]) * | function(function(method, url, data, headers)}` - * – The respond method takes a set of static data to be returned or a function that can + * – The respond method takes a set of static data to be returned or a function that can * return an array containing response status (number), response data (string), response - * headers (Object), and the text for the status (string). + * headers (Object), and the text for the status (string). The respond method returns the + * `requestHandler` object for possible overrides. */ $httpBackend.when = function(method, url, data, headers) { var definition = new MockHttpExpectation(method, url, data, headers), chain = { respond: function(status, data, headers, statusText) { + definition.passThrough = undefined; definition.response = createResponse(status, data, headers, statusText); + return chain; } }; if ($browser) { chain.passThrough = function() { + definition.response = undefined; definition.passThrough = true; + return chain; }; } @@ -1225,10 +1249,12 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { * @description * Creates a new backend definition for GET requests. For more info see `when()`. * - * @param {string|RegExp} url HTTP url. + * @param {string|RegExp|function(string)} url HTTP url or function that receives the url + * and returns true if the url match the current definition. * @param {(Object|function(Object))=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. */ /** @@ -1237,10 +1263,12 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { * @description * Creates a new backend definition for HEAD requests. For more info see `when()`. * - * @param {string|RegExp} url HTTP url. + * @param {string|RegExp|function(string)} url HTTP url or function that receives the url + * and returns true if the url match the current definition. * @param {(Object|function(Object))=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. */ /** @@ -1249,10 +1277,12 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { * @description * Creates a new backend definition for DELETE requests. For more info see `when()`. * - * @param {string|RegExp} url HTTP url. + * @param {string|RegExp|function(string)} url HTTP url or function that receives the url + * and returns true if the url match the current definition. * @param {(Object|function(Object))=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. */ /** @@ -1261,12 +1291,14 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { * @description * Creates a new backend definition for POST requests. For more info see `when()`. * - * @param {string|RegExp} url HTTP url. + * @param {string|RegExp|function(string)} url HTTP url or function that receives the url + * and returns true if the url match the current definition. * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives * data string and returns true if the data is as expected. * @param {(Object|function(Object))=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. */ /** @@ -1275,12 +1307,14 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { * @description * Creates a new backend definition for PUT requests. For more info see `when()`. * - * @param {string|RegExp} url HTTP url. + * @param {string|RegExp|function(string)} url HTTP url or function that receives the url + * and returns true if the url match the current definition. * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives * data string and returns true if the data is as expected. * @param {(Object|function(Object))=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. */ /** @@ -1289,9 +1323,11 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { * @description * Creates a new backend definition for JSONP requests. For more info see `when()`. * - * @param {string|RegExp} url HTTP url. + * @param {string|RegExp|function(string)} url HTTP url or function that receives the url + * and returns true if the url match the current definition. * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. */ createShortMethods('when'); @@ -1303,30 +1339,36 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { * Creates a new request expectation. * * @param {string} method HTTP method. - * @param {string|RegExp} url HTTP url. + * @param {string|RegExp|function(string)} url HTTP url or function that receives the url + * and returns true if the url match the current definition. * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that * receives data string and returns true if the data is as expected, or Object if request body * is in JSON format. * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header * object and returns true if the headers match the current expectation. * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. * - * - respond – + * - respond – * `{function([status,] data[, headers, statusText]) * | function(function(method, url, data, headers)}` - * – The respond method takes a set of static data to be returned or a function that can + * – The respond method takes a set of static data to be returned or a function that can * return an array containing response status (number), response data (string), response - * headers (Object), and the text for the status (string). + * headers (Object), and the text for the status (string). The respond method returns the + * `requestHandler` object for possible overrides. */ $httpBackend.expect = function(method, url, data, headers) { - var expectation = new MockHttpExpectation(method, url, data, headers); + var expectation = new MockHttpExpectation(method, url, data, headers), + chain = { + respond: function (status, data, headers, statusText) { + expectation.response = createResponse(status, data, headers, statusText); + return chain; + } + }; + expectations.push(expectation); - return { - respond: function (status, data, headers, statusText) { - expectation.response = createResponse(status, data, headers, statusText); - } - }; + return chain; }; @@ -1336,10 +1378,12 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { * @description * Creates a new request expectation for GET requests. For more info see `expect()`. * - * @param {string|RegExp} url HTTP url. + * @param {string|RegExp|function(string)} url HTTP url or function that receives the url + * and returns true if the url match the current definition. * @param {Object=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. See #expect for more info. + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. See #expect for more info. */ /** @@ -1348,10 +1392,12 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { * @description * Creates a new request expectation for HEAD requests. For more info see `expect()`. * - * @param {string|RegExp} url HTTP url. + * @param {string|RegExp|function(string)} url HTTP url or function that receives the url + * and returns true if the url match the current definition. * @param {Object=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. */ /** @@ -1360,10 +1406,12 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { * @description * Creates a new request expectation for DELETE requests. For more info see `expect()`. * - * @param {string|RegExp} url HTTP url. + * @param {string|RegExp|function(string)} url HTTP url or function that receives the url + * and returns true if the url match the current definition. * @param {Object=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. */ /** @@ -1372,13 +1420,15 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { * @description * Creates a new request expectation for POST requests. For more info see `expect()`. * - * @param {string|RegExp} url HTTP url. + * @param {string|RegExp|function(string)} url HTTP url or function that receives the url + * and returns true if the url match the current definition. * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that * receives data string and returns true if the data is as expected, or Object if request body * is in JSON format. * @param {Object=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. */ /** @@ -1387,13 +1437,15 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { * @description * Creates a new request expectation for PUT requests. For more info see `expect()`. * - * @param {string|RegExp} url HTTP url. + * @param {string|RegExp|function(string)} url HTTP url or function that receives the url + * and returns true if the url match the current definition. * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that * receives data string and returns true if the data is as expected, or Object if request body * is in JSON format. * @param {Object=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. */ /** @@ -1402,13 +1454,15 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { * @description * Creates a new request expectation for PATCH requests. For more info see `expect()`. * - * @param {string|RegExp} url HTTP url. + * @param {string|RegExp|function(string)} url HTTP url or function that receives the url + * and returns true if the url match the current definition. * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that * receives data string and returns true if the data is as expected, or Object if request body * is in JSON format. * @param {Object=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. */ /** @@ -1417,9 +1471,11 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { * @description * Creates a new request expectation for JSONP requests. For more info see `expect()`. * - * @param {string|RegExp} url HTTP url. + * @param {string|RegExp|function(string)} url HTTP url or function that receives the url + * and returns true if the url match the current definition. * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. */ createShortMethods('expect'); @@ -1434,11 +1490,11 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { * all pending requests will be flushed. If there are no pending requests when the flush method * is called an exception is thrown (as this typically a sign of programming error). */ - $httpBackend.flush = function(count) { - $rootScope.$digest(); + $httpBackend.flush = function(count, digest) { + if (digest !== false) $rootScope.$digest(); if (!responses.length) throw new Error('No pending request to flush !'); - if (angular.isDefined(count)) { + if (angular.isDefined(count) && count !== null) { while (count--) { if (!responses.length) throw new Error('No more pending request to flush !'); responses.shift()(); @@ -1448,7 +1504,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { responses.shift()(); } } - $httpBackend.verifyNoOutstandingExpectation(); + $httpBackend.verifyNoOutstandingExpectation(digest); }; @@ -1466,8 +1522,8 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { * afterEach($httpBackend.verifyNoOutstandingExpectation); * ``` */ - $httpBackend.verifyNoOutstandingExpectation = function() { - $rootScope.$digest(); + $httpBackend.verifyNoOutstandingExpectation = function(digest) { + if (digest !== false) $rootScope.$digest(); if (expectations.length) { throw new Error('Unsatisfied requests: ' + expectations.join(', ')); } @@ -1511,7 +1567,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { function createShortMethods(prefix) { - angular.forEach(['GET', 'DELETE', 'JSONP'], function(method) { + angular.forEach(['GET', 'DELETE', 'JSONP', 'HEAD'], function(method) { $httpBackend[prefix + method] = function(url, headers) { return $httpBackend[prefix](method, url, undefined, headers); }; @@ -1541,6 +1597,7 @@ function MockHttpExpectation(method, url, data, headers) { this.matchUrl = function(u) { if (!url) return true; if (angular.isFunction(url.test)) return url.test(u); + if (angular.isFunction(url)) return url(u); return url == u; }; @@ -1627,7 +1684,7 @@ function MockXhr() { * that adds a "flush" and "verifyNoPendingTasks" methods. */ -angular.mock.$TimeoutDecorator = function($delegate, $browser) { +angular.mock.$TimeoutDecorator = ['$delegate', '$browser', function ($delegate, $browser) { /** * @ngdoc method @@ -1666,9 +1723,9 @@ angular.mock.$TimeoutDecorator = function($delegate, $browser) { } return $delegate; -}; +}]; -angular.mock.$RAFDecorator = function($delegate) { +angular.mock.$RAFDecorator = ['$delegate', function($delegate) { var queue = []; var rafFn = function(fn) { var index = queue.length; @@ -1694,9 +1751,9 @@ angular.mock.$RAFDecorator = function($delegate) { }; return rafFn; -}; +}]; -angular.mock.$AsyncCallbackDecorator = function($delegate) { +angular.mock.$AsyncCallbackDecorator = ['$delegate', function($delegate) { var callbacks = []; var addFn = function(fn) { callbacks.push(fn); @@ -1708,7 +1765,7 @@ angular.mock.$AsyncCallbackDecorator = function($delegate) { callbacks = []; }; return addFn; -}; +}]; /** * @@ -1822,22 +1879,25 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { * Creates a new backend definition. * * @param {string} method HTTP method. - * @param {string|RegExp} url HTTP url. + * @param {string|RegExp|function(string)} url HTTP url or function that receives the url + * and returns true if the url match the current definition. * @param {(string|RegExp)=} data HTTP request body. * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header * object and returns true if the headers match the current definition. * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that - * control how a matched request is handled. + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. * - * - respond – + * - respond – * `{function([status,] data[, headers, statusText]) * | function(function(method, url, data, headers)}` - * – The respond method takes a set of static data to be returned or a function that can return + * – The respond method takes a set of static data to be returned or a function that can return * an array containing response status (number), response data (string), response headers * (Object), and the text for the status (string). - * - passThrough – `{function()}` – Any request matching a backend definition with + * - passThrough – `{function()}` – Any request matching a backend definition with * `passThrough` handler will be passed through to the real backend (an XHR request will be made * to the server.) + * - Both methods return the `requestHandler` object for possible overrides. */ /** @@ -1847,10 +1907,12 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { * @description * Creates a new backend definition for GET requests. For more info see `when()`. * - * @param {string|RegExp} url HTTP url. + * @param {string|RegExp|function(string)} url HTTP url or function that receives the url + * and returns true if the url match the current definition. * @param {(Object|function(Object))=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that - * control how a matched request is handled. + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. */ /** @@ -1860,10 +1922,12 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { * @description * Creates a new backend definition for HEAD requests. For more info see `when()`. * - * @param {string|RegExp} url HTTP url. + * @param {string|RegExp|function(string)} url HTTP url or function that receives the url + * and returns true if the url match the current definition. * @param {(Object|function(Object))=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that - * control how a matched request is handled. + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. */ /** @@ -1873,10 +1937,12 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { * @description * Creates a new backend definition for DELETE requests. For more info see `when()`. * - * @param {string|RegExp} url HTTP url. + * @param {string|RegExp|function(string)} url HTTP url or function that receives the url + * and returns true if the url match the current definition. * @param {(Object|function(Object))=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that - * control how a matched request is handled. + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. */ /** @@ -1886,11 +1952,13 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { * @description * Creates a new backend definition for POST requests. For more info see `when()`. * - * @param {string|RegExp} url HTTP url. + * @param {string|RegExp|function(string)} url HTTP url or function that receives the url + * and returns true if the url match the current definition. * @param {(string|RegExp)=} data HTTP request body. * @param {(Object|function(Object))=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that - * control how a matched request is handled. + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. */ /** @@ -1900,11 +1968,13 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { * @description * Creates a new backend definition for PUT requests. For more info see `when()`. * - * @param {string|RegExp} url HTTP url. + * @param {string|RegExp|function(string)} url HTTP url or function that receives the url + * and returns true if the url match the current definition. * @param {(string|RegExp)=} data HTTP request body. * @param {(Object|function(Object))=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that - * control how a matched request is handled. + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. */ /** @@ -1914,11 +1984,13 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { * @description * Creates a new backend definition for PATCH requests. For more info see `when()`. * - * @param {string|RegExp} url HTTP url. + * @param {string|RegExp|function(string)} url HTTP url or function that receives the url + * and returns true if the url match the current definition. * @param {(string|RegExp)=} data HTTP request body. * @param {(Object|function(Object))=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that - * control how a matched request is handled. + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. */ /** @@ -1928,30 +2000,17 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { * @description * Creates a new backend definition for JSONP requests. For more info see `when()`. * - * @param {string|RegExp} url HTTP url. + * @param {string|RegExp|function(string)} url HTTP url or function that receives the url + * and returns true if the url match the current definition. * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that - * control how a matched request is handled. + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. */ angular.mock.e2e = {}; angular.mock.e2e.$httpBackendDecorator = ['$rootScope', '$delegate', '$browser', createHttpBackendMock]; -angular.mock.clearDataCache = function() { - var key, - cache = angular.element.cache; - - for(key in cache) { - if (Object.prototype.hasOwnProperty.call(cache,key)) { - var handle = cache[key].handle; - - handle && angular.element(handle.elem).off(); - delete cache[key]; - } - } -}; - - if(window.jasmine || window.mocha) { var currentSpec = null, @@ -1982,8 +2041,6 @@ if(window.jasmine || window.mocha) { injector.get('$browser').pollFns.length = 0; } - angular.mock.clearDataCache(); - // clean up jquery's fragment cache angular.forEach(angular.element.fragments, function(val, key) { delete angular.element.fragments[key]; @@ -2003,6 +2060,7 @@ if(window.jasmine || window.mocha) { * @description * * *NOTE*: This function is also published on window for easy access.<br> + * *NOTE*: This function is declared ONLY WHEN running tests with jasmine or mocha * * This function registers a module configuration code. It collects the configuration information * which will be used when the injector is created by {@link angular.mock.inject inject}. @@ -2045,6 +2103,7 @@ if(window.jasmine || window.mocha) { * @description * * *NOTE*: This function is also published on window for easy access.<br> + * *NOTE*: This function is declared ONLY WHEN running tests with jasmine or mocha * * The inject function wraps a function into an injectable function. The inject() creates new * instance of {@link auto.$injector $injector} per test, which is then used for @@ -2144,14 +2203,28 @@ if(window.jasmine || window.mocha) { ///////////////////// function workFn() { var modules = currentSpec.$modules || []; - + var strictDi = !!currentSpec.$injectorStrict; modules.unshift('ngMock'); modules.unshift('ng'); var injector = currentSpec.$injector; if (!injector) { - injector = currentSpec.$injector = angular.injector(modules); + if (strictDi) { + // If strictDi is enabled, annotate the providerInjector blocks + angular.forEach(modules, function(moduleFn) { + if (typeof moduleFn === "function") { + angular.injector.$$annotate(moduleFn); + } + }); + } + injector = currentSpec.$injector = angular.injector(modules, strictDi); + currentSpec.$injectorStrict = strictDi; } for(var i = 0, ii = blockFns.length; i < ii; i++) { + if (currentSpec.$injectorStrict) { + // If the injector is strict / strictDi, and the spec wants to inject using automatic + // annotation, then annotate the function here. + injector.annotate(blockFns[i]); + } try { /* jshint -W040 *//* Jasmine explicitly provides a `this` object when calling functions */ injector.invoke(blockFns[i] || angular.noop, this); @@ -2167,7 +2240,20 @@ if(window.jasmine || window.mocha) { } } }; -} -})(window, window.angular); \ No newline at end of file + angular.mock.inject.strictDi = function(value) { + value = arguments.length ? !!value : true; + return isSpecRunning() ? workFn() : workFn; + + function workFn() { + if (value !== currentSpec.$injectorStrict) { + if (currentSpec.$injector) { + throw new Error('Injector already created, can not modify strict annotations'); + } else { + currentSpec.$injectorStrict = value; + } + } + } + }; +} diff --git a/webclient/js/angular-route.js b/syweb/webclient/js/angular-route.js
index 305d92e855..305d92e855 100644 --- a/webclient/js/angular-route.js +++ b/syweb/webclient/js/angular-route.js
diff --git a/webclient/js/angular-route.min.js b/syweb/webclient/js/angular-route.min.js
index 03da279ec3..03da279ec3 100644 --- a/webclient/js/angular-route.min.js +++ b/syweb/webclient/js/angular-route.min.js
diff --git a/webclient/js/angular-sanitize.js b/syweb/webclient/js/angular-sanitize.js
index ec46895f68..ec46895f68 100644 --- a/webclient/js/angular-sanitize.js +++ b/syweb/webclient/js/angular-sanitize.js
diff --git a/webclient/js/angular-sanitize.min.js b/syweb/webclient/js/angular-sanitize.min.js
index ce99bba18e..ce99bba18e 100644 --- a/webclient/js/angular-sanitize.min.js +++ b/syweb/webclient/js/angular-sanitize.min.js
diff --git a/webclient/js/angular.js b/syweb/webclient/js/angular.js
index bdc97abb02..bdc97abb02 100644 --- a/webclient/js/angular.js +++ b/syweb/webclient/js/angular.js
diff --git a/webclient/js/angular.min.js b/syweb/webclient/js/angular.min.js
index 5475589e2f..5475589e2f 100644 --- a/webclient/js/angular.min.js +++ b/syweb/webclient/js/angular.min.js
diff --git a/webclient/js/autofill-event.js b/syweb/webclient/js/autofill-event.js
index 006f83e1be..006f83e1be 100755 --- a/webclient/js/autofill-event.js +++ b/syweb/webclient/js/autofill-event.js
diff --git a/webclient/js/jquery-1.8.3.min.js b/syweb/webclient/js/jquery-1.8.3.min.js
index 3883779527..3883779527 100644 --- a/webclient/js/jquery-1.8.3.min.js +++ b/syweb/webclient/js/jquery-1.8.3.min.js
diff --git a/webclient/js/ng-infinite-scroll-matrix.js b/syweb/webclient/js/ng-infinite-scroll-matrix.js
index 045ec8d93e..045ec8d93e 100644 --- a/webclient/js/ng-infinite-scroll-matrix.js +++ b/syweb/webclient/js/ng-infinite-scroll-matrix.js
diff --git a/webclient/js/ui-bootstrap-tpls-0.11.2.js b/syweb/webclient/js/ui-bootstrap-tpls-0.11.2.js
index 260c2769b8..260c2769b8 100644 --- a/webclient/js/ui-bootstrap-tpls-0.11.2.js +++ b/syweb/webclient/js/ui-bootstrap-tpls-0.11.2.js
diff --git a/webclient/login/login-controller.js b/syweb/webclient/login/login-controller.js
index 5ef39a7122..5ef39a7122 100644 --- a/webclient/login/login-controller.js +++ b/syweb/webclient/login/login-controller.js
diff --git a/webclient/login/login.html b/syweb/webclient/login/login.html
index 6b321f8fc5..6b321f8fc5 100644 --- a/webclient/login/login.html +++ b/syweb/webclient/login/login.html
diff --git a/webclient/login/register-controller.js b/syweb/webclient/login/register-controller.js
index be970ce1c3..b23a72b185 100644 --- a/webclient/login/register-controller.js +++ b/syweb/webclient/login/register-controller.js
@@ -124,7 +124,7 @@ angular.module('RegisterController', ['matrixService']) $location.url("home"); }, function(error) { - console.trace("Registration error: "+error); + console.error("Registration error: "+JSON.stringify(error)); if (useCaptcha) { Recaptcha.reload(); } diff --git a/webclient/login/register.html b/syweb/webclient/login/register.html
index a27f9ad4e8..a27f9ad4e8 100644 --- a/webclient/login/register.html +++ b/syweb/webclient/login/register.html
diff --git a/webclient/media/busy.mp3 b/syweb/webclient/media/busy.mp3
index fec27ba4c5..fec27ba4c5 100644 --- a/webclient/media/busy.mp3 +++ b/syweb/webclient/media/busy.mp3
Binary files differdiff --git a/webclient/media/busy.ogg b/syweb/webclient/media/busy.ogg
index 5d64a7d0d9..5d64a7d0d9 100644 --- a/webclient/media/busy.ogg +++ b/syweb/webclient/media/busy.ogg
Binary files differdiff --git a/webclient/media/callend.mp3 b/syweb/webclient/media/callend.mp3
index 50c34e5640..50c34e5640 100644 --- a/webclient/media/callend.mp3 +++ b/syweb/webclient/media/callend.mp3
Binary files differdiff --git a/webclient/media/callend.ogg b/syweb/webclient/media/callend.ogg
index 927ce1f634..927ce1f634 100644 --- a/webclient/media/callend.ogg +++ b/syweb/webclient/media/callend.ogg
Binary files differdiff --git a/webclient/media/ring.mp3 b/syweb/webclient/media/ring.mp3
index 3c3cdde3f9..3c3cdde3f9 100644 --- a/webclient/media/ring.mp3 +++ b/syweb/webclient/media/ring.mp3
Binary files differdiff --git a/webclient/media/ring.ogg b/syweb/webclient/media/ring.ogg
index de49b8ae6f..de49b8ae6f 100644 --- a/webclient/media/ring.ogg +++ b/syweb/webclient/media/ring.ogg
Binary files differdiff --git a/webclient/media/ringback.mp3 b/syweb/webclient/media/ringback.mp3
index 6ee34bf395..6ee34bf395 100644 --- a/webclient/media/ringback.mp3 +++ b/syweb/webclient/media/ringback.mp3
Binary files differdiff --git a/webclient/media/ringback.ogg b/syweb/webclient/media/ringback.ogg
index 7dbfdcd017..7dbfdcd017 100644 --- a/webclient/media/ringback.ogg +++ b/syweb/webclient/media/ringback.ogg
Binary files differdiff --git a/webclient/mobile.css b/syweb/webclient/mobile.css
index 6fa9221ccf..32b01c503d 100644 --- a/webclient/mobile.css +++ b/syweb/webclient/mobile.css
@@ -1,4 +1,13 @@ /*** Mobile voodoo ***/ + +/** iPads **/ +@media all and (max-device-width: 768px) { + #roomRecentsTableWrapper { + display: none; + } +} + +/** iPhones **/ @media all and (max-device-width: 640px) { #messageTableWrapper { @@ -37,11 +46,16 @@ max-width: 640px ! important; } + #controls { + padding: 0px; + } + #headerUserId, #roomHeader img, #userIdCell, #roomRecentsTableWrapper, #usersTableWrapper, + #controlButtons, .extraControls { display: none; } @@ -64,6 +78,10 @@ padding-top: 10px; } + .roomHeaderInfo { + margin-right: 0px; + } + #roomName { font-size: 12px ! important; margin-top: 0px ! important; diff --git a/webclient/recents/recents-filter.js b/syweb/webclient/recents/recents-filter.js
index ef8d9897f7..cfbc6f4bd8 100644 --- a/webclient/recents/recents-filter.js +++ b/syweb/webclient/recents/recents-filter.js
@@ -17,7 +17,7 @@ 'use strict'; angular.module('RecentsController') -.filter('orderRecents', ["matrixService", "eventHandlerService", function(matrixService, eventHandlerService) { +.filter('orderRecents', ["matrixService", "eventHandlerService", "modelService", function(matrixService, eventHandlerService, modelService) { return function(rooms) { var user_id = matrixService.config().user_id; @@ -25,26 +25,33 @@ angular.module('RecentsController') // The key, room_id, is already in value objects var filtered = []; angular.forEach(rooms, function(room, room_id) { - + room.recent = {}; + var meEvent = room.current_room_state.state("m.room.member", user_id); // Show the room only if the user has joined it or has been invited // (ie, do not show it if he has been banned) - var member = eventHandlerService.getMember(room_id, user_id); - if (member && ("invite" === member.membership || "join" === member.membership)) { - + var member = modelService.getMember(room_id, user_id); + if (member) { + member = member.event; + } + room.recent.me = member; + if (member && ("invite" === member.content.membership || "join" === member.content.membership)) { + if ("invite" === member.content.membership) { + room.recent.inviter = member.user_id; + } // Count users here // TODO: Compute it directly in eventHandlerService - room.numUsersInRoom = eventHandlerService.getUsersCountInRoom(room_id); + room.recent.numUsersInRoom = eventHandlerService.getUsersCountInRoom(room_id); filtered.push(room); } - else if ("invite" === room.membership) { + else if (meEvent && "invite" === meEvent.content.membership) { // The only information we have about the room is that the user has been invited filtered.push(room); } }); // And time sort them - // The room with the lastest message at first + // The room with the latest message at first filtered.sort(function (roomA, roomB) { var lastMsgRoomA = eventHandlerService.getLastMessage(roomA.room_id, true); diff --git a/webclient/recents/recents.html b/syweb/webclient/recents/recents.html
index a52b215c7e..0b3a77ca11 100644 --- a/webclient/recents/recents.html +++ b/syweb/webclient/recents/recents.html
@@ -1,16 +1,16 @@ <div ng-controller="RecentsController"> <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" - ng-class="{'recentsRoomSelected': (room.room_id === recentsSelectedRoomID)}"> + <tbody ng-repeat="(index, room) in rooms | orderRecents" + ng-click="selectRoom(room)" + class="recentsRoom" + ng-class="{'recentsRoomSelected': (room.room_id === recentsSelectedRoomID), 'recentsRoomBing': (unreadBings[room.room_id]), 'recentsRoomUnread': (unreadMessages[room.room_id])}"> <tr> - <td ng-class="room['m.room.join_rules'].content.join_rule == 'public' ? 'recentsRoomName recentsPublicRoom' : 'recentsRoomName'"> + <td ng-class="room.current_room_state.state('m.room.join_rules').content.join_rule == 'public' ? 'recentsRoomName recentsPublicRoom' : 'recentsRoomName'"> {{ room.room_id | mRoomName }} </td> <td class="recentsRoomSummaryUsersCount"> - <span ng-show="undefined !== room.numUsersInRoom"> - {{ room.numUsersInRoom || '1' }} {{ room.numUsersInRoom == 1 ? 'user' : 'users' }} + <span ng-show="undefined !== room.recent.numUsersInRoom"> + {{ room.recent.numUsersInRoom || '1' }} {{ room.recent.numUsersInRoom == 1 ? 'user' : 'users' }} </span> </td> <td class="recentsRoomSummaryTS"> @@ -27,11 +27,11 @@ <tr> <td colspan="3" class="recentsRoomSummary"> - <div ng-show="room.membership === 'invite'"> - {{ room.inviter | mUserDisplayName: room.room_id }} invited you + <div ng-show="room.recent.me.content.membership === 'invite'"> + {{ room.recent.inviter | mUserDisplayName: room.room_id }} invited you </div> - <div ng-hide="room.membership === 'invite'" ng-switch="lastMsg.type"> + <div ng-hide="room.recent.me.membership === 'invite'" ng-switch="lastMsg.type"> <div ng-switch-when="m.room.member"> <span ng-switch="lastMsg.changedKey"> <span ng-switch-when="membership"> diff --git a/webclient/room/room-controller.js b/syweb/webclient/room/room-controller.js
index 841b5cccdd..67372a804f 100644 --- a/webclient/room/room-controller.js +++ b/syweb/webclient/room/room-controller.js
@@ -14,12 +14,15 @@ See the License for the specific language governing permissions and limitations under the License. */ -angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) -.controller('RoomController', ['$modal', '$filter', '$scope', '$timeout', '$routeParams', '$location', '$rootScope', 'matrixService', 'mPresence', 'eventHandlerService', 'mFileUpload', 'matrixPhoneService', 'MatrixCall', - function($modal, $filter, $scope, $timeout, $routeParams, $location, $rootScope, matrixService, mPresence, eventHandlerService, mFileUpload, matrixPhoneService, MatrixCall) { +angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput', 'angular-peity']) +.controller('RoomController', ['$modal', '$filter', '$scope', '$timeout', '$routeParams', '$location', '$rootScope', 'matrixService', 'mPresence', 'eventHandlerService', 'mFileUpload', 'matrixPhoneService', 'MatrixCall', 'modelService', 'recentsService', 'commandsService', + function($modal, $filter, $scope, $timeout, $routeParams, $location, $rootScope, matrixService, mPresence, eventHandlerService, mFileUpload, matrixPhoneService, MatrixCall, modelService, recentsService, commandsService) { 'use strict'; var MESSAGES_PER_PAGINATION = 30; var THUMBNAIL_SIZE = 320; + + // .html needs this + $scope.containsBingWord = eventHandlerService.eventContainsBingWord; // Room ids. Computed and resolved in onInit $scope.room_id = undefined; @@ -36,12 +39,8 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) messages_visibility: "hidden", // In order to avoid flickering when scrolling down the message table at the page opening, delay the message table display }; $scope.members = {}; - $scope.autoCompleting = false; - $scope.autoCompleteIndex = 0; - $scope.autoCompleteOriginal = ""; $scope.imageURLToSend = ""; - $scope.userIDToInvite = ""; // vars and functions for updating the name @@ -54,7 +53,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) return; }; - var nameEvent = $rootScope.events.rooms[$scope.room_id]['m.room.name']; + var nameEvent = $scope.room.current_room_state.state_events['m.room.name']; if (nameEvent) { $scope.name.newNameText = nameEvent.content.name; } @@ -95,7 +94,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) console.log("Warning: Already editing topic."); return; } - var topicEvent = $rootScope.events.rooms[$scope.room_id]['m.room.topic']; + var topicEvent = $scope.room.current_room_state.state_events['m.room.topic']; if (topicEvent) { $scope.topic.newTopicText = topicEvent.content.topic; } @@ -152,7 +151,6 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) $scope.$on(eventHandlerService.MSG_EVENT, function(ngEvent, event, isLive) { if (isLive && event.room_id === $scope.room_id) { - scrollToBottom(); } }); @@ -187,21 +185,6 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) else { scrollToBottom(); updateMemberList(event); - - // Notify when a user joins - if ((document.hidden || matrixService.presence.unavailable === mPresence.getState()) - && event.state_key !== $scope.state.user_id && "join" === event.membership) { - var notification = new window.Notification( - event.content.displayname + - " (" + (matrixService.getRoomIdToAliasMapping(event.room_id) || event.room_id) + ")", // FIXME: don't leak room_ids here - { - "body": event.content.displayname + " joined", - "icon": event.content.avatar_url ? event.content.avatar_url : undefined - }); - $timeout(function() { - notification.close(); - }, 5 * 1000); - } } } }); @@ -240,11 +223,11 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) $scope.state.paginating = true; } - console.log("paginateBackMessages from " + $rootScope.events.rooms[$scope.room_id].pagination.earliest_token + " for " + numItems); + console.log("paginateBackMessages from " + $scope.room.old_room_state.pagination_token + " for " + numItems); var originalTopRow = $("#messageTable>tbody>tr:first")[0]; // Paginate events from the point in cache - matrixService.paginateBackMessages($scope.room_id, $rootScope.events.rooms[$scope.room_id].pagination.earliest_token, numItems).then( + matrixService.paginateBackMessages($scope.room_id, $scope.room.old_room_state.pagination_token, numItems).then( function(response) { eventHandlerService.handleRoomMessages($scope.room_id, response.data, false, 'b'); @@ -327,8 +310,9 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) } $scope.members[target_user_id] = chunk; - if (target_user_id in $rootScope.presence) { - updatePresence($rootScope.presence[target_user_id]); + var usr = modelService.getUser(target_user_id); + if (usr) { + updatePresence(usr.event); } } else { @@ -390,7 +374,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) var updateUserPowerLevel = function(user_id) { var member = $scope.members[user_id]; if (member) { - member.powerLevel = matrixService.getUserPowerLevel($scope.room_id, user_id); + member.powerLevel = eventHandlerService.getUserPowerLevel($scope.room_id, user_id); normaliseMembersPowerLevels(); } @@ -431,172 +415,25 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) scrollToBottom(true); // Store the command in the history - history.push(input); + $rootScope.$broadcast("commandHistory:BROADCAST_NEW_HISTORY_ITEM(item)", + input); + var isEmote = input.indexOf("/me ") === 0; var promise; - var cmd; - var args; + if (!isEmote) { + promise = commandsService.processInput($scope.room_id, input); + } var echo = false; - // Check for IRC style commands first - // trim any trailing whitespace, as it can confuse the parser for IRC-style commands - input = input.replace(/\s+$/, ""); - - if (input[0] === "/" && input[1] !== "/") { - var bits = input.match(/^(\S+?)( +(.*))?$/); - cmd = bits[1]; - args = bits[3]; - - console.log("cmd: " + cmd + ", args: " + args); - - switch (cmd) { - case "/me": - promise = matrixService.sendEmoteMessage($scope.room_id, args); - echo = true; - break; - - case "/nick": - // Change user display name - if (args) { - promise = matrixService.setDisplayName(args); - } - else { - $scope.feedback = "Usage: /nick <display_name>"; - } - break; - - case "/join": - // Join a room - if (args) { - var matches = args.match(/^(\S+)$/); - if (matches) { - var room_alias = matches[1]; - if (room_alias.indexOf(':') == -1) { - // FIXME: actually track the :domain style name of our homeserver - // with or without port as is appropriate and append it at this point - } - - var room_id = matrixService.getAliasToRoomIdMapping(room_alias); - console.log("joining " + room_alias + " id=" + room_id); - if ($rootScope.events.rooms[room_id]) { - // don't send a join event for a room you're already in. - $location.url("room/" + room_alias); - } - 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) { - $scope.feedback = "Can't join room: " + JSON.stringify(error.data); - } - ); - } - } - } - else { - $scope.feedback = "Usage: /join <room_alias>"; - } - break; - - case "/kick": - // Kick a user from the room with an optional reason - if (args) { - var matches = args.match(/^(\S+?)( +(.*))?$/); - if (matches) { - promise = matrixService.kick($scope.room_id, matches[1], matches[3]); - } - } - - if (!promise) { - $scope.feedback = "Usage: /kick <userId> [<reason>]"; - } - break; - - case "/ban": - // Ban a user from the room with an optional reason - if (args) { - var matches = args.match(/^(\S+?)( +(.*))?$/); - if (matches) { - promise = matrixService.ban($scope.room_id, matches[1], matches[3]); - } - } - - if (!promise) { - $scope.feedback = "Usage: /ban <userId> [<reason>]"; - } - break; - - case "/unban": - // Unban a user from the room - if (args) { - var matches = args.match(/^(\S+)$/); - if (matches) { - // Reset the user membership to "leave" to unban him - promise = matrixService.unban($scope.room_id, matches[1]); - } - } - - if (!promise) { - $scope.feedback = "Usage: /unban <userId>"; - } - break; - - case "/op": - // Define the power level of a user - if (args) { - var matches = args.match(/^(\S+?)( +(\d+))?$/); - var powerLevel = 50; // default power level for op - if (matches) { - var user_id = matches[1]; - if (matches.length === 4 && undefined !== matches[3]) { - powerLevel = parseInt(matches[3]); - } - if (powerLevel !== NaN) { - promise = matrixService.setUserPowerLevel($scope.room_id, user_id, powerLevel); - } - } - } - - if (!promise) { - $scope.feedback = "Usage: /op <userId> [<power level>]"; - } - break; - - case "/deop": - // Reset the power level of a user - if (args) { - var matches = args.match(/^(\S+)$/); - if (matches) { - promise = matrixService.setUserPowerLevel($scope.room_id, args, undefined); - } - } - - if (!promise) { - $scope.feedback = "Usage: /deop <userId>"; - } - break; - - default: - $scope.feedback = ("Unrecognised IRC-style command: " + cmd); - break; - } - } - // By default send this as a message unless it's an IRC-style command - if (!promise && !cmd) { - // Make the request - promise = matrixService.sendTextMessage($scope.room_id, input); + if (!promise) { // not a non-echoable command echo = true; + if (isEmote) { + promise = matrixService.sendEmoteMessage($scope.room_id, input.substring(4)); + } + else { + promise = matrixService.sendTextMessage($scope.room_id, input); + } } if (echo) { @@ -604,8 +441,8 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) // To do so, create a minimalist fake text message event and add it to the in-memory list of room messages var echoMessage = { content: { - body: (cmd === "/me" ? args : input), - msgtype: (cmd === "/me" ? "m.emote" : "m.text"), + body: (isEmote ? input.substring(4) : input), + msgtype: (isEmote ? "m.emote" : "m.text"), }, origin_server_ts: new Date().getTime(), // fake a timestamp room_id: $scope.room_id, @@ -615,7 +452,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) }; $('#mainInput').val(''); - $rootScope.events.rooms[$scope.room_id].messages.push(echoMessage); + $scope.room.addMessageEvent(echoMessage); scrollToBottom(); } @@ -638,7 +475,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) } }, function(error) { - $scope.feedback = "Request failed: " + error.data.error; + $scope.feedback = error.data.error; if (echoMessage) { // Mark the message as unsent for the rest of the page life @@ -661,7 +498,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) if (room_id_or_alias && '!' === room_id_or_alias[0]) { // Yes. We can go on right now $scope.room_id = room_id_or_alias; - $scope.room_alias = matrixService.getRoomIdToAliasMapping($scope.room_id); + $scope.room_alias = modelService.getRoomIdToAliasMapping($scope.room_id); onInit2(); } else { @@ -703,6 +540,9 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) var onInit2 = function() { console.log("onInit2"); + // ============================= + $scope.room = modelService.getRoom($scope.room_id); + // ============================= // Scroll down as soon as possible so that we point to the last message // if it already exists in memory @@ -715,9 +555,9 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) var needsToJoin = true; // The room members is available in the data fetched by initialSync - if ($rootScope.events.rooms[$scope.room_id]) { + if ($scope.room) { - var messages = $rootScope.events.rooms[$scope.room_id].messages; + var messages = $scope.room.events; if (0 === messages.length || (1 === messages.length && "m.room.member" === messages[0].type && "invite" === messages[0].content.membership && $scope.state.user_id === messages[0].state_key)) { @@ -729,19 +569,19 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) $scope.state.first_pagination = false; } - var members = $rootScope.events.rooms[$scope.room_id].members; + var members = $scope.room.current_room_state.members; // Update the member list for (var i in members) { if (!members.hasOwnProperty(i)) continue; - var member = members[i]; + var member = members[i].event; updateMemberList(member); } // Check if the user has already join the room if ($scope.state.user_id in members) { - if ("join" === members[$scope.state.user_id].membership) { + if ("join" === members[$scope.state.user_id].event.content.membership) { needsToJoin = false; } } @@ -785,10 +625,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) console.log("onInit3"); // Make recents highlight the current room - $scope.recentsSelectedRoomID = $scope.room_id; - - // Init the history for this room - history.init(); + recentsService.setSelectedRoomId($scope.room_id); // Get the up-to-date the current member list matrixService.getMemberList($scope.room_id).then( @@ -822,19 +659,6 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) } ); }; - - $scope.inviteUser = function() { - - matrixService.invite($scope.room_id, $scope.userIDToInvite).then( - function() { - console.log("Invited."); - $scope.feedback = "Invite successfully sent to " + $scope.userIDToInvite; - $scope.userIDToInvite = ""; - }, - function(reason) { - $scope.feedback = "Failure: " + reason.data.error; - }); - }; $scope.leaveRoom = function() { @@ -886,109 +710,51 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) paginate(MESSAGES_PER_PAGINATION); }; - $scope.startVoiceCall = function() { + $scope.checkWebRTC = function() { + if (!$rootScope.isWebRTCSupported()) { + alert("Your browser does not support WebRTC"); + return false; + } + if ($scope.memberCount() != 2) { + alert("WebRTC calls are currently only supported on rooms with two members"); + return false; + } + return true; + }; + + $scope.startVoiceCall = function() { + if (!$scope.checkWebRTC()) return; var call = new MatrixCall($scope.room_id); call.onError = $rootScope.onCallError; call.onHangup = $rootScope.onCallHangup; // remote video element is used for playing audio in voice calls - call.remoteVideoElement = angular.element('#remoteVideo')[0]; + call.remoteVideoSelector = angular.element('#remoteVideo')[0]; call.placeVoiceCall(); $rootScope.currentCall = call; }; $scope.startVideoCall = function() { + if (!$scope.checkWebRTC()) return; + var call = new MatrixCall($scope.room_id); call.onError = $rootScope.onCallError; call.onHangup = $rootScope.onCallHangup; - call.localVideoElement = angular.element('#localVideo')[0]; - call.remoteVideoElement = angular.element('#remoteVideo')[0]; + call.localVideoSelector = '#localVideo'; + call.remoteVideoSelector = '#remoteVideo'; call.placeVideoCall(); $rootScope.currentCall = call; }; - // Manage history of typed messages - // History is saved in sessionStoratge so that it survives when the user - // navigates through the rooms and when it refreshes the page - var history = { - // The list of typed messages. Index 0 is the more recents - data: [], - - // The position in the history currently displayed - position: -1, - - // The message the user has started to type before going into the history - typingMessage: undefined, - - // Init/load data for the current room - init: function() { - var data = sessionStorage.getItem("history_" + $scope.room_id); - if (data) { - this.data = JSON.parse(data); - } - }, - - // Store a message in the history - push: function(message) { - this.data.unshift(message); - - // Update the session storage - sessionStorage.setItem("history_" + $scope.room_id, JSON.stringify(this.data)); - - // Reset history position - this.position = -1; - this.typingMessage = undefined; - }, - - // Move in the history - go: function(offset) { - - if (-1 === this.position) { - // User starts to go to into the history, save the current line - this.typingMessage = $('#mainInput').val(); - } - else { - // If the user modified this line in history, keep the change - this.data[this.position] = $('#mainInput').val(); - } - - // Bounds the new position to valid data - var newPosition = this.position + offset; - newPosition = Math.max(-1, newPosition); - newPosition = Math.min(newPosition, this.data.length - 1); - this.position = newPosition; - - if (-1 !== this.position) { - // Show the message from the history - $('#mainInput').val(this.data[this.position]); - } - else if (undefined !== this.typingMessage) { - // Go back to the message the user started to type - $('#mainInput').val(this.typingMessage); - } - } - }; - - // Make history singleton methods available from HTML - $scope.history = { - goUp: function($event) { - if ($scope.room_id) { - history.go(1); - } - $event.preventDefault(); - }, - goDown: function($event) { - if ($scope.room_id) { - history.go(-1); - } - $event.preventDefault(); - } - }; - $scope.openJson = function(content) { - $scope.event_selected = content; + $scope.event_selected = angular.copy(content); + + // FIXME: Pre-calculated event data should be stripped in a nicer way. + $scope.event_selected.__room_member = undefined; + $scope.event_selected.__target_room_member = undefined; + // scope this so the template can check power levels and enable/disable // buttons - $scope.pow = matrixService.getUserPowerLevel; + $scope.pow = eventHandlerService.getUserPowerLevel; var modalInstance = $modal.open({ templateUrl: 'eventInfoTemplate.html', @@ -1017,13 +783,70 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) }); }; + $scope.openRoomInfo = function() { + $scope.roomInfo = {}; + $scope.roomInfo.newEvent = { + content: {}, + type: "", + state_key: "" + }; + + var stateEvents = $scope.room.current_room_state.state_events; + // The modal dialog will 2-way bind this field, so we MUST make a deep + // copy of the state events else we will be *actually adjusing our view + // of the world* when fiddling with the JSON!! Apparently parse/stringify + // is faster than jQuery's extend when doing deep copies. + $scope.roomInfo.stateEvents = JSON.parse(JSON.stringify(stateEvents)); + var modalInstance = $modal.open({ + templateUrl: 'roomInfoTemplate.html', + controller: 'RoomInfoController', + size: 'lg', + scope: $scope + }); + }; + }]) .controller('EventInfoController', function($scope, $modalInstance) { console.log("Displaying modal dialog for >>>> " + JSON.stringify($scope.event_selected)); $scope.redact = function() { console.log("User level = "+$scope.pow($scope.room_id, $scope.state.user_id)+ - " Redact level = "+$scope.events.rooms[$scope.room_id]["m.room.ops_levels"].content.redact_level); + " Redact level = "+$scope.room.current_room_state.state_events["m.room.ops_levels"].content.redact_level); console.log("Redact event >> " + JSON.stringify($scope.event_selected)); $modalInstance.close("redact"); }; + $scope.dismiss = $modalInstance.dismiss; +}) +.controller('RoomInfoController', function($scope, $modalInstance, $filter, matrixService) { + console.log("Displaying room info."); + + $scope.userIDToInvite = ""; + + $scope.inviteUser = function() { + + matrixService.invite($scope.room_id, $scope.userIDToInvite).then( + function() { + console.log("Invited."); + $scope.feedback = "Invite successfully sent to " + $scope.userIDToInvite; + $scope.userIDToInvite = ""; + }, + function(reason) { + $scope.feedback = "Failure: " + reason.data.error; + }); + }; + + $scope.submit = function(event) { + if (event.content) { + console.log("submit >>> " + JSON.stringify(event.content)); + matrixService.sendStateEvent($scope.room_id, event.type, + event.content, event.state_key).then(function(response) { + $modalInstance.dismiss(); + }, function(err) { + $scope.feedback = err.data.error; + } + ); + } + }; + + $scope.dismiss = $modalInstance.dismiss; + }); diff --git a/webclient/room/room-directive.js b/syweb/webclient/room/room-directive.js
index 05382cfcd3..187032aa88 100644 --- a/webclient/room/room-directive.js +++ b/syweb/webclient/room/room-directive.js
@@ -144,19 +144,106 @@ angular.module('RoomController') }); }; }]) +// A directive which stores text sent into it and restores it via up/down arrows .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); + var BROADCAST_NEW_HISTORY_ITEM = "commandHistory:BROADCAST_NEW_HISTORY_ITEM(item)"; + + // Manage history of typed messages + // History is saved in sessionStorage so that it survives when the user + // navigates through the rooms and when it refreshes the page + var history = { + // The list of typed messages. Index 0 is the more recents + data: [], + + // The position in the history currently displayed + position: -1, + + element: undefined, + roomId: undefined, + + // The message the user has started to type before going into the history + typingMessage: undefined, + + // Init/load data for the current room + init: function(element, roomId) { + this.roomId = roomId; + this.element = element; + var data = sessionStorage.getItem("history_" + this.roomId); + if (data) { + this.data = JSON.parse(data); } - else if (keycodePressed === DOWN_ARROW) { - scope.history.goDown(event); - } - }); + }, + + // Store a message in the history + push: function(message) { + this.data.unshift(message); + + // Update the session storage + sessionStorage.setItem("history_" + this.roomId, JSON.stringify(this.data)); + + // Reset history position + this.position = -1; + this.typingMessage = undefined; + }, + + // Move in the history + go: function(offset) { + + if (-1 === this.position) { + // User starts to go to into the history, save the current line + this.typingMessage = this.element.val(); + } + else { + // If the user modified this line in history, keep the change + this.data[this.position] = this.element.val(); + } + + // Bounds the new position to valid data + var newPosition = this.position + offset; + newPosition = Math.max(-1, newPosition); + newPosition = Math.min(newPosition, this.data.length - 1); + this.position = newPosition; + + if (-1 !== this.position) { + // Show the message from the history + this.element.val(this.data[this.position]); + } + else if (undefined !== this.typingMessage) { + // Go back to the message the user started to type + this.element.val(this.typingMessage); + } + } + }; + + return { + restrict: "AE", + scope: { + roomId: "=commandHistory" + }, + link: function (scope, element, attrs) { + element.bind("keydown", function (event) { + var keycodePressed = event.which; + var UP_ARROW = 38; + var DOWN_ARROW = 40; + if (scope.roomId) { + if (keycodePressed === UP_ARROW) { + history.go(1); + event.preventDefault(); + } + else if (keycodePressed === DOWN_ARROW) { + history.go(-1); + event.preventDefault(); + } + } + }); + + scope.$on(BROADCAST_NEW_HISTORY_ITEM, function(ngEvent, item) { + history.push(item); + }); + + history.init(element, scope.roomId); + }, + } }]) diff --git a/webclient/settings/settings-controller.js b/syweb/webclient/settings/settings-controller.js
index 9cdace704a..9cdace704a 100644 --- a/webclient/settings/settings-controller.js +++ b/syweb/webclient/settings/settings-controller.js
diff --git a/webclient/settings/settings.html b/syweb/webclient/settings/settings.html
index 094c846f8b..094c846f8b 100644 --- a/webclient/settings/settings.html +++ b/syweb/webclient/settings/settings.html
diff --git a/webclient/test/README b/syweb/webclient/test/README
index 1a7bc832c7..e7ed4eaa87 100644 --- a/webclient/test/README +++ b/syweb/webclient/test/README
@@ -1,13 +1,31 @@ -Requires: - - nodejs/npm - - npm install karma +Testing is done using Karma. + + +UNIT TESTING +============ + +Requires the following: + - npm/nodejs + - phantomjs + +Requires the following node packages: - npm install jasmine - - npm install protractor (e2e testing) + - npm install karma + - npm install karma-jasmine + - npm install karma-phantomjs-launcher + - npm install karma-junit-reporter -Setting up continuous integration / run the unit tests (make sure you're in -this directory so it can find the config file): +Make sure you're in this directory so it can find the config file and run: karma start +You should see all the tests pass. + + +E2E TESTING +=========== + +npm install protractor + Setting up e2e tests (only if you don't have a selenium server to run the tests on. If you do, edit the config to point to that url): diff --git a/webclient/test/e2e/home.spec.js b/syweb/webclient/test/e2e/home.spec.js
index 470237d557..470237d557 100644 --- a/webclient/test/e2e/home.spec.js +++ b/syweb/webclient/test/e2e/home.spec.js
diff --git a/webclient/test/karma.conf.js b/syweb/webclient/test/karma.conf.js
index 22c4eaaafa..37a9eaf1c1 100644 --- a/webclient/test/karma.conf.js +++ b/syweb/webclient/test/karma.conf.js
@@ -22,19 +22,27 @@ module.exports = function(config) { '../js/angular-route.js', '../js/angular-animate.js', '../js/angular-sanitize.js', + '../js/jquery.peity.min.js', + '../js/angular-peity.js', '../js/ng-infinite-scroll-matrix.js', - '../login/**/*.*', - '../room/**/*.*', - '../components/**/*.*', - '../user/**/*.*', - '../home/**/*.*', - '../recents/**/*.*', - '../settings/**/*.*', + '../js/ui-bootstrap*', + '../js/elastic.js', + '../login/**/*.js', + '../room/**/*.js', + '../components/**/*.js', + '../user/**/*.js', + '../home/**/*.js', + '../recents/**/*.js', + '../settings/**/*.js', '../app.js', '../app*', './unit/**/*.js' ], + plugins: [ + 'karma-*', + ], + // list of files to exclude exclude: [ @@ -44,14 +52,31 @@ module.exports = function(config) { // preprocess matching files before serving them to the browser // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor preprocessors: { + '../login/**/*.js': 'coverage', + '../room/**/*.js': 'coverage', + '../components/**/*.js': 'coverage', + '../user/**/*.js': 'coverage', + '../home/**/*.js': 'coverage', + '../recents/**/*.js': 'coverage', + '../settings/**/*.js': 'coverage', + '../app.js': 'coverage' }, // test results reporter to use // possible values: 'dots', 'progress' // available reporters: https://npmjs.org/browse/keyword/karma-reporter - reporters: ['progress'], + reporters: ['progress', 'junit', 'coverage'], + junitReporter: { + outputFile: 'test-results.xml', + suite: '' + }, + coverageReporter: { + type: 'cobertura', + dir: 'coverage/', + file: 'coverage.xml' + }, // web server port port: 9876, @@ -72,11 +97,11 @@ module.exports = function(config) { // start these browsers // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher - browsers: ['Chrome'], + browsers: ['PhantomJS'], // Continuous Integration mode // if true, Karma captures browsers, runs the tests and exits - singleRun: false + singleRun: true }); }; diff --git a/webclient/test/protractor.conf.js b/syweb/webclient/test/protractor.conf.js
index 76ae7b712b..76ae7b712b 100644 --- a/webclient/test/protractor.conf.js +++ b/syweb/webclient/test/protractor.conf.js
diff --git a/webclient/test/unit/user-controller.spec.js b/syweb/webclient/test/unit/user-controller.spec.js
index 798cc4de48..798cc4de48 100644 --- a/webclient/test/unit/user-controller.spec.js +++ b/syweb/webclient/test/unit/user-controller.spec.js
diff --git a/webclient/user/user-controller.js b/syweb/webclient/user/user-controller.js
index 0dbfa325d0..0dbfa325d0 100644 --- a/webclient/user/user-controller.js +++ b/syweb/webclient/user/user-controller.js
diff --git a/webclient/user/user.html b/syweb/webclient/user/user.html
index 2aa981437b..2aa981437b 100644 --- a/webclient/user/user.html +++ b/syweb/webclient/user/user.html
diff --git a/webclient/components/matrix/event-handler-service.js b/webclient/components/matrix/event-handler-service.js deleted file mode 100644
index 3b1354cdef..0000000000 --- a/webclient/components/matrix/event-handler-service.js +++ /dev/null
@@ -1,704 +0,0 @@ -/* -Copyright 2014 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -'use strict'; - -/* -This service handles what should happen when you get an event. This service does -not care where the event came from, it only needs enough context to be able to -process them. Events may be coming from the event stream, the REST API (via -direct GETs or via a pagination stream API), etc. - -Typically, this service will store events or broadcast them to any listeners -(e.g. controllers) via $broadcast. Alternatively, it may update the $rootScope -if typically all the $on method would do is update its own $scope. -*/ -angular.module('eventHandlerService', []) -.factory('eventHandlerService', ['matrixService', '$rootScope', '$q', '$timeout', 'mPresence', -function(matrixService, $rootScope, $q, $timeout, mPresence) { - var ROOM_CREATE_EVENT = "ROOM_CREATE_EVENT"; - 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 TOPIC_EVENT = "TOPIC_EVENT"; - var RESET_EVENT = "RESET_EVENT"; // eventHandlerService has been resetted - - // used for dedupping events - could be expanded in future... - // FIXME: means that we leak memory over time (along with lots of the rest - // of the app, given we never try to reap memory yet) - var eventMap = {}; - - $rootScope.presence = {}; - - // TODO: This is attached to the rootScope so .html can just go containsBingWord - // for determining classes so it is easy to highlight bing messages. It seems a - // bit strange to put the impl in this service though, but I can't think of a better - // file to put it in. - $rootScope.containsBingWord = function(content) { - if (!content || $.type(content) != "string") { - return false; - } - var bingWords = matrixService.config().bingWords; - var shouldBing = false; - - // case-insensitive name check for user_id OR display_name if they exist - var userRegex = ""; - var myUserId = matrixService.config().user_id; - if (myUserId) { - var localpart = getLocalPartFromUserId(myUserId); - if (localpart) { - localpart = localpart.toLocaleLowerCase(); - userRegex += "\\b" + localpart + "\\b"; - } - } - var myDisplayName = matrixService.config().display_name; - if (myDisplayName) { - myDisplayName = myDisplayName.toLocaleLowerCase(); - if (userRegex.length > 0) { - userRegex += "|"; - } - userRegex += "\\b" + myDisplayName + "\\b"; - } - - var r = new RegExp(userRegex, 'i'); - if (content.search(r) >= 0) { - shouldBing = true; - } - - if ( (myDisplayName && content.toLocaleLowerCase().indexOf(myDisplayName) != -1) || - (myUserId && content.toLocaleLowerCase().indexOf(myUserId) != -1) ) { - shouldBing = true; - } - - // bing word list check - if (bingWords && !shouldBing) { - for (var i=0; i<bingWords.length; i++) { - var re = RegExp(bingWords[i]); - if (content.search(re) != -1) { - shouldBing = true; - break; - } - } - } - return shouldBing; - }; - - var getLocalPartFromUserId = function(user_id) { - if (!user_id) { - return null; - } - var localpartRegex = /@(.*):\w+/i - var results = localpartRegex.exec(user_id); - if (results && results.length == 2) { - return results[1]; - } - return null; - }; - - var initialSyncDeferred; - - var reset = function() { - initialSyncDeferred = $q.defer(); - - $rootScope.events = { - rooms: {} // will contain roomId: { messages:[], members:{userid1: event} } - }; - - $rootScope.presence = {}; - - eventMap = {}; - }; - reset(); - - var initRoom = function(room_id, room) { - if (!(room_id in $rootScope.events.rooms)) { - console.log("Creating new rooms entry for " + room_id); - $rootScope.events.rooms[room_id] = { - room_id: room_id, - messages: [], - members: {}, - // Pagination information - pagination: { - earliest_token: "END" // how far back we've paginated - } - }; - } - - if (room) { // we got an existing room object from initialsync, seemingly. - // Report all other metadata of the room object (membership, inviter, visibility, ...) - for (var field in room) { - if (!room.hasOwnProperty(field)) continue; - - if (-1 === ["room_id", "messages", "state"].indexOf(field)) { // why indexOf - why not ===? --Matthew - $rootScope.events.rooms[room_id][field] = room[field]; - } - } - $rootScope.events.rooms[room_id].membership = room.membership; - } - }; - - var resetRoomMessages = function(room_id) { - if ($rootScope.events.rooms[room_id]) { - $rootScope.events.rooms[room_id].messages = []; - } - }; - - // Generic method to handle events data - var handleRoomDateEvent = function(event, isLiveEvent, addToRoomMessages) { - // Add topic changes as if they were a room message - if (addToRoomMessages) { - if (isLiveEvent) { - $rootScope.events.rooms[event.room_id].messages.push(event); - } - else { - $rootScope.events.rooms[event.room_id].messages.unshift(event); - } - } - - // live events always update, but non-live events only update if the - // ts is later. - var latestData = true; - if (!isLiveEvent) { - var eventTs = event.origin_server_ts; - var storedEvent = $rootScope.events.rooms[event.room_id][event.type]; - if (storedEvent) { - if (storedEvent.origin_server_ts > eventTs) { - // ignore it, we have a newer one already. - latestData = false; - } - } - } - if (latestData) { - $rootScope.events.rooms[event.room_id][event.type] = event; - } - }; - - var handleRoomCreate = function(event, isLiveEvent) { - // For now, we do not use the event data. Simply signal it to the app controllers - $rootScope.$broadcast(ROOM_CREATE_EVENT, event, isLiveEvent); - }; - - var handleRoomAliases = function(event, isLiveEvent) { - matrixService.createRoomIdToAliasMapping(event.room_id, event.content.aliases[0]); - }; - - var handleMessage = function(event, isLiveEvent) { - // Check for empty event content - var hasContent = false; - for (var prop in event.content) { - hasContent = true; - break; - } - if (!hasContent) { - // empty json object is a redacted event, so ignore. - return; - } - - if (isLiveEvent) { - if (event.user_id === matrixService.config().user_id && - (event.content.msgtype === "m.text" || event.content.msgtype === "m.emote") ) { - // Assume we've already echoed it. So, there is a fake event in the messages list of the room - // Replace this fake event by the true one - var index = getRoomEventIndex(event.room_id, event.event_id); - if (index) { - $rootScope.events.rooms[event.room_id].messages[index] = event; - } - else { - $rootScope.events.rooms[event.room_id].messages.push(event); - } - } - else { - $rootScope.events.rooms[event.room_id].messages.push(event); - } - - if (window.Notification && event.user_id != matrixService.config().user_id) { - var shouldBing = $rootScope.containsBingWord(event.content.body); - - // Ideally we would notify only when the window is hidden (i.e. document.hidden = true). - // - // However, Chrome on Linux and OSX currently returns document.hidden = false unless the window is - // explicitly showing a different tab. So we need another metric to determine hiddenness - we - // simply use idle time. If the user has been idle enough that their presence goes to idle, then - // we also display notifs when things happen. - // - // This is far far better than notifying whenever anything happens anyway, otherwise you get spammed - // to death with notifications when the window is in the foreground, which is horrible UX (especially - // if you have not defined any bingers and so get notified for everything). - var isIdle = (document.hidden || matrixService.presence.unavailable === mPresence.getState()); - - // We need a way to let people get notifications for everything, if they so desire. The way to do this - // is to specify zero bingwords. - var bingWords = matrixService.config().bingWords; - if (bingWords === undefined || bingWords.length === 0) { - shouldBing = true; - } - - if (shouldBing && isIdle) { - console.log("Displaying notification for "+JSON.stringify(event)); - 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") { - message = "* " + displayname + " " + message; - } - - var roomTitle = matrixService.getRoomIdToAliasMapping(event.room_id); - var theRoom = $rootScope.events.rooms[event.room_id]; - if (!roomTitle && theRoom && theRoom["m.room.name"] && theRoom["m.room.name"].content) { - roomTitle = theRoom["m.room.name"].content.name; - } - - if (!roomTitle) { - roomTitle = event.room_id; - } - - var notification = new window.Notification( - displayname + - " (" + roomTitle + ")", - { - "body": message, - "icon": member ? member.avatar_url : undefined - }); - - notification.onclick = function() { - console.log("notification.onclick() room=" + event.room_id); - $rootScope.goToPage('room/' + (event.room_id)); - }; - - $timeout(function() { - notification.close(); - }, 5 * 1000); - } - } - } - else { - $rootScope.events.rooms[event.room_id].messages.unshift(event); - } - - // TODO send delivery receipt if isLiveEvent - - // $broadcast this, as controllers may want to do funky things such as - // scroll to the bottom, etc which cannot be expressed via simple $scope - // updates. - $rootScope.$broadcast(MSG_EVENT, event, isLiveEvent); - }; - - var handleRoomMember = function(event, isLiveEvent, isStateEvent) { - - // add membership changes as if they were a room message if something interesting changed - // Exception: Do not do this if the event is a room state event because such events already come - // as room messages events. Moreover, when they come as room messages events, they are relatively ordered - // with other other room messages - if (!isStateEvent) { - // could be a membership change, display name change, etc. - // Find out which one. - var memberChanges = undefined; - if ((event.prev_content === undefined && event.content.membership) || (event.prev_content && (event.prev_content.membership !== event.content.membership))) { - memberChanges = "membership"; - } - else if (event.prev_content && (event.prev_content.displayname !== event.content.displayname)) { - memberChanges = "displayname"; - } - - // mark the key which changed - event.changedKey = memberChanges; - - // If there was a change we want to display, dump it in the message - // list. - if (memberChanges) { - if (isLiveEvent) { - $rootScope.events.rooms[event.room_id].messages.push(event); - } - else { - $rootScope.events.rooms[event.room_id].messages.unshift(event); - } - } - } - - // Use data from state event or the latest data from the stream. - // Do not care of events that come when paginating back - if (isStateEvent || isLiveEvent) { - $rootScope.events.rooms[event.room_id].members[event.state_key] = event; - } - - $rootScope.$broadcast(MEMBER_EVENT, event, isLiveEvent, isStateEvent); - }; - - var handlePresence = function(event, isLiveEvent) { - $rootScope.presence[event.content.user_id] = event; - $rootScope.$broadcast(PRESENCE_EVENT, event, isLiveEvent); - }; - - var handlePowerLevels = function(event, isLiveEvent) { - // Keep the latest data. Do not care of events that come when paginating back - if (!$rootScope.events.rooms[event.room_id][event.type] || isLiveEvent) { - $rootScope.events.rooms[event.room_id][event.type] = event; - $rootScope.$broadcast(POWERLEVEL_EVENT, event, isLiveEvent); - } - }; - - var handleRoomName = function(event, isLiveEvent, isStateEvent) { - console.log("handleRoomName room_id: " + event.room_id + " - isLiveEvent: " + isLiveEvent + " - name: " + event.content.name); - handleRoomDateEvent(event, isLiveEvent, !isStateEvent); - $rootScope.$broadcast(NAME_EVENT, event, isLiveEvent); - }; - - - var handleRoomTopic = function(event, isLiveEvent, isStateEvent) { - console.log("handleRoomTopic room_id: " + event.room_id + " - isLiveEvent: " + isLiveEvent + " - topic: " + event.content.topic); - handleRoomDateEvent(event, isLiveEvent, !isStateEvent); - $rootScope.$broadcast(TOPIC_EVENT, event, isLiveEvent); - }; - - var handleCallEvent = function(event, isLiveEvent) { - $rootScope.$broadcast(CALL_EVENT, event, isLiveEvent); - if (event.type === 'm.call.invite') { - $rootScope.events.rooms[event.room_id].messages.push(event); - } - }; - - var handleRedaction = function(event, isLiveEvent) { - if (!isLiveEvent) { - // we have nothing to remove, so just ignore it. - console.log("Received redacted event: "+JSON.stringify(event)); - return; - } - - // we need to remove something possibly: do we know the redacted - // event ID? - if (eventMap[event.redacts]) { - // remove event from list of messages in this room. - var eventList = $rootScope.events.rooms[event.room_id].messages; - for (var i=0; i<eventList.length; i++) { - if (eventList[i].event_id === event.redacts) { - console.log("Removing event " + event.redacts); - eventList.splice(i, 1); - break; - } - } - - // broadcast the redaction so controllers can nuke this - console.log("Redacted an event."); - } - } - - /** - * Get the index of the event in $rootScope.events.rooms[room_id].messages - * @param {type} room_id the room id - * @param {type} event_id the event id to look for - * @returns {Number | undefined} the index. undefined if not found. - */ - var getRoomEventIndex = function(room_id, event_id) { - var index; - - var room = $rootScope.events.rooms[room_id]; - if (room) { - // Start looking from the tail since the first goal of this function - // is to find a messaged among the latest ones - for (var i = room.messages.length - 1; i > 0; i--) { - var message = room.messages[i]; - if (event_id === message.event_id) { - index = i; - break; - } - } - } - return index; - }; - - /** - * Get the member object of a room member - * @param {String} room_id the room id - * @param {String} user_id the id of the user - * @returns {undefined | Object} the member object of this user in this room if he is part of the room - */ - var getMember = function(room_id, user_id) { - var member; - - var room = $rootScope.events.rooms[room_id]; - if (room) { - member = room.members[user_id]; - } - return member; - }; - - /** - * Return the display name of an user acccording to data already downloaded - * @param {String} room_id the room id - * @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, - MEMBER_EVENT: MEMBER_EVENT, - PRESENCE_EVENT: PRESENCE_EVENT, - POWERLEVEL_EVENT: POWERLEVEL_EVENT, - CALL_EVENT: CALL_EVENT, - NAME_EVENT: NAME_EVENT, - TOPIC_EVENT: TOPIC_EVENT, - RESET_EVENT: RESET_EVENT, - - reset: function() { - reset(); - $rootScope.$broadcast(RESET_EVENT); - }, - - initRoom: function(room) { - initRoom(room.room_id, room); - }, - - handleEvent: function(event, isLiveEvent, isStateEvent) { - - // FIXME: /initialSync on a particular room is not yet available - // So initRoom on a new room is not called. Make sure the room data is initialised here - if (event.room_id) { - initRoom(event.room_id); - } - - // Avoid duplicated events - // Needed for rooms where initialSync has not been done. - // In this case, we do not know where to start pagination. So, it starts from the END - // and we can have the same event (ex: joined, invitation) coming from the pagination - // AND from the event stream. - // FIXME: This workaround should be no more required when /initialSync on a particular room - // will be available (as opposite to the global /initialSync done at startup) - if (!isStateEvent) { // Do not consider state events - if (event.event_id && eventMap[event.event_id]) { - console.log("discarding duplicate event: " + JSON.stringify(event, undefined, 4)); - return; - } - else { - eventMap[event.event_id] = 1; - } - } - - if (event.type.indexOf('m.call.') === 0) { - handleCallEvent(event, isLiveEvent); - } - else { - switch(event.type) { - case "m.room.create": - handleRoomCreate(event, isLiveEvent); - break; - case "m.room.aliases": - handleRoomAliases(event, isLiveEvent); - break; - case "m.room.message": - handleMessage(event, isLiveEvent); - break; - case "m.room.member": - handleRoomMember(event, isLiveEvent, isStateEvent); - break; - case "m.presence": - handlePresence(event, isLiveEvent); - break; - case 'm.room.ops_levels': - case 'm.room.send_event_level': - case 'm.room.add_state_level': - case 'm.room.join_rules': - case 'm.room.power_levels': - handlePowerLevels(event, isLiveEvent); - break; - case 'm.room.name': - handleRoomName(event, isLiveEvent, isStateEvent); - break; - case 'm.room.topic': - handleRoomTopic(event, isLiveEvent, isStateEvent); - break; - case 'm.room.redaction': - handleRedaction(event, isLiveEvent); - break; - default: - console.log("Unable to handle event type " + event.type); - console.log(JSON.stringify(event, undefined, 4)); - break; - } - } - }, - - // isLiveEvents determines whether notifications should be shown, whether - // messages get appended to the start/end of lists, etc. - handleEvents: function(events, isLiveEvents, isStateEvents) { - for (var i=0; i<events.length; i++) { - this.handleEvent(events[i], isLiveEvents, isStateEvents); - } - }, - - // Handle messages from /initialSync or /messages - handleRoomMessages: function(room_id, messages, isLiveEvents, dir) { - initRoom(room_id); - - var events = messages.chunk; - - // Handles messages according to their time order - if (dir && 'b' === dir) { - // paginateBackMessages requests messages to be in reverse chronological order - for (var i=0; i<events.length; i++) { - this.handleEvent(events[i], isLiveEvents, isLiveEvents); - } - - // Store how far back we've paginated - $rootScope.events.rooms[room_id].pagination.earliest_token = messages.end; - } - else { - // InitialSync returns messages in chronological order - for (var i=events.length - 1; i>=0; i--) { - this.handleEvent(events[i], isLiveEvents, isLiveEvents); - } - // Store where to start pagination - $rootScope.events.rooms[room_id].pagination.earliest_token = messages.start; - } - }, - - handleInitialSyncDone: function(initialSyncData) { - console.log("# handleInitialSyncDone"); - initialSyncDeferred.resolve(initialSyncData); - }, - - // Returns a promise that resolves when the initialSync request has been processed - waitForInitialSyncCompletion: function() { - return initialSyncDeferred.promise; - }, - - resetRoomMessages: function(room_id) { - resetRoomMessages(room_id); - }, - - /** - * Return the last message event of a room - * @param {String} room_id the room id - * @param {Boolean} filterFake true to not take into account fake messages - * @returns {undefined | Event} the last message event if available - */ - getLastMessage: function(room_id, filterEcho) { - var lastMessage; - - var room = $rootScope.events.rooms[room_id]; - if (room) { - for (var i = room.messages.length - 1; i >= 0; i--) { - var message = room.messages[i]; - - if (!filterEcho || undefined === message.echo_msg_state) { - lastMessage = message; - break; - } - } - } - - return lastMessage; - }, - - /** - * Compute the room users number, ie the number of members who has joined the room. - * @param {String} room_id the room id - * @returns {undefined | Number} the room users number if available - */ - getUsersCountInRoom: function(room_id) { - var memberCount; - - var room = $rootScope.events.rooms[room_id]; - if (room) { - memberCount = 0; - - for (var i in room.members) { - if (!room.members.hasOwnProperty(i)) continue; - - var member = room.members[i]; - - if ("join" === member.membership) { - memberCount = memberCount + 1; - } - } - } - - return memberCount; - }, - - /** - * Get the member object of a room member - * @param {String} room_id the room id - * @param {String} user_id the id of the user - * @returns {undefined | Object} the member object of this user in this room if he is part of the room - */ - getMember: function(room_id, user_id) { - 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; - } - initRoom(room_id); - - var room = $rootScope.events.rooms[room_id]; - if (room) { - room.visibility = visible; - } - } - }; -}]); diff --git a/webclient/components/matrix/matrix-filter.js b/webclient/components/matrix/matrix-filter.js deleted file mode 100644
index 3d64a569a1..0000000000 --- a/webclient/components/matrix/matrix-filter.js +++ /dev/null
@@ -1,146 +0,0 @@ -/* - Copyright 2014 OpenMarket Ltd - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -'use strict'; - -angular.module('matrixFilter', []) - -// Compute the room name according to information we have -.filter('mRoomName', ['$rootScope', 'matrixService', 'eventHandlerService', function($rootScope, matrixService, eventHandlerService) { - return function(room_id) { - var roomName; - - // If there is an alias, use it - // TODO: only one alias is managed for now - var alias = matrixService.getRoomIdToAliasMapping(room_id); - - var room = $rootScope.events.rooms[room_id]; - 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 && !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) { - roomName = eventHandlerService.getUserDisplayName(room_id, member.state_key); - break; - } - } - } - else if (Object.keys(room.members).length <= 1) { - - var otherUserId; - - if (Object.keys(room.members)[0]) { - otherUserId = Object.keys(room.members)[0]; - // this could be an invite event (from event stream) - if (otherUserId === user_id && - room.members[user_id].content.membership === "invite") { - // this is us being invited to this room, so the - // *user_id* is the other user ID and not the state - // key. - otherUserId = room.members[user_id].user_id; - } - } - else { - // it's got to be an invite, or failing that a self-chat; - otherUserId = room.inviter || user_id; -/* - // XXX: This should all be unnecessary now thanks to using the /rooms/<room>/roomid API - - // The other member may be in the invite list, get all invited users - var invitedUserIDs = []; - - // XXX: *SURELY* we shouldn't have to trawl through the whole messages list to - // find invite - surely the other user should be in room.members with state invited? :/ --Matthew - for (var i in room.messages) { - var message = room.messages[i]; - if ("m.room.member" === message.type && "invite" === message.content.membership) { - // Filter out the current user - var member_id = message.state_key; - if (member_id === user_id) { - member_id = message.user_id; - } - if (member_id !== user_id) { - // Make sure there is no duplicate user - if (-1 === invitedUserIDs.indexOf(member_id)) { - invitedUserIDs.push(member_id); - } - } - } - } - - // For now, only 1:1 room needs to be renamed. It means only 1 invited user - if (1 === invitedUserIDs.length) { - otherUserId = invitedUserIDs[0]; - } -*/ - } - - // Get the user display name - roomName = eventHandlerService.getUserDisplayName(room_id, otherUserId); - } - } - } - - // Always show the alias in the room displayed name - if (roomName && alias && alias !== roomName) { - roomName += " (" + alias + ")"; - } - - if (undefined === roomName) { - // By default, use the room ID - roomName = room_id; - - // XXX: this is *INCREDIBLY* heavy logging for a function that calls every single - // time any kind of digest runs which refreshes a room name... - // commenting it out for now. - - // Log some information that lead to this leak - // console.log("Room ID leak for " + room_id); - // console.log("room object: " + JSON.stringify(room, undefined, 4)); - } - - return roomName; - }; -}]) - -// Return the user display name -.filter('mUserDisplayName', ['eventHandlerService', function(eventHandlerService) { - return function(user_id, room_id) { - return eventHandlerService.getUserDisplayName(room_id, user_id); - }; -}]); diff --git a/webclient/img/red_phone.png b/webclient/img/red_phone.png deleted file mode 100644
index 11fc44940c..0000000000 --- a/webclient/img/red_phone.png +++ /dev/null
Binary files differdiff --git a/webclient/recents/recents-controller.js b/webclient/recents/recents-controller.js deleted file mode 100644
index ee8a41c366..0000000000 --- a/webclient/recents/recents-controller.js +++ /dev/null
@@ -1,31 +0,0 @@ -/* - Copyright 2014 OpenMarket Ltd - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -'use strict'; - -angular.module('RecentsController', ['matrixService', 'matrixFilter']) -.controller('RecentsController', ['$rootScope', '$scope', 'eventHandlerService', - function($rootScope, $scope, eventHandlerService) { - - // Expose the service to the view - $scope.eventHandlerService = eventHandlerService; - - // $rootScope of the parent where the recents component is included can override this value - // in order to highlight a specific room in the list - $rootScope.recentsSelectedRoomID; - -}]); - diff --git a/webclient/room/room.html b/webclient/room/room.html deleted file mode 100644
index 38b6d591ea..0000000000 --- a/webclient/room/room.html +++ /dev/null
@@ -1,232 +0,0 @@ -<div ng-controller="RoomController" data-ng-init="onInit()" class="room" style="height: 100%;"> - - <script type="text/ng-template" id="eventInfoTemplate.html"> - <div class="modal-body"> - <pre> {{event_selected | json}} </pre> - </div> - <div class="modal-footer"> - <button ng-click="redact()" type="button" class="btn btn-danger" - ng-disabled="!events.rooms[room_id]['m.room.ops_levels'].content.redact_level || !pow(room_id, state.user_id) || pow(room_id, state.user_id) < events.rooms[room_id]['m.room.ops_levels'].content.redact_level" - title="Delete this event on all home servers. This cannot be undone."> - Redact - </button> - </div> - </script> - - <div id="roomHeader"> - <a href ng-click="goToPage('/')"><img src="img/logo-small.png" width="100" height="43" alt="[matrix]"/></a> - <div class="roomHeaderInfo"> - - <div class="roomNameSection"> - <div ng-hide="name.isEditing" ng-dblclick="name.editName()" id="roomName"> - {{ room_id | mRoomName }} - </div> - <form ng-submit="name.updateName()" ng-show="name.isEditing" class="roomNameForm"> - <input ng-model="name.newNameText" ng-blur="name.cancelEdit()" class="roomNameInput" placeholder="Room name"/> - </form> - </div> - - <div class="roomTopicSection"> - <button ng-hide="events.rooms[room_id]['m.room.topic'].content.topic || topic.isEditing" - ng-click="topic.editTopic()" class="roomTopicSetNew"> - Set Topic - </button> - <div ng-show="events.rooms[room_id]['m.room.topic'].content.topic || topic.isEditing"> - <div ng-hide="topic.isEditing" ng-dblclick="topic.editTopic()" id="roomTopic"> - {{ events.rooms[room_id]['m.room.topic'].content.topic | limitTo: 200}} - </div> - <form ng-submit="topic.updateTopic()" ng-show="topic.isEditing" class="roomTopicForm"> - <input ng-model="topic.newTopicText" ng-blur="topic.cancelEdit()" class="roomTopicInput" placeholder="Topic"/> - </form> - </div> - </div> - </div> - </div> - - <div id="roomPage"> - <div id="roomWrapper"> - - <div id="roomRecentsTableWrapper"> - <div ng-include="'recents/recents.html'"></div> - </div> - - <div id="usersTableWrapper" ng-hide="state.permission_denied"> - <table id="usersTable"> - <tr ng-repeat="member in members | orderMembersList"> - <td class="userAvatar mouse-pointer" ng-click="$parent.goToUserPage(member.id)" ng-class="member.membership == 'invite' ? 'invited' : ''"> - <img class="userAvatarImage" - ng-src="{{member.avatar_url || 'img/default-profile.png'}}" - alt="{{ member.displayname || member.id.substr(0, member.id.indexOf(':')) }}" - title="{{ member.id }} - power: {{ member.powerLevel }}" - 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"> - <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> - </td> - </table> - </div> - - <div id="messageTableWrapper" - ng-hide="state.permission_denied" - ng-style="{ 'visibility': state.messages_visibility }" - keep-scroll> - <table id="messageTable" infinite-scroll="paginateMore()"> - <tr ng-repeat="msg in events.rooms[room_id].messages" - ng-class="(events.rooms[room_id].messages[$index + 1].user_id !== msg.user_id ? 'differentUser' : '') + (msg.user_id === state.user_id ? ' mine' : '')" scroll-item> - <td class="leftBlock"> - <div class="sender" ng-hide="events.rooms[room_id].messages[$index - 1].user_id === msg.user_id"> {{ msg.user_id | mUserDisplayName: room_id }}</div> - <div class="timestamp" - ng-class="msg.echo_msg_state"> - {{ (msg.origin_server_ts) | date:'MMM d HH:mm' }} - </div> - </td> - <td class="avatar"> - <img class="avatarImage" ng-src="{{ members[msg.user_id].avatar_url || 'img/default-profile.png' }}" width="32" height="32" - ng-hide="events.rooms[room_id].messages[$index - 1].user_id === msg.user_id || msg.user_id === state.user_id"/> - </td> - <td ng-class="(!msg.content.membership && ('m.room.topic' !== msg.type && 'm.room.name' !== msg.type))? (msg.content.msgtype === 'm.emote' ? 'emote text' : 'text') : 'membership text'"> - <div class="bubble" ng-click="openJson(msg)"> - <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 && msg.changedKey === '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 && msg.prev_content"> - {{ members[msg.user_id].displayname || msg.user_id }} - {{ {"invite": "kicked", "join": "kicked", "ban": "unbanned"}[msg.prev_content.membership] }} - {{ members[msg.state_key].displayname || msg.state_key }} - <span ng-if="'join' === msg.prev_content.membership && msg.content.reason"> - : {{ msg.content.reason }} - </span> - </span> - </span> - <span ng-if="'invite' === msg.content.membership && msg.changedKey === 'membership' || - 'ban' === msg.content.membership && msg.changedKey === 'membership'"> - {{ members[msg.user_id].displayname || msg.user_id }} - {{ {"invite": "invited", "ban": "banned"}[msg.content.membership] }} - {{ members[msg.state_key].displayname || msg.state_key }} - <span ng-if="msg.prev_content && 'ban' === msg.prev_content.membership && msg.content.reason"> - : {{ 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" - ng-bind-html="'* ' + (members[msg.user_id].displayname || msg.user_id) + ' ' + msg.content.body | linky:'_blank'" - /> - - <span ng-show='msg.content.msgtype === "m.text"' - class="message" - ng-class="containsBingWord(msg.content.body) && msg.user_id != state.user_id ? msg.echo_msg_state + ' messageBing' : msg.echo_msg_state" - ng-bind-html="(msg.content.msgtype === 'm.text' && msg.type === 'm.room.message' && msg.content.format === 'org.matrix.custom.html') ? - (msg.content.formatted_body | unsanitizedLinky) : - (msg.content.msgtype === 'm.text' && msg.type === 'm.room.message') ? (msg.content.body | linky:'_blank') : '' "/> - - <span ng-show='msg.type === "m.call.invite" && msg.user_id == state.user_id'>Outgoing Call{{ isWebRTCSupported ? '' : ' (But your browser does not support VoIP)' }}</span> - <span ng-show='msg.type === "m.call.invite" && msg.user_id != state.user_id'>Incoming Call{{ isWebRTCSupported ? '' : ' (But your browser does not support VoIP)' }}</span> - - <div ng-show='msg.content.msgtype === "m.image"'> - <div ng-hide='msg.content.thumbnail_url' ng-style="msg.content.body.h && { 'height' : (msg.content.body.h < 320) ? msg.content.body.h : 320}"> - <img class="image" ng-src="{{ msg.content.url }}"/> - </div> - <div ng-show='msg.content.thumbnail_url' ng-style="{ 'height' : msg.content.thumbnail_info.h }"> - <img class="image mouse-pointer" ng-src="{{ msg.content.thumbnail_url }}" - ng-click="$parent.fullScreenImageURL = msg.content.url; $event.stopPropagation();"/> - </div> - </div> - - <span ng-if="'m.room.topic' === msg.type"> - {{ members[msg.user_id].displayname || msg.user_id }} changed the topic to: {{ msg.content.topic }} - </span> - - <span ng-if="'m.room.name' === msg.type"> - {{ members[msg.user_id].displayname || msg.user_id }} changed the room name to: {{ msg.content.name }} - </span> - - </div> - </td> - <td class="rightBlock"> - <img class="avatarImage" ng-src="{{ members[msg.user_id].avatar_url || 'img/default-profile.png' }}" width="32" height="32" - ng-hide="events.rooms[room_id].messages[$index - 1].user_id === msg.user_id || msg.user_id !== state.user_id"/> - </td> - </tr> - </table> - </div> - - <div ng-show="state.permission_denied"> - {{ state.permission_denied }} - </div> - - </div> - </div> - - <div id="controlPanel"> - <div id="controls"> - <table id="inputBarTable"> - <tr> - <td id="userIdCell" width="1px"> - {{ state.user_id }} - </td> - <td width="*"> - <textarea id="mainInput" rows="1" ng-enter="send()" - ng-disabled="state.permission_denied" - ng-focus="true" autocomplete="off" tab-complete command-history/> - </td> - <td id="buttonsCell"> - <button ng-click="send()" ng-disabled="state.permission_denied">Send</button> - <button m-file-input="imageFileToSend" class="extraControls" ng-disabled="state.permission_denied">Image</button> - </td> - </tr> - </table> - - <div class="extraControls"> - <span> - Invite a user: - <input ng-model="userIDToInvite" size="32" type="text" ng-enter="inviteUser()" ng-disabled="state.permission_denied" placeholder="User ID (ex:@user:homeserver)"/> - <button ng-click="inviteUser()" ng-disabled="state.permission_denied">Invite</button> - </span> - <button ng-click="leaveRoom()" ng-disabled="state.permission_denied">Leave</button> - <button ng-click="startVoiceCall()" - ng-show="(currentCall == undefined || currentCall.state == 'ended')" - ng-disabled="state.permission_denied || !isWebRTCSupported || memberCount() != 2" - title ="{{ !isWebRTCSupported ? 'VoIP requires webRTC but your browser does not support it' : (memberCount() == 2 ? '' : 'VoIP calls can only be made in rooms with two participants') }}" - > - Voice Call - </button> - <button ng-click="startVideoCall()" - ng-show="(currentCall == undefined || currentCall.state == 'ended')" - ng-disabled="state.permission_denied || !isWebRTCSupported || memberCount() != 2" - title ="{{ !isWebRTCSupported ? 'VoIP requires webRTC but your browser does not support it' : (memberCount() == 2 ? '' : 'VoIP calls can only be made in rooms with two participants') }}" - > - Video Call - </button> - </div> - - {{ feedback }} - <div ng-show="state.stream_failure"> - {{ state.stream_failure.data.error || "Connection failure" }} - </div> - </div> - </div> - - <div id="room-fullscreen-image" ng-show="fullScreenImageURL" ng-click="fullScreenImageURL = undefined;"> - <img ng-src="{{ fullScreenImageURL }}"/> - </div> - - </div>