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>
|