From 534d9965825fa112c1e4b2c955dab19c9b86af29 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Mon, 15 Aug 2016 16:17:35 +0100 Subject: [PATCH 01/15] ignore local busy - workaround for https://github.com/vector-im/vector-web/issues/1964 --- src/CallHandler.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/CallHandler.js b/src/CallHandler.js index 9118ee1973..015160a1fe 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -273,8 +273,11 @@ function _onAction(payload) { break; case 'incoming_call': if (module.exports.getAnyActiveCall()) { - payload.call.hangup("busy"); - return; // don't allow >1 call to be received, hangup newer one. + // ignore multiple incoming calls. in future, we may want a line-1/line-2 setup. + // we avoid rejecting with "busy" in case the user wants to answer it on a different device. + // in future we could signal a "local busy" as a warning to the caller. + // see https://github.com/vector-im/vector-web/issues/1964 + return; } // if the runtime env doesn't do VoIP, stop here. From 2a3b0e85ea747e6bd4502931b270d85333465e4d Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Mon, 15 Aug 2016 21:37:26 +0100 Subject: [PATCH 02/15] add rel='noopener' wherever we do target='_blank' because https://mathiasbynens.github.io/rel-noopener/ --- src/HtmlUtils.js | 5 +++-- src/components/views/messages/MFileBody.js | 2 +- src/components/views/messages/MImageBody.js | 2 +- src/components/views/rooms/LinkPreviewWidget.js | 2 +- src/linkify-matrix.js | 4 ++++ 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/HtmlUtils.js b/src/HtmlUtils.js index c0792e6d14..6a8d903df8 100644 --- a/src/HtmlUtils.js +++ b/src/HtmlUtils.js @@ -69,7 +69,7 @@ var sanitizeHtmlParams = { allowedAttributes: { // custom ones first: font: [ 'color' ], // custom to matrix - a: [ 'href', 'name', 'target' ], // remote target: custom to matrix + a: [ 'href', 'name', 'target', 'rel' ], // remote target: custom to matrix // We don't currently allow img itself by default, but this // would make sense if we did img: [ 'src' ], @@ -81,7 +81,7 @@ var sanitizeHtmlParams = { allowedSchemesByTag: { img: [ 'data' ], }, - + transformTags: { // custom to matrix // add blank targets to all hyperlinks except vector URLs 'a': function(tagName, attribs) { @@ -92,6 +92,7 @@ var sanitizeHtmlParams = { else { attribs.target = '_blank'; } + attribs.rel = 'noopener'; // https://mathiasbynens.github.io/rel-noopener/ return { tagName: tagName, attribs : attribs }; }, }, diff --git a/src/components/views/messages/MFileBody.js b/src/components/views/messages/MFileBody.js index 2f416daf95..dbad084024 100644 --- a/src/components/views/messages/MFileBody.js +++ b/src/components/views/messages/MFileBody.js @@ -60,7 +60,7 @@ module.exports = React.createClass({ return (
- + Download {text} diff --git a/src/components/views/messages/MImageBody.js b/src/components/views/messages/MImageBody.js index 13f9cf4c19..ec594af2ce 100644 --- a/src/components/views/messages/MImageBody.js +++ b/src/components/views/messages/MImageBody.js @@ -134,7 +134,7 @@ module.exports = React.createClass({ onMouseLeave={this.onImageLeave} />
- + Download {content.body} ({ content.info && content.info.size ? filesize(content.info.size) : "Unknown size" }) diff --git a/src/components/views/rooms/LinkPreviewWidget.js b/src/components/views/rooms/LinkPreviewWidget.js index ba438c1d12..60f4f8abc0 100644 --- a/src/components/views/rooms/LinkPreviewWidget.js +++ b/src/components/views/rooms/LinkPreviewWidget.js @@ -123,7 +123,7 @@ module.exports = React.createClass({
{ img }
- +
{ p["og:site_name"] ? (" - " + p["og:site_name"]) : null }
{ p["og:description"] } diff --git a/src/linkify-matrix.js b/src/linkify-matrix.js index a12ef8eaf5..99b7ee5c33 100644 --- a/src/linkify-matrix.js +++ b/src/linkify-matrix.js @@ -137,6 +137,10 @@ matrixLinkify.options = { } }, + linkAttributes: { + rel: 'noopener', + }, + target: function(href, type) { if (type === 'url') { if (href.match(matrixLinkify.VECTOR_URL_PATTERN)) { From 8777780928d3edacfefaacb3d7ed6e622eb54b0a Mon Sep 17 00:00:00 2001 From: Aviral Dasgupta Date: Tue, 16 Aug 2016 03:50:59 +0530 Subject: [PATCH 03/15] strip (IRC) suffix from tabcomplete entries fixes vector-im/vector-web#574 --- src/TabCompleteEntries.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TabCompleteEntries.js b/src/TabCompleteEntries.js index 4a28103210..2a8c7b383a 100644 --- a/src/TabCompleteEntries.js +++ b/src/TabCompleteEntries.js @@ -94,7 +94,7 @@ CommandEntry.fromCommands = function(commandArray) { class MemberEntry extends Entry { constructor(member) { - super(member.name || member.userId); + super((member.name || member.userId).replace(' (IRC)', '')); this.member = member; this.kind = 'member'; } From 30168a1b9c60fcda80512e8711ad3a945e5343fb Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 17 Aug 2016 09:57:06 +0100 Subject: [PATCH 04/15] Don't download E2E devices if feature disabled If the user hasn't enabled the E2E setting in the labs, there is no point in firing off the device download request when the MemberInfo is opened. --- src/components/views/rooms/MemberInfo.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index 0b37847257..b3c61ce807 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -73,6 +73,11 @@ module.exports = React.createClass({ }, componentDidMount: function() { + // only display the devices list if our client supports E2E *and* the + // feature is enabled in the user settings + this._enableDevices = MatrixClientPeg.get().isCryptoEnabled() && + UserSettingsStore.isFeatureEnabled("e2e_encryption"); + this._updateStateForNewMember(this.props.member); MatrixClientPeg.get().on("deviceVerificationChanged", this.onDeviceVerificationChanged); }, @@ -147,6 +152,10 @@ module.exports = React.createClass({ }, onDeviceVerificationChanged: function(userId, device) { + if (!this._enableDevices) { + return; + } + if (userId == this.props.member.userId) { // no need to re-download the whole thing; just update our copy of // the list. @@ -170,6 +179,10 @@ module.exports = React.createClass({ }, _downloadDeviceList: function(member) { + if (!this._enableDevices) { + return; + } + var cancelled = false; this._cancelDeviceList = function() { cancelled = true; } @@ -532,7 +545,7 @@ module.exports = React.createClass({ }, _renderDevices: function() { - if (!UserSettingsStore.isFeatureEnabled("e2e_encryption")) { + if (!this._enableDevices) { return null; } From 0356f04b9c8378b9657f83e484cf6d85f954b9d4 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 17 Aug 2016 14:40:10 +0100 Subject: [PATCH 05/15] MemberInfo: initialise _enableDevices in componentWillMount ... to avoid referencing it in render() before it is set --- src/components/views/rooms/MemberInfo.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index b3c61ce807..59e186da06 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -67,17 +67,17 @@ module.exports = React.createClass({ componentWillMount: function() { this._cancelDeviceList = null; + // only display the devices list if our client supports E2E *and* the + // feature is enabled in the user settings + this._enableDevices = MatrixClientPeg.get().isCryptoEnabled() && + UserSettingsStore.isFeatureEnabled("e2e_encryption"); + this.setState({ existingOneToOneRoomId: this.getExistingOneToOneRoomId() }); }, componentDidMount: function() { - // only display the devices list if our client supports E2E *and* the - // feature is enabled in the user settings - this._enableDevices = MatrixClientPeg.get().isCryptoEnabled() && - UserSettingsStore.isFeatureEnabled("e2e_encryption"); - this._updateStateForNewMember(this.props.member); MatrixClientPeg.get().on("deviceVerificationChanged", this.onDeviceVerificationChanged); }, From 46d306a217e6101a2c37f4eeaf6760e96537bd60 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Wed, 17 Aug 2016 17:16:19 +0100 Subject: [PATCH 06/15] Change register response access_token to scalar_token --- src/ScalarAuthClient.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ScalarAuthClient.js b/src/ScalarAuthClient.js index 67607b4996..d145cebfe0 100644 --- a/src/ScalarAuthClient.js +++ b/src/ScalarAuthClient.js @@ -34,8 +34,10 @@ class ScalarAuthClient { defer.reject(err); } else if (response.statusCode / 100 !== 2) { defer.reject({statusCode: response.statusCode}); + } else if (!body || !body.scalar_token) { + defer.reject(new Error("Missing scalar_token in response")); } else { - defer.resolve(body.access_token); + defer.resolve(body.scalar_token); } }); From 87f94bde62458b585c193dad472730d628ab2516 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 17 Aug 2016 18:26:37 +0100 Subject: [PATCH 07/15] Fix up notification setting listener in roomtile The previous dispatch only did binary muted/non-muted but we now have 4 states. We now just listen for the push rules account data and update on that so it stays in sync if the pishrules are changed elsewhere. Also add util functions used here for getting the notif state and in vector for both getting and setting it. --- src/RoomNotifs.js | 137 +++++++++++++++++++++++++ src/components/views/rooms/RoomTile.js | 52 +++++----- 2 files changed, 162 insertions(+), 27 deletions(-) create mode 100644 src/RoomNotifs.js diff --git a/src/RoomNotifs.js b/src/RoomNotifs.js new file mode 100644 index 0000000000..704fdd17b8 --- /dev/null +++ b/src/RoomNotifs.js @@ -0,0 +1,137 @@ +/* +Copyright 2016 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. +*/ + +import MatrixClientPeg from './MatrixClientPeg'; +import PushProcessor from 'matrix-js-sdk/lib/pushprocessor'; +import q from 'q'; + +export function getRoomNotifsState(roomId) { + // look through the override rules for a rule affecting this room: + // if one exists, it will take precedence. + const muteRule = findOverrideMuteRule(roomId); + if (muteRule && muteRule.enabled) { + return 'mute'; + } + + // for everything else, look at the room rule. + const roomRule = MatrixClientPeg.get().getRoomPushRule('global', roomId); + + // XXX: We have to assume the default is to notify for all messages + // (in particular this will be 'wrong' for one to one rooms because + // they will notify loudly for all messages) + if (!roomRule || !roomRule.enabled) return 'all_messages'; + + // a mute at the room level will still allow mentions + // to notify + if (isMuteRule(roomRule)) return 'mentions_only'; + + const actionsObject = PushProcessor.actionListToActionsObject(roomRule.actions); + if (actionsObject.tweaks.sound) return 'all_messages_loud'; + + return null; +} + +export function setRoomNotifsState(roomId, newState) { + const cli = MatrixClientPeg.get(); + const promises = []; + + if (newState == 'mute') { + // delete the room rule + const roomRule = MatrixClientPeg.get().getRoomPushRule('global', roomId); + if (roomRule) { + promises.push(cli.deletePushRule('global', 'room', roomRule.rule_id)); + } + + // add an override rule to squelch everything in this room + promises.push(cli.addPushRule('global', 'override', roomId, { + conditions: [ + { + kind: 'event_match', + key: 'room_id', + pattern: roomId, + } + ], + actions: [ + 'dont_notify', + ] + })); + } else { + const overrideMuteRule = findOverrideMuteRule(roomId); + if (overrideMuteRule) { + promises.push(cli.deletePushRule('global', 'override', overrideMuteRule.rule_id)); + } + + if (newState == 'all_messages') { + promises.push(cli.deletePushRule('global', 'room', roomId)); + } else if (newState == 'mentions_only') { + promises.push(cli.addPushRule('global', 'room', roomId, { + actions: [ + 'dont_notify', + ] + })); + // https://matrix.org/jira/browse/SPEC-400 + promises.push(cli.setPushRuleEnabled('global', 'room', roomId, true)); + } else if ('all_messages_loud') { + promises.push(cli.addPushRule('global', 'room', roomId, { + actions: [ + 'notify', + { + set_tweak: 'sound', + value: 'default', + } + ] + })); + // https://matrix.org/jira/browse/SPEC-400 + promises.push(cli.setPushRuleEnabled('global', 'room', roomId, true)); + } + } + + return q.all(promises); +} + +function findOverrideMuteRule(roomId) { + for (const rule of MatrixClientPeg.get().pushRules['global'].override) { + if (isRuleForRoom(roomId, rule)) { + if (isMuteRule(rule)) { + return rule; + } + } + } + return null; +} + +function isRuleForRoom(roomId, rule) { + if (rule.conditions.length !== 1) { + return false; + } + const cond = rule.conditions[0]; + if ( + cond.kind == 'event_match' && + cond.key == 'room_id' && + cond.pattern == roomId + ) { + return true; + } + return false; +} + +function isMuteRule(rule) { + return ( + rule.actions.length == 1 && + rule.actions[0] == 'dont_notify' + ); +} + diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index ae0ffafae5..7248ed7fcd 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -22,6 +22,7 @@ var dis = require("../../../dispatcher"); var MatrixClientPeg = require('../../../MatrixClientPeg'); var sdk = require('../../../index'); var ContextualMenu = require('../../structures/ContextualMenu'); +var RoomNotifs = require('../../../RoomNotifs'); module.exports = React.createClass({ displayName: 'RoomTile', @@ -43,43 +44,40 @@ module.exports = React.createClass({ }, getInitialState: function() { - var areNotifsMuted = false; - var cli = MatrixClientPeg.get(); - if (!cli.isGuest()) { - var roomPushRule = cli.getRoomPushRule("global", this.props.room.roomId); - if (roomPushRule) { - if (0 <= roomPushRule.actions.indexOf("dont_notify")) { - areNotifsMuted = true; - } - } - } - return({ hover : false, badgeHover : false, notificationTagMenu: false, roomTagMenu: false, - areNotifsMuted: areNotifsMuted, + showBadge: this._shouldShowBadge(), }); }, - onAction: function(payload) { - switch (payload.action) { - case 'notification_change': - // Is the notification about this room? - if (payload.roomId === this.props.room.roomId) { - this.setState( { areNotifsMuted : payload.areNotifsMuted }); - } - break; + _shouldShowBadge: function() { + if (MatrixClientPeg.get().isGuest()) return true; + + const showBadgeInStates = ['all_messages', 'all_messages_loud']; + const currentState = RoomNotifs.getRoomNotifsState(this.props.room.roomId); + return showBadgeInStates.indexOf(currentState) > -1; + }, + + onAccountData: function(accountDataEvent) { + if (accountDataEvent.getType() == 'm.push_rules') { + this.setState({ + showBadge: this._shouldShowBadge(), + }); } }, - componentDidMount: function() { - this.dispatcherRef = dis.register(this.onAction); + componentWillMount: function() { + MatrixClientPeg.get().on("accountData", this.onAccountData); }, componentWillUnmount: function() { - dis.unregister(this.dispatcherRef); + var cli = MatrixClientPeg.get(); + if (cli) { + MatrixClientPeg.get().removeListener("accountData", this.onAccountData); + } }, onClick: function() { @@ -183,11 +181,11 @@ module.exports = React.createClass({ 'mx_RoomTile': true, 'mx_RoomTile_selected': this.props.selected, 'mx_RoomTile_unread': this.props.unread, - 'mx_RoomTile_unreadNotify': notificationCount > 0 && !this.state.areNotifsMuted, + 'mx_RoomTile_unreadNotify': notificationCount > 0 && this.state.showBadge, 'mx_RoomTile_highlight': this.props.highlight, 'mx_RoomTile_invited': (me && me.membership == 'invite'), 'mx_RoomTile_notificationTagMenu': this.state.notificationTagMenu, - 'mx_RoomTile_noBadges': !(this.props.highlight || (notificationCount > 0 && !this.state.areNotifsMuted)) + 'mx_RoomTile_noBadges': !(this.props.highlight || (notificationCount > 0 && this.state.showBadge)) }); var avatarClasses = classNames({ @@ -214,7 +212,7 @@ module.exports = React.createClass({ if (this.state.badgeHover || this.state.notificationTagMenu) { badgeContent = "\u00B7\u00B7\u00B7"; - } else if (this.props.highlight || (notificationCount > 0 && !this.state.areNotifsMuted)) { + } else if (this.props.highlight || (notificationCount > 0 && this.state.showBadge)) { var limitedCount = (notificationCount > 99) ? '99+' : notificationCount; badgeContent = notificationCount ? limitedCount : '!'; } else { @@ -230,7 +228,7 @@ module.exports = React.createClass({ var nameClasses = classNames({ 'mx_RoomTile_name': true, 'mx_RoomTile_invite': this.props.isInvite, - 'mx_RoomTile_badgeShown': this.props.highlight || (notificationCount > 0 && !this.state.areNotifsMuted) || this.state.badgeHover || this.state.notificationTagMenu, + 'mx_RoomTile_badgeShown': this.props.highlight || (notificationCount > 0 && this.state.showBadge) || this.state.badgeHover || this.state.notificationTagMenu, }); if (this.props.selected) { From dd088794c2ec5df25116860ceb9945b34bbf2c74 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 17 Aug 2016 18:50:34 +0100 Subject: [PATCH 08/15] Remove the mute toggle from room settings As it now incorrectly represents the mute as a binary toggle rather than a quad-state --- src/components/views/rooms/RoomSettings.js | 23 ---------------------- 1 file changed, 23 deletions(-) diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js index abc90ae486..1896207c09 100644 --- a/src/components/views/rooms/RoomSettings.js +++ b/src/components/views/rooms/RoomSettings.js @@ -47,16 +47,6 @@ module.exports = React.createClass({ tags[tagName] = ['yep']; }); - var areNotifsMuted = false; - if (!MatrixClientPeg.get().isGuest()) { - var roomPushRule = MatrixClientPeg.get().getRoomPushRule("global", this.props.room.roomId); - if (roomPushRule) { - if (0 <= roomPushRule.actions.indexOf("dont_notify")) { - areNotifsMuted = true; - } - } - } - return { name: this._yankValueFromEvent("m.room.name", "name"), topic: this._yankValueFromEvent("m.room.topic", "topic"), @@ -66,7 +56,6 @@ module.exports = React.createClass({ power_levels_changed: false, tags_changed: false, tags: tags, - areNotifsMuted: areNotifsMuted, // isRoomPublished is loaded async in componentWillMount so when the component // inits, the saved value will always be undefined, however getInitialState() // is also called from the saving code so we must return the correct value here @@ -188,12 +177,6 @@ module.exports = React.createClass({ } - if (this.state.areNotifsMuted !== originalState.areNotifsMuted) { - promises.push(MatrixClientPeg.get().setRoomMutePushRule( - "global", roomId, this.state.areNotifsMuted - )); - } - // power levels var powerLevels = this._getPowerLevels(); if (powerLevels) { @@ -647,12 +630,6 @@ module.exports = React.createClass({ { tagsSection }
-

Who can access this room?

{ inviteGuestWarning } From 73e486cc583371c111f653238cae9391550b59c6 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 18 Aug 2016 11:58:27 +0100 Subject: [PATCH 09/15] Hide red highlight badge in mute mode --- src/components/views/rooms/RoomTile.js | 30 +++++++++++++++++--------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index 7248ed7fcd..8a7b83cb44 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -49,22 +49,29 @@ module.exports = React.createClass({ badgeHover : false, notificationTagMenu: false, roomTagMenu: false, - showBadge: this._shouldShowBadge(), + notifState: this._getNotifState(), }); }, - _shouldShowBadge: function() { - if (MatrixClientPeg.get().isGuest()) return true; + _getNotifState: function() { + if (MatrixClientPeg.get().isGuest()) return 'all_messages'; + return RoomNotifs.getRoomNotifsState(this.props.room.roomId); + }, + _shouldShowNotifBadge: function() { const showBadgeInStates = ['all_messages', 'all_messages_loud']; - const currentState = RoomNotifs.getRoomNotifsState(this.props.room.roomId); + const currentState = this._getNotifState(); return showBadgeInStates.indexOf(currentState) > -1; }, + _shouldShowMentionBadge: function() { + return this._getNotifState() != 'mute'; + }, + onAccountData: function(accountDataEvent) { if (accountDataEvent.getType() == 'm.push_rules') { this.setState({ - showBadge: this._shouldShowBadge(), + notifState: this._getNotifState(), }); } }, @@ -177,15 +184,18 @@ module.exports = React.createClass({ var notificationCount = this.props.room.getUnreadNotificationCount(); // var highlightCount = this.props.room.getUnreadNotificationCount("highlight"); + var badges = notificationCount > 0 && this._shouldShowNotifBadge(); + badges |= this.props.highlight && this._shouldShowMentionBadge(); + var classes = classNames({ 'mx_RoomTile': true, 'mx_RoomTile_selected': this.props.selected, 'mx_RoomTile_unread': this.props.unread, - 'mx_RoomTile_unreadNotify': notificationCount > 0 && this.state.showBadge, - 'mx_RoomTile_highlight': this.props.highlight, + 'mx_RoomTile_unreadNotify': notificationCount > 0 && this._shouldShowNotifBadge(), + 'mx_RoomTile_highlight': this.props.highlight && badges, 'mx_RoomTile_invited': (me && me.membership == 'invite'), 'mx_RoomTile_notificationTagMenu': this.state.notificationTagMenu, - 'mx_RoomTile_noBadges': !(this.props.highlight || (notificationCount > 0 && this.state.showBadge)) + 'mx_RoomTile_noBadges': !badges, }); var avatarClasses = classNames({ @@ -212,7 +222,7 @@ module.exports = React.createClass({ if (this.state.badgeHover || this.state.notificationTagMenu) { badgeContent = "\u00B7\u00B7\u00B7"; - } else if (this.props.highlight || (notificationCount > 0 && this.state.showBadge)) { + } else if (badges) { var limitedCount = (notificationCount > 99) ? '99+' : notificationCount; badgeContent = notificationCount ? limitedCount : '!'; } else { @@ -228,7 +238,7 @@ module.exports = React.createClass({ var nameClasses = classNames({ 'mx_RoomTile_name': true, 'mx_RoomTile_invite': this.props.isInvite, - 'mx_RoomTile_badgeShown': this.props.highlight || (notificationCount > 0 && this.state.showBadge) || this.state.badgeHover || this.state.notificationTagMenu, + 'mx_RoomTile_badgeShown': badges || this.state.badgeHover || this.state.notificationTagMenu, }); if (this.props.selected) { From 9e45279894c6d7d31d496393835f546a5134e15b Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 18 Aug 2016 13:44:58 +0100 Subject: [PATCH 10/15] Use enumalike thing --- src/RoomNotifs.js | 13 +++++++++---- src/components/views/rooms/RoomTile.js | 11 ++++++----- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/RoomNotifs.js b/src/RoomNotifs.js index 704fdd17b8..6f792df26b 100644 --- a/src/RoomNotifs.js +++ b/src/RoomNotifs.js @@ -18,12 +18,17 @@ import MatrixClientPeg from './MatrixClientPeg'; import PushProcessor from 'matrix-js-sdk/lib/pushprocessor'; import q from 'q'; +export const ALL_MESSAGES_LOUD = 'all_messages_loud'; +export const ALL_MESSAGES = 'all_messages'; +export const MENTIONS_ONLY = 'mentions_only'; +export const MUTE = 'mute'; + export function getRoomNotifsState(roomId) { // look through the override rules for a rule affecting this room: // if one exists, it will take precedence. const muteRule = findOverrideMuteRule(roomId); if (muteRule && muteRule.enabled) { - return 'mute'; + return MUTE; } // for everything else, look at the room rule. @@ -32,14 +37,14 @@ export function getRoomNotifsState(roomId) { // XXX: We have to assume the default is to notify for all messages // (in particular this will be 'wrong' for one to one rooms because // they will notify loudly for all messages) - if (!roomRule || !roomRule.enabled) return 'all_messages'; + if (!roomRule || !roomRule.enabled) return ALL_MESSAGES; // a mute at the room level will still allow mentions // to notify - if (isMuteRule(roomRule)) return 'mentions_only'; + if (isMuteRule(roomRule)) return MENTIONS_ONLY; const actionsObject = PushProcessor.actionListToActionsObject(roomRule.actions); - if (actionsObject.tweaks.sound) return 'all_messages_loud'; + if (actionsObject.tweaks.sound) return ALL_MESSAGES_LOUD; return null; } diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index 8a7b83cb44..5b508b6ad0 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -54,18 +54,18 @@ module.exports = React.createClass({ }, _getNotifState: function() { - if (MatrixClientPeg.get().isGuest()) return 'all_messages'; + if (MatrixClientPeg.get().isGuest()) return RoomNotifs.ALL_MESSAGES; return RoomNotifs.getRoomNotifsState(this.props.room.roomId); }, _shouldShowNotifBadge: function() { - const showBadgeInStates = ['all_messages', 'all_messages_loud']; + const showBadgeInStates = [RoomNotifs.ALL_MESSAGES, RoomNotifs.ALL_MESSAGES_LOUD]; const currentState = this._getNotifState(); return showBadgeInStates.indexOf(currentState) > -1; }, _shouldShowMentionBadge: function() { - return this._getNotifState() != 'mute'; + return this._getNotifState() != RoomNotifs.MUTE; }, onAccountData: function(accountDataEvent) { @@ -184,8 +184,9 @@ module.exports = React.createClass({ var notificationCount = this.props.room.getUnreadNotificationCount(); // var highlightCount = this.props.room.getUnreadNotificationCount("highlight"); - var badges = notificationCount > 0 && this._shouldShowNotifBadge(); - badges |= this.props.highlight && this._shouldShowMentionBadge(); + const notifBadges = notificationCount > 0 && this._shouldShowNotifBadge(); + const mentionBadges = this.props.highlight && this._shouldShowMentionBadge(); + const badges = notifBadges || mentionBadges; var classes = classNames({ 'mx_RoomTile': true, From af48b8920ec8d192d82869176b7839b4216fd6e5 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 18 Aug 2016 14:00:14 +0100 Subject: [PATCH 11/15] Various PR feedback --- src/RoomNotifs.js | 107 ++++++++++++++----------- src/components/views/rooms/RoomTile.js | 16 ++-- 2 files changed, 66 insertions(+), 57 deletions(-) diff --git a/src/RoomNotifs.js b/src/RoomNotifs.js index 6f792df26b..563491b695 100644 --- a/src/RoomNotifs.js +++ b/src/RoomNotifs.js @@ -24,10 +24,12 @@ export const MENTIONS_ONLY = 'mentions_only'; export const MUTE = 'mute'; export function getRoomNotifsState(roomId) { + if (MatrixClientPeg.get().isGuest()) return RoomNotifs.ALL_MESSAGES; + // look through the override rules for a rule affecting this room: // if one exists, it will take precedence. const muteRule = findOverrideMuteRule(roomId); - if (muteRule && muteRule.enabled) { + if (muteRule) { return MUTE; } @@ -50,58 +52,71 @@ export function getRoomNotifsState(roomId) { } export function setRoomNotifsState(roomId, newState) { + if (newState == 'mute') { + return setRoomNotifsStateMuted(roomId); + } else { + return setRoomNotifsStateUnmuted(roomId, newState); + } +} + +function setRoomNotifsStateMuted(roomId) { const cli = MatrixClientPeg.get(); const promises = []; - if (newState == 'mute') { - // delete the room rule - const roomRule = MatrixClientPeg.get().getRoomPushRule('global', roomId); - if (roomRule) { - promises.push(cli.deletePushRule('global', 'room', roomRule.rule_id)); - } + // delete the room rule + const roomRule = cli.getRoomPushRule('global', roomId); + if (roomRule) { + promises.push(cli.deletePushRule('global', 'room', roomRule.rule_id)); + } - // add an override rule to squelch everything in this room - promises.push(cli.addPushRule('global', 'override', roomId, { - conditions: [ - { - kind: 'event_match', - key: 'room_id', - pattern: roomId, - } - ], + // add an override rule to squelch everything in this room + promises.push(cli.addPushRule('global', 'override', roomId, { + conditions: [ + { + kind: 'event_match', + key: 'room_id', + pattern: roomId, + } + ], + actions: [ + 'dont_notify', + ] + })); + + return q.all(promises); +} + +function setRoomNotifsStateUnmuted(roomId, newState) { + const cli = MatrixClientPeg.get(); + const promises = []; + + const overrideMuteRule = findOverrideMuteRule(roomId); + if (overrideMuteRule) { + promises.push(cli.deletePushRule('global', 'override', overrideMuteRule.rule_id)); + } + + if (newState == 'all_messages') { + promises.push(cli.deletePushRule('global', 'room', roomId)); + } else if (newState == 'mentions_only') { + promises.push(cli.addPushRule('global', 'room', roomId, { actions: [ 'dont_notify', ] })); - } else { - const overrideMuteRule = findOverrideMuteRule(roomId); - if (overrideMuteRule) { - promises.push(cli.deletePushRule('global', 'override', overrideMuteRule.rule_id)); - } - - if (newState == 'all_messages') { - promises.push(cli.deletePushRule('global', 'room', roomId)); - } else if (newState == 'mentions_only') { - promises.push(cli.addPushRule('global', 'room', roomId, { - actions: [ - 'dont_notify', - ] - })); - // https://matrix.org/jira/browse/SPEC-400 - promises.push(cli.setPushRuleEnabled('global', 'room', roomId, true)); - } else if ('all_messages_loud') { - promises.push(cli.addPushRule('global', 'room', roomId, { - actions: [ - 'notify', - { - set_tweak: 'sound', - value: 'default', - } - ] - })); - // https://matrix.org/jira/browse/SPEC-400 - promises.push(cli.setPushRuleEnabled('global', 'room', roomId, true)); - } + // https://matrix.org/jira/browse/SPEC-400 + promises.push(cli.setPushRuleEnabled('global', 'room', roomId, true)); + } else if ('all_messages_loud') { + promises.push(cli.addPushRule('global', 'room', roomId, { + actions: [ + 'notify', + { + set_tweak: 'sound', + value: 'default', + } + ] + })); + // https://matrix.org/jira/browse/SPEC-400 + promises.push(cli.setPushRuleEnabled('global', 'room', roomId, true)); } return q.all(promises); @@ -110,7 +125,7 @@ export function setRoomNotifsState(roomId, newState) { function findOverrideMuteRule(roomId) { for (const rule of MatrixClientPeg.get().pushRules['global'].override) { if (isRuleForRoom(roomId, rule)) { - if (isMuteRule(rule)) { + if (isMuteRule(rule) && rule.enabled) { return rule; } } diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index 5b508b6ad0..e520b0bb00 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -49,29 +49,23 @@ module.exports = React.createClass({ badgeHover : false, notificationTagMenu: false, roomTagMenu: false, - notifState: this._getNotifState(), + notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId), }); }, - _getNotifState: function() { - if (MatrixClientPeg.get().isGuest()) return RoomNotifs.ALL_MESSAGES; - return RoomNotifs.getRoomNotifsState(this.props.room.roomId); - }, - _shouldShowNotifBadge: function() { const showBadgeInStates = [RoomNotifs.ALL_MESSAGES, RoomNotifs.ALL_MESSAGES_LOUD]; - const currentState = this._getNotifState(); - return showBadgeInStates.indexOf(currentState) > -1; + return showBadgeInStates.indexOf(this.state.notifState) > -1; }, _shouldShowMentionBadge: function() { - return this._getNotifState() != RoomNotifs.MUTE; + return this.state.notifState != RoomNotifs.MUTE; }, onAccountData: function(accountDataEvent) { if (accountDataEvent.getType() == 'm.push_rules') { this.setState({ - notifState: this._getNotifState(), + notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId), }); } }, @@ -193,7 +187,7 @@ module.exports = React.createClass({ 'mx_RoomTile_selected': this.props.selected, 'mx_RoomTile_unread': this.props.unread, 'mx_RoomTile_unreadNotify': notificationCount > 0 && this._shouldShowNotifBadge(), - 'mx_RoomTile_highlight': this.props.highlight && badges, + 'mx_RoomTile_highlight': this.props.highlight && this._shouldShowMentionBadge(), 'mx_RoomTile_invited': (me && me.membership == 'invite'), 'mx_RoomTile_notificationTagMenu': this.state.notificationTagMenu, 'mx_RoomTile_noBadges': !badges, From bab2f23db3b8547f87df5f7fb47d7e979dee47ba Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 18 Aug 2016 15:18:02 +0100 Subject: [PATCH 12/15] Oops, missed a constant --- src/RoomNotifs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/RoomNotifs.js b/src/RoomNotifs.js index 563491b695..f545275641 100644 --- a/src/RoomNotifs.js +++ b/src/RoomNotifs.js @@ -52,7 +52,7 @@ export function getRoomNotifsState(roomId) { } export function setRoomNotifsState(roomId, newState) { - if (newState == 'mute') { + if (newState == MUTE) { return setRoomNotifsStateMuted(roomId); } else { return setRoomNotifsStateUnmuted(roomId, newState); From fc2c62e8964fcaedff8f65fe9d7eea95cf2c6c0d Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 18 Aug 2016 15:19:24 +0100 Subject: [PATCH 13/15] We can use the new consts here --- src/components/views/rooms/RoomTile.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index e520b0bb00..dd1ca125aa 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -186,8 +186,8 @@ module.exports = React.createClass({ 'mx_RoomTile': true, 'mx_RoomTile_selected': this.props.selected, 'mx_RoomTile_unread': this.props.unread, - 'mx_RoomTile_unreadNotify': notificationCount > 0 && this._shouldShowNotifBadge(), - 'mx_RoomTile_highlight': this.props.highlight && this._shouldShowMentionBadge(), + 'mx_RoomTile_unreadNotify': notifBadges, + 'mx_RoomTile_highlight': mentionBadges, 'mx_RoomTile_invited': (me && me.membership == 'invite'), 'mx_RoomTile_notificationTagMenu': this.state.notificationTagMenu, 'mx_RoomTile_noBadges': !badges, From d08f7166815a6b100e2145510244f3c504c785fa Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 18 Aug 2016 15:21:46 +0100 Subject: [PATCH 14/15] Comment override rule stuff --- src/RoomNotifs.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/RoomNotifs.js b/src/RoomNotifs.js index f545275641..ea1e525769 100644 --- a/src/RoomNotifs.js +++ b/src/RoomNotifs.js @@ -69,7 +69,11 @@ function setRoomNotifsStateMuted(roomId) { promises.push(cli.deletePushRule('global', 'room', roomRule.rule_id)); } - // add an override rule to squelch everything in this room + // add/replace an override rule to squelch everything in this room + // NB. We use the room ID as the name of this rule too, although this + // is an override rule, not a room rule: it still pertains to this room + // though, so using the room ID as the rule ID is logical and prevents + // duplicate copies of the rule. promises.push(cli.addPushRule('global', 'override', roomId, { conditions: [ { From 5495cfaca9a7b35a238cc0f158c852348c355742 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 18 Aug 2016 16:59:25 +0100 Subject: [PATCH 15/15] Only try to delete room rule if it exists --- src/RoomNotifs.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/RoomNotifs.js b/src/RoomNotifs.js index ea1e525769..00cad23791 100644 --- a/src/RoomNotifs.js +++ b/src/RoomNotifs.js @@ -100,7 +100,10 @@ function setRoomNotifsStateUnmuted(roomId, newState) { } if (newState == 'all_messages') { - promises.push(cli.deletePushRule('global', 'room', roomId)); + const roomRule = cli.getRoomPushRule('global', roomId); + if (roomRule) { + promises.push(cli.deletePushRule('global', 'room', roomRule.rule_id)); + } } else if (newState == 'mentions_only') { promises.push(cli.addPushRule('global', 'room', roomId, { actions: [