diff --git a/webclient/components/matrix/event-handler-service.js b/webclient/components/matrix/event-handler-service.js
index 492ec08bca..e63584510b 100644
--- a/webclient/components/matrix/event-handler-service.js
+++ b/webclient/components/matrix/event-handler-service.js
@@ -27,8 +27,8 @@ Typically, this service will store events or broadcast them to any listeners
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) {
+.factory('eventHandlerService', ['matrixService', '$rootScope', '$q', '$timeout', 'mPresence', 'notificationService',
+function(matrixService, $rootScope, $q, $timeout, mPresence, notificationService) {
var ROOM_CREATE_EVENT = "ROOM_CREATE_EVENT";
var MSG_EVENT = "MSG_EVENT";
var MEMBER_EVENT = "MEMBER_EVENT";
@@ -45,44 +45,6 @@ function(matrixService, $rootScope, $q, $timeout, mPresence) {
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 myUserId = matrixService.config().user_id;
- if (myUserId) {
- myUserId = myUserId.toLocaleLowerCase();
- }
- var myDisplayName = matrixService.config().display_name;
- if (myDisplayName) {
- myDisplayName = myDisplayName.toLocaleLowerCase();
- }
- 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 initialSyncDeferred;
@@ -172,6 +134,17 @@ function(matrixService, $rootScope, $q, $timeout, mPresence) {
};
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") ) {
@@ -190,7 +163,12 @@ function(matrixService, $rootScope, $q, $timeout, mPresence) {
}
if (window.Notification && event.user_id != matrixService.config().user_id) {
- var shouldBing = $rootScope.containsBingWord(event.content.body);
+ var shouldBing = notificationService.containsBingWord(
+ matrixService.config().user_id,
+ matrixService.config().display_name,
+ matrixService.config().bingWords,
+ event.content.body
+ );
// Ideally we would notify only when the window is hidden (i.e. document.hidden = true).
//
@@ -220,17 +198,29 @@ function(matrixService, $rootScope, $q, $timeout, mPresence) {
if (event.content.msgtype === "m.emote") {
message = "* " + displayname + " " + message;
}
+ else if (event.content.msgtype === "m.image") {
+ message = displayname + " sent an image.";
+ }
+
+ 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;
+ }
- var notification = new window.Notification(
- displayname +
- " (" + (matrixService.getRoomIdToAliasMapping(event.room_id) || event.room_id) + ")", // FIXME: don't leak room_ids here
- {
- "body": message,
- "icon": member ? member.avatar_url : undefined
- });
- $timeout(function() {
- notification.close();
- }, 5 * 1000);
+ if (!roomTitle) {
+ roomTitle = event.room_id;
+ }
+
+ notificationService.showNotification(
+ displayname + " (" + roomTitle + ")",
+ message,
+ member ? member.avatar_url : undefined,
+ function() {
+ console.log("notification.onclick() room=" + event.room_id);
+ $rootScope.goToPage('room/' + event.room_id);
+ }
+ );
}
}
}
@@ -256,7 +246,7 @@ function(matrixService, $rootScope, $q, $timeout, mPresence) {
// could be a membership change, display name change, etc.
// Find out which one.
var memberChanges = undefined;
- if (event.content.prev !== event.content.membership) {
+ 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)) {
@@ -319,6 +309,31 @@ function(matrixService, $rootScope, $q, $timeout, mPresence) {
$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
@@ -481,7 +496,17 @@ function(matrixService, $rootScope, $q, $timeout, mPresence) {
case 'm.room.topic':
handleRoomTopic(event, isLiveEvent, isStateEvent);
break;
+ case 'm.room.redaction':
+ handleRedaction(event, isLiveEvent);
+ break;
default:
+ // if it is a state event, then just add it in so it
+ // displays on the Room Info screen.
+ if (typeof(event.state_key) === "string") { // incls. 0-len strings
+ if (event.room_id) {
+ handleRoomDateEvent(event, isLiveEvent, false);
+ }
+ }
console.log("Unable to handle event type " + event.type);
console.log(JSON.stringify(event, undefined, 4));
break;
diff --git a/webclient/components/matrix/matrix-filter.js b/webclient/components/matrix/matrix-filter.js
index e6f2acc5fd..3d64a569a1 100644
--- a/webclient/components/matrix/matrix-filter.js
+++ b/webclient/components/matrix/matrix-filter.js
@@ -47,7 +47,6 @@ angular.module('matrixFilter', [])
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) {
@@ -65,8 +64,16 @@ angular.module('matrixFilter', [])
var otherUserId;
- if (Object.keys(room.members)[0] && Object.keys(room.members)[0] !== user_id) {
+ 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;
diff --git a/webclient/components/matrix/matrix-service.js b/webclient/components/matrix/matrix-service.js
index a4f0568bce..1840cf46c0 100644
--- a/webclient/components/matrix/matrix-service.js
+++ b/webclient/components/matrix/matrix-service.js
@@ -438,6 +438,14 @@ angular.module('matrixService', [])
return this.sendMessage(room_id, msg_id, content);
},
+ redactEvent: function(room_id, event_id) {
+ var path = "/rooms/$room_id/redact/$event_id";
+ path = path.replace("$room_id", room_id);
+ path = path.replace("$event_id", event_id);
+ var content = {};
+ return doRequest("POST", path, undefined, content);
+ },
+
// get a snapshot of the members in a room.
getMemberList: function(room_id) {
// Like the cmd client, escape room ids
diff --git a/webclient/components/matrix/notification-service.js b/webclient/components/matrix/notification-service.js
new file mode 100644
index 0000000000..9a911413c3
--- /dev/null
+++ b/webclient/components/matrix/notification-service.js
@@ -0,0 +1,104 @@
+/*
+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 manages notifications: enabling, creating and showing them. This
+also contains 'bing word' logic.
+*/
+angular.module('notificationService', [])
+.factory('notificationService', ['$timeout', function($timeout) {
+
+ 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;
+ };
+
+ return {
+
+ containsBingWord: function(userId, displayName, bingWords, content) {
+ // case-insensitive name check for user_id OR display_name if they exist
+ var userRegex = "";
+ if (userId) {
+ var localpart = getLocalPartFromUserId(userId);
+ if (localpart) {
+ localpart = localpart.toLocaleLowerCase();
+ userRegex += "\\b" + localpart + "\\b";
+ }
+ }
+ if (displayName) {
+ displayName = displayName.toLocaleLowerCase();
+ if (userRegex.length > 0) {
+ userRegex += "|";
+ }
+ userRegex += "\\b" + displayName + "\\b";
+ }
+
+ var regexList = [new RegExp(userRegex, 'i')];
+
+ // bing word list check
+ if (bingWords && bingWords.length > 0) {
+ for (var i=0; i<bingWords.length; i++) {
+ var re = RegExp(bingWords[i], 'i');
+ regexList.push(re);
+ }
+ }
+ return this.hasMatch(regexList, content);
+ },
+
+ hasMatch: function(regExps, content) {
+ if (!content || $.type(content) != "string") {
+ return false;
+ }
+
+ if (regExps && regExps.length > 0) {
+ for (var i=0; i<regExps.length; i++) {
+ if (content.search(regExps[i]) != -1) {
+ return true;
+ }
+ }
+ }
+ return false;
+ },
+
+ showNotification: function(title, body, icon, onclick) {
+ var notification = new window.Notification(
+ title,
+ {
+ "body": body,
+ "icon": icon
+ }
+ );
+
+ if (onclick) {
+ notification.onclick = onclick;
+ }
+
+ $timeout(function() {
+ notification.close();
+ }, 5 * 1000);
+ }
+ };
+
+}]);
|