Skip to content
Snippets Groups Projects
Commit 704e7e9f authored by Erik Johnston's avatar Erik Johnston
Browse files

Merge branch 'release-v0.3.0' of github.com:matrix-org/synapse

parents 9f94f9de c58f7f29
No related branches found
No related tags found
No related merge requests found
......@@ -62,7 +62,8 @@
<span ng-show="currentCall.state == 'connecting'">Call Connecting...</span>
<span ng-show="currentCall.state == 'connected'">Call Connected</span>
<span ng-show="currentCall.state == 'ended' && !currentCall.didConnect && currentCall.direction == 'outbound' && currentCall.hangupParty == 'remote'">Call Rejected</span>
<span ng-show="currentCall.state == 'ended' && !currentCall.didConnect && currentCall.direction == 'outbound' && currentCall.hangupParty == 'local'">Call Canceled</span>
<span ng-show="currentCall.state == 'ended' && !currentCall.didConnect && currentCall.direction == 'outbound' && currentCall.hangupParty == 'local' && currentCall.hangupReason == undefined">Call Canceled</span>
<span ng-show="currentCall.state == 'ended' && !currentCall.didConnect && currentCall.direction == 'outbound' && currentCall.hangupParty == 'local' && currentCall.hangupReason == 'invite_timeout'">User Not Responding</span>
<span ng-show="currentCall.state == 'ended' && currentCall.didConnect && currentCall.direction == 'outbound'">Call Ended</span>
<span ng-show="currentCall.state == 'ended' && !currentCall.didConnect && currentCall.direction == 'inbound'">Call Canceled</span>
<span ng-show="currentCall.state == 'ended' && currentCall.didConnect && currentCall.direction == 'inbound'">Call Ended</span>
......
......@@ -16,134 +16,16 @@
'use strict';
angular.module('RecentsController', ['matrixService', 'matrixFilter', 'eventHandlerService'])
.controller('RecentsController', ['$rootScope', '$scope', 'matrixService', 'eventHandlerService',
function($rootScope, $scope, matrixService, eventHandlerService) {
// FIXME: Angularjs reloads the controller (and resets its $scope) each time
// the page URL changes, use $rootScope to avoid to have to reload data
$rootScope.rooms;
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;
var listenToEventStream = function() {
// Refresh the list on matrix invitation and message event
$rootScope.$on(eventHandlerService.MEMBER_EVENT, function(ngEvent, event, isLive) {
if (isLive) {
if (!$rootScope.rooms[event.room_id]) {
// The user has joined a new room, which we do not have data yet. The reason is that
// the room has appeared in the scope of the user rooms after the global initialSync
// FIXME: an initialSync on this specific room should be done
$rootScope.rooms[event.room_id] = {
room_id:event.room_id
};
}
else if (event.state_key === matrixService.config().user_id && "invite" !== event.membership && "join" !== event.membership) {
// The user has been kicked or banned from the room, remove this room from the recents
delete $rootScope.rooms[event.room_id];
}
if ($rootScope.rooms[event.room_id]) {
$rootScope.rooms[event.room_id].lastMsg = event;
}
// Update room users count
$rootScope.rooms[event.room_id].numUsersInRoom = getUsersCountInRoom(event.room_id);
}
});
$rootScope.$on(eventHandlerService.MSG_EVENT, function(ngEvent, event, isLive) {
if (isLive) {
$rootScope.rooms[event.room_id].lastMsg = event;
}
});
$rootScope.$on(eventHandlerService.CALL_EVENT, function(ngEvent, event, isLive) {
if (isLive) {
$rootScope.rooms[event.room_id].lastMsg = event;
}
});
$rootScope.$on(eventHandlerService.ROOM_CREATE_EVENT, function(ngEvent, event, isLive) {
if (isLive) {
$rootScope.rooms[event.room_id] = event;
}
});
$rootScope.$on(eventHandlerService.NAME_EVENT, function(ngEvent, event, isLive) {
if (isLive) {
$rootScope.rooms[event.room_id].lastMsg = event;
}
});
$rootScope.$on(eventHandlerService.TOPIC_EVENT, function(ngEvent, event, isLive) {
if (isLive) {
$rootScope.rooms[event.room_id].lastMsg = event;
}
});
};
/**
* 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
*/
var getUsersCountInRoom = function(room_id) {
var memberCount;
var room = $rootScope.events.rooms[room_id];
if (room) {
memberCount = 0;
for (var i in room.members) {
var member = room.members[i];
if ("join" === member.membership) {
memberCount = memberCount + 1;
}
}
}
return memberCount;
};
$scope.onInit = function() {
// Init recents list only once
if ($rootScope.rooms) {
return;
}
$rootScope.rooms = {};
// Use initialSync data to init the recents list
eventHandlerService.waitForInitialSyncCompletion().then(
function(initialSyncData) {
var rooms = initialSyncData.data.rooms;
for (var i=0; i<rooms.length; i++) {
var room = rooms[i];
// Add room_alias & room_display_name members
$rootScope.rooms[room.room_id] = angular.extend(room, matrixService.getRoomAliasAndDisplayName(room));
// Create a shortcut for the last message of this room
if (room.messages && room.messages.chunk && room.messages.chunk[0]) {
$rootScope.rooms[room.room_id].lastMsg = room.messages.chunk[0];
}
$rootScope.rooms[room.room_id].numUsersInRoom = getUsersCountInRoom(room.room_id);
}
// From now, update recents from the stream
listenToEventStream();
},
function(error) {
$rootScope.feedback = "Failure: " + error.data;
}
);
};
// Clean data when user logs out
$scope.$on(eventHandlerService.RESET_EVENT, function() {
delete $rootScope.rooms;
});
}]);
......@@ -17,31 +17,47 @@
'use strict';
angular.module('RecentsController')
.filter('orderRecents', function() {
.filter('orderRecents', ["matrixService", "eventHandlerService", function(matrixService, eventHandlerService) {
return function(rooms) {
var user_id = matrixService.config().user_id;
// Transform the dict into an array
// The key, room_id, is already in value objects
var filtered = [];
angular.forEach(rooms, function(value, key) {
filtered.push( value );
angular.forEach(rooms, function(room, room_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)) {
// Count users here
// TODO: Compute it directly in eventHandlerService
room.numUsersInRoom = eventHandlerService.getUsersCountInRoom(room_id);
}
filtered.push(room);
});
// And time sort them
// The room with the lastest message at first
filtered.sort(function (a, b) {
filtered.sort(function (roomA, roomB) {
var lastMsgRoomA = eventHandlerService.getLastMessage(roomA.room_id, true);
var lastMsgRoomB = eventHandlerService.getLastMessage(roomB.room_id, true);
// Invite message does not have a body message nor ts
// Puth them at the top of the list
if (undefined === a.lastMsg) {
if (undefined === lastMsgRoomA) {
return -1;
}
else if (undefined === b.lastMsg) {
else if (undefined === lastMsgRoomB) {
return 1;
}
else {
return b.lastMsg.ts - a.lastMsg.ts;
return lastMsgRoomB.ts - lastMsgRoomA.ts;
}
});
return filtered;
};
});
\ No newline at end of file
}]);
<div ng-controller="RecentsController" data-ng-init="onInit()">
<div ng-controller="RecentsController">
<table class="recentsTable">
<tbody ng-repeat="(rm_id, room) in rooms | orderRecents"
<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)}">
ng-class="{'recentsRoomSelected': (room.room_id === recentsSelectedRoomID)}">
<tr>
<td class="recentsRoomName">
<td ng-class="room['m.room.join_rules'].content.join_rule == 'public' ? 'recentsRoomName recentsPublicRoom' : 'recentsRoomName'">
{{ room.room_id | mRoomName }}
</td>
<td class="recentsRoomSummaryTS">
<td class="recentsRoomSummaryUsersCount">
<span ng-show="undefined !== room.numUsersInRoom">
{{ room.numUsersInRoom || '1' }} {{ room.numUsersInRoom == 1 ? 'user' : 'users' }}
</span>
</td>
<td class="recentsRoomSummaryTS">
{{ (room.lastMsg.ts) | date:'MMM d HH:mm' }}
<!-- Use a temp var as alias to the last room message.
Declaring it in this way ensures the data-binding -->
{{ lastMsg = eventHandlerService.getLastMessage(room.room_id, true);"" }}
{{ (lastMsg.ts) | date:'MMM d HH:mm' }}
</td>
</tr>
......@@ -22,70 +26,70 @@
<td colspan="3" class="recentsRoomSummary">
<div ng-show="room.membership === 'invite'">
{{ room.lastMsg.inviter | mUserDisplayName: room.room_id }} invited you
{{ room.inviter | mUserDisplayName: room.room_id }} invited you
</div>
<div ng-hide="room.membership === 'invite'" ng-switch="room.lastMsg.type">
<div ng-hide="room.membership === 'invite'" ng-switch="lastMsg.type">
<div ng-switch-when="m.room.member">
<span ng-if="'join' === room.lastMsg.content.membership">
{{ room.lastMsg.state_key | mUserDisplayName: room.room_id}} joined
<span ng-if="'join' === lastMsg.content.membership">
{{ lastMsg.state_key | mUserDisplayName: room.room_id}} joined
</span>
<span ng-if="'leave' === room.lastMsg.content.membership">
<span ng-if="room.lastMsg.user_id === room.lastMsg.state_key">
{{room.lastMsg.state_key | mUserDisplayName: room.room_id }} left
<span ng-if="'leave' === lastMsg.content.membership">
<span ng-if="lastMsg.user_id === lastMsg.state_key">
{{lastMsg.state_key | mUserDisplayName: room.room_id }} left
</span>
<span ng-if="room.lastMsg.user_id !== room.lastMsg.state_key">
{{ room.lastMsg.user_id | mUserDisplayName: room.room_id }}
{{ {"join": "kicked", "ban": "unbanned"}[room.lastMsg.content.prev] }}
{{ room.lastMsg.state_key | mUserDisplayName: room.room_id }}
<span ng-if="lastMsg.user_id !== lastMsg.state_key">
{{ lastMsg.user_id | mUserDisplayName: room.room_id }}
{{ {"join": "kicked", "ban": "unbanned"}[lastMsg.content.prev] }}
{{ lastMsg.state_key | mUserDisplayName: room.room_id }}
</span>
<span ng-if="'join' === room.lastMsg.content.prev && room.lastMsg.content.reason">
: {{ room.lastMsg.content.reason }}
<span ng-if="'join' === lastMsg.content.prev && lastMsg.content.reason">
: {{ lastMsg.content.reason }}
</span>
</span>
<span ng-if="'invite' === room.lastMsg.content.membership || 'ban' === room.lastMsg.content.membership">
{{ room.lastMsg.user_id | mUserDisplayName: room.room_id }}
{{ {"invite": "invited", "ban": "banned"}[room.lastMsg.content.membership] }}
{{ room.lastMsg.state_key | mUserDisplayName: room.room_id }}
<span ng-if="'ban' === room.lastMsg.content.prev && room.lastMsg.content.reason">
: {{ room.lastMsg.content.reason }}
<span ng-if="'invite' === lastMsg.content.membership || 'ban' === lastMsg.content.membership">
{{ lastMsg.user_id | mUserDisplayName: room.room_id }}
{{ {"invite": "invited", "ban": "banned"}[lastMsg.content.membership] }}
{{ lastMsg.state_key | mUserDisplayName: room.room_id }}
<span ng-if="'ban' === lastMsg.content.prev && lastMsg.content.reason">
: {{ lastMsg.content.reason }}
</span>
</span>
</div>
<div ng-switch-when="m.room.message">
<div ng-switch="room.lastMsg.content.msgtype">
<div ng-switch="lastMsg.content.msgtype">
<div ng-switch-when="m.text">
{{ room.lastMsg.user_id | mUserDisplayName: room.room_id }} :
<span ng-bind-html="(room.lastMsg.content.body) | linky:'_blank'">
{{ lastMsg.user_id | mUserDisplayName: room.room_id }} :
<span ng-bind-html="(lastMsg.content.body) | linky:'_blank'">
</span>
</div>
<div ng-switch-when="m.image">
{{ room.lastMsg.user_id | mUserDisplayName: room.room_id }} sent an image
{{ lastMsg.user_id | mUserDisplayName: room.room_id }} sent an image
</div>
<div ng-switch-when="m.emote">
<span ng-bind-html="'* ' + (room.lastMsg.user_id | mUserDisplayName: room.room_id) + ' ' + room.lastMsg.content.body | linky:'_blank'">
<span ng-bind-html="'* ' + (lastMsg.user_id | mUserDisplayName: room.room_id) + ' ' + lastMsg.content.body | linky:'_blank'">
</span>
</div>
<div ng-switch-default>
{{ room.lastMsg.content }}
{{ lastMsg.content }}
</div>
</div>
</div>
<div ng-switch-when="m.room.topic">
{{ room.lastMsg.user_id | mUserDisplayName: room.room_id }} changed the topic to: {{ room.lastMsg.content.topic }}
{{ lastMsg.user_id | mUserDisplayName: room.room_id }} changed the topic to: {{ lastMsg.content.topic }}
</div>
<div ng-switch-when="m.room.name">
{{ room.lastMsg.user_id | mUserDisplayName: room.room_id }} changed the room name to: {{ room.lastMsg.content.name }}
{{ lastMsg.user_id | mUserDisplayName: room.room_id }} changed the room name to: {{ lastMsg.content.name }}
</div>
<div ng-switch-default>
<div ng-if="room.lastMsg.type.indexOf('m.call.') === 0">
<div ng-if="lastMsg.type.indexOf('m.call.') === 0">
Call
</div>
</div>
......
......@@ -15,8 +15,8 @@ limitations under the License.
*/
angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
.controller('RoomController', ['$filter', '$scope', '$timeout', '$routeParams', '$location', '$rootScope', 'matrixService', 'eventHandlerService', 'mFileUpload', 'mPresence', 'matrixPhoneService', 'MatrixCall',
function($filter, $scope, $timeout, $routeParams, $location, $rootScope, matrixService, eventHandlerService, mFileUpload, mPresence, matrixPhoneService, MatrixCall) {
.controller('RoomController', ['$filter', '$scope', '$timeout', '$routeParams', '$location', '$rootScope', 'matrixService', 'eventHandlerService', 'mFileUpload', 'matrixPhoneService', 'MatrixCall',
function($filter, $scope, $timeout, $routeParams, $location, $rootScope, matrixService, eventHandlerService, mFileUpload, matrixPhoneService, MatrixCall) {
'use strict';
var MESSAGES_PER_PAGINATION = 30;
var THUMBNAIL_SIZE = 320;
......@@ -32,7 +32,8 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
can_paginate: false, // this is toggled off when we are not ready yet to paginate or when we run out of items
paginating: false, // used to avoid concurrent pagination requests pulling in dup contents
stream_failure: undefined, // the response when the stream fails
waiting_for_joined_event: false // true when the join request is pending. Back to false once the corresponding m.room.member event is received
waiting_for_joined_event: false, // true when the join request is pending. Back to false once the corresponding m.room.member event is received
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;
......@@ -53,8 +54,13 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
return;
};
// Use the filter applied in html to set the input value
$scope.name.newNameText = $filter('mRoomName')($scope.room_id);
var nameEvent = $rootScope.events.rooms[$scope.room_id]['m.room.name'];
if (nameEvent) {
$scope.name.newNameText = nameEvent.content.name;
}
else {
$scope.name.newNameText = "";
}
// Force focus to the input
$timeout(function() {
......@@ -131,6 +137,13 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
$timeout(function() {
objDiv.scrollTop = objDiv.scrollHeight;
// Show the message table once the first scrolldown is done
if ("visible" !== $scope.state.messages_visibility) {
$timeout(function() {
$scope.state.messages_visibility = "visible";
}, 0);
}
}, 0);
}
};
......@@ -139,27 +152,11 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
if (isLive && event.room_id === $scope.room_id) {
scrollToBottom();
if (window.Notification) {
// Show notification when the window is hidden, or the user is idle
if (document.hidden || matrixService.presence.unavailable === mPresence.getState()) {
var notification = new window.Notification(
($scope.members[event.user_id].displayname || event.user_id) +
" (" + ($scope.room_alias || $scope.room_id) + ")", // FIXME: don't leak room_ids here
{
"body": event.content.body,
"icon": $scope.members[event.user_id].avatar_url
});
$timeout(function() {
notification.close();
}, 5 * 1000);
}
}
}
});
$scope.$on(eventHandlerService.MEMBER_EVENT, function(ngEvent, event, isLive) {
if (isLive) {
if (isLive && event.room_id === $scope.room_id) {
if ($scope.state.waiting_for_joined_event) {
// The user has successfully joined the room, we can getting data for this room
$scope.state.waiting_for_joined_event = false;
......@@ -177,19 +174,33 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
else {
user = event.user_id;
}
if ("ban" === event.membership) {
$scope.state.permission_denied = "You have been banned by " + user;
}
else {
$scope.state.permission_denied = "You have been kicked by " + user;
}
}
}
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) {
debugger;
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);
}
}
}
});
......@@ -235,7 +246,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
matrixService.paginateBackMessages($scope.room_id, $rootScope.events.rooms[$scope.room_id].pagination.earliest_token, numItems).then(
function(response) {
eventHandlerService.handleRoomMessages($scope.room_id, response.data, false);
eventHandlerService.handleRoomMessages($scope.room_id, response.data, false, 'b');
if (response.data.chunk.length < MESSAGES_PER_PAGINATION) {
// no more messages to paginate. this currently never gets turned true again, as we never
// expire paginated contents in the current implementation.
......@@ -406,12 +417,15 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
};
$scope.send = function() {
if ($scope.textInput === "") {
if (undefined === $scope.textInput || $scope.textInput === "") {
return;
}
scrollToBottom(true);
// Store the command in the history
history.push($scope.textInput);
var promise;
var cmd;
var args;
......@@ -676,6 +690,10 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
var onInit2 = function() {
console.log("onInit2");
// Scroll down as soon as possible so that we point to the last message
// if it already exists in memory
scrollToBottom(true);
// Make sure the initialSync has been before going further
eventHandlerService.waitForInitialSyncCompletion().then(
function() {
......@@ -684,6 +702,10 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
// The room members is available in the data fetched by initialSync
if ($rootScope.events.rooms[$scope.room_id]) {
// There is no need to do a 1st pagination (initialSync provided enough to fill a page)
$scope.state.first_pagination = false;
var members = $rootScope.events.rooms[$scope.room_id].members;
// Update the member list
......@@ -729,7 +751,10 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
// Make recents highlight the current room
$scope.recentsSelectedRoomID = $scope.room_id;
// Get the up-to-date the current member list
// Init the history for this room
history.init();
// Get the up-to-date the current member list
matrixService.getMemberList($scope.room_id).then(
function(response) {
for (var i = 0; i < response.data.chunk.length; i++) {
......@@ -743,9 +768,18 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
// Arm list timing update timer
updateMemberListPresenceAge();
// Start pagination
// Allow pagination
$scope.state.can_paginate = true;
paginate(MESSAGES_PER_PAGINATION);
// Do a first pagination only if it is required
// FIXME: Should be no more require when initialSync/{room_id} will be available
if ($scope.state.first_pagination) {
paginate(MESSAGES_PER_PAGINATION);
}
else {
// There are already messages, go to the last message
scrollToBottom(true);
}
},
function(error) {
$scope.feedback = "Failed get member list: " + error.data.error;
......@@ -832,4 +866,82 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput'])
$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 = $scope.textInput;
}
else {
// If the user modified this line in history, keep the change
this.data[this.position] = $scope.textInput;
}
// 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
$scope.textInput = this.data[this.position];
}
else if (undefined !== this.typingMessage) {
// Go back to the message the user started to type
$scope.textInput = 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();
}
};
}]);
......@@ -9,7 +9,7 @@
{{ 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" />
<input ng-model="name.newNameText" ng-blur="name.cancelEdit()" class="roomNameInput" placeholder="Room name"/>
</form>
</div>
......@@ -23,7 +23,7 @@
{{ 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" />
<input ng-model="topic.newTopicText" ng-blur="topic.cancelEdit()" class="roomTopicInput" placeholder="Topic"/>
</form>
</div>
</div>
......@@ -56,7 +56,10 @@
</table>
</div>
<div id="messageTableWrapper" ng-hide="state.permission_denied" keep-scroll>
<div id="messageTableWrapper"
ng-hide="state.permission_denied"
ng-style="{ 'visibility': state.messages_visibility }"
keep-scroll>
<!-- FIXME: need to have better timestamp semantics than the (msg.content.hsob_ts || msg.ts) hack below -->
<table id="messageTable" infinite-scroll="paginateMore()">
<tr ng-repeat="msg in events.rooms[room_id].messages"
......@@ -105,7 +108,7 @@
<span ng-show='msg.content.msgtype === "m.text"'
class="message"
ng-class="msg.echo_msg_state"
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.content.body : '') | linky:'_blank'"/>
<span ng-show='msg.type === "m.call.invite" && msg.user_id == state.user_id'>Outgoing Call</span>
......@@ -156,7 +159,8 @@
<td width="*">
<textarea id="mainInput" rows="1" ng-model="textInput" ng-enter="send()"
ng-disabled="state.permission_denied"
ng-focus="true" autocomplete="off" tab-complete/>
ng-keydown="(38 === $event.which) ? history.goUp($event) : ((40 === $event.which) ? history.goDown($event) : 0)"
ng-focus="true" autocomplete="off" tab-complete/>
</td>
<td id="buttonsCell">
<button ng-click="send()" ng-disabled="state.permission_denied">Send</button>
......
......@@ -194,7 +194,16 @@ angular.module('SettingsController', ['matrixService', 'mFileUpload', 'mFileInpu
/*** Desktop notifications section ***/
$scope.settings = {
notifications: undefined
notifications: undefined,
bingWords: matrixService.config().bingWords
};
$scope.saveBingWords = function() {
console.log("Saving words: "+JSON.stringify($scope.settings.bingWords));
var config = matrixService.config();
config.bingWords = $scope.settings.bingWords;
matrixService.setConfig(config);
matrixService.saveConfig();
};
// If the browser supports it, check the desktop notification state
......
......@@ -51,7 +51,16 @@
<h3>Desktop notifications</h3>
<div class="section" ng-switch="settings.notifications">
<div ng-switch-when="granted">
Notifications are enabled.
Notifications are enabled. You will be alerted when a message contains your user ID or display name.
<div class="section">
<h4>Additional words to alert on:</h4>
<p>Leave blank to alert on all messages.</p>
<input size=40 name="bingWords" ng-model="settings.bingWords" ng-list placeholder="Enter words separated with , (supports regex)"
ng-blur="saveBingWords()"/>
<ul>
<li ng-repeat="word in settings.bingWords">{{word}}</li>
</ul>
</div>
</div>
<div ng-switch-when="denied">
You have denied permission for notifications.<br/>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment