From 731d94eea40f0c11df3f4aff5335df9d09995f35 Mon Sep 17 00:00:00 2001 From: Mark Haines Date: Fri, 26 Feb 2016 20:51:16 +0000 Subject: [PATCH 1/5] Update the actions of default rules instead of overriding. The Matrix CS API, and synapse now supports setting the actions for default rules. Doing that makes managing the rules much simpler from a vector persepctive since the ON/LOUD/OFF toggle buttons can be implemented by setting the actions and enabling/disabling the default rules rather than overidding them. Overriding the default rules was difficult because it was not possible to intermingle the evaluation of user-specified rules with the default rules. So even though you could add a rule with the same conditions as a default rule, it would evaluate before *all* the other default rules. Also creating new rules under a im.vector namespace creates challenges if we want vector to cooperate with other matrix clients that want to provide a similar set of toggle switches for the push rules. --- .../views/settings/Notifications.js | 389 +++++------------- 1 file changed, 99 insertions(+), 290 deletions(-) diff --git a/src/components/views/settings/Notifications.js b/src/components/views/settings/Notifications.js index 4c880760..dc46e099 100644 --- a/src/components/views/settings/Notifications.js +++ b/src/components/views/settings/Notifications.js @@ -37,6 +37,40 @@ var PushRuleVectorState = { OFF: "off" }; + +var ACTION_NOTIFY = [ "notify" ]; + +var ACTION_NOTIFY_DEFAULT_SOUND = [ + "notify", + { + "set_tweak": "sound", + "value": "default" + } +]; + +var ACTION_NOTIFY_RING_SOUND = [ + "notify", + { + "set_tweak": "sound", + "value": "ring" + } +]; + +var ACTION_HIGHLIGHT_DEFAULT_SOUND = [ + "notify", + { + "set_tweak": "sound", + "value": "default" + }, + { + "set_tweak":"highlight" + } +]; + +var ACTION_DONT_NOTIFY = [ "dont_notify" ]; + +var ACTION_DISABLED = null; + /** * The descriptions of rules managed by the Vector UI. * Each rule is described so that if the server does not have it in its default @@ -48,225 +82,71 @@ var VectorPushRulesDefinitions = { // Messages containing user's display name // (skip contains_user_name which is too geeky) - "im.vector.rule.contains_display_name": { + ".m.rule.contains_display_name": { kind: "underride", - hsDefaultRuleId: ".m.rule.contains_display_name", description: "Messages containing my name", - conditions: [{ - "kind": "contains_display_name" - }], - vectorStateToActions: { // The actions for each vector state - on: [ - "notify" - ], - loud: [ - "notify", - { - "set_tweak": "sound", - "value": "default" - }, - { - "set_tweak":"highlight" - } - ] - }, - vectorStateToHsDefaultRuleEnabled: { // If it exists, the hs default push rule enabled expected value for each vector state - on: undefined, // ON (and its actions) does not corresponds to the default hs push rule, so NA - loud: true, // LOUD corresponds to the default rule when its enabled value is true - off: false // OFF corresponds to the default rule when its enabled value is false - }, + vectorStateToActions: { // The actions for each vector state, or null to disable the rule. + on: ACTION_NOTIFY, + loud: ACTION_HIGHLIGHT_DEFAULT, + off: ACTION_DISABLED + } }, // Messages just sent to the user in a 1:1 room - "im.vector.rule.room_one_to_one": { + ".m.rule.room_one_to_one": { kind: "underride", - hsDefaultRuleId: ".m.rule.room_one_to_one", description: "Messages in one-to-one chats", - conditions: [{ - "is": "2", - "kind": "room_member_count" - }], vectorStateToActions: { - on: [ - "notify" - ], - loud: [ - "notify", - { - "set_tweak": "sound", - "value": "default" - } - ], - off: [ - "dont_notify" - ] - }, - vectorStateToHsDefaultRuleEnabled: { - on: undefined, - loud: true, - off: undefined + on: ACTION_NOTIFY, + loud: ACTION_NOTIFY_DEFAULT_SOUND, + off: ACTION_DONT_NOTIFY } }, // Messages just sent to a group chat room // 1:1 room messages are catched by the .m.rule.room_one_to_one rule if any defined // By opposition, all other room messages are from group chat rooms. - "im.vector.rule.room_message": { + ".m.rule.room_message": { kind: "underride", description: "Messages in group chats", - conditions: [{ - "pattern": "m.room.message", - "kind": "event_match", - "key": "type" - }], - hsDefaultRuleId: ".m.rule.message", vectorStateToActions: { - on: [ - "notify" - ], - loud: [ - "notify", - { - "set_tweak": "sound", - "value": "default" - } - ], - off: [ - "dont_notify" - ] - }, - vectorStateToHsDefaultRuleEnabled: { - on: true, - loud: undefined, - off: undefined + on: ACTION_NOTIFY, + loud: ACTION_NOTIFY_DEFAULT_SOUND, + off: ACTION_DONT_NOTIFY } }, // Invitation for the user - "im.vector.rule.invite_for_me": { + ".m.rule.invite_for_me": { kind: "underride", - hsDefaultRuleId: ".m.rule.invite_for_me", description: "When I'm invited to a room", - conditions: [ - { - "key": "type", - "kind": "event_match", - "pattern": "m.room.member" - }, - { - "key": "content.membership", - "kind": "event_match", - "pattern": "invite" - }, - { - "key": "state_key", - "kind": "event_match", - "pattern": "" // It is updated at runtime the user id - } - ], vectorStateToActions: { - on: [ - "notify" - ], - loud: [ - "notify", - { - "set_tweak": "sound", - "value": "default" - } - ] - }, - vectorStateToHsDefaultRuleEnabled: { - on: undefined, - loud: true, - off: false + on: ACTION_NOTIFY, + loud: ACTION_NOTIFY_DEFAULT_SOUND, + off: ACTION_DISABLED } }, - // When people join or leave a room - /*"im.vector.rule.member_event": { - hsDefaultRuleId: ".m.rule.member_event", - description: "When people join or leave a room", - conditions: [{ - "pattern": "m.room.member", - "kind": "event_match", - "key": "type" - }], - vectorStateToActions: { - on: [ - "notify" - ], - loud: [ - "notify", - { - "set_tweak": "sound", - "value": "default" - } - ] - }, - vectorStateToHsDefaultRuleEnabled: { - on: true, - loud: undefined, - off: false - } - },*/ - // Incoming call - "im.vector.rule.call": { + ".m.rule.call": { kind: "underride", - hsDefaultRuleId: ".m.rule.call", description: "Call invitation", - conditions: [{ - "pattern": "m.room.member", - "kind": "event_match", - "key": "type" - }], vectorStateToActions: { - on: [ - "notify" - ], - loud: [ - "notify", - { - "set_tweak": "sound", - "value": "ring" - } - ], - }, - vectorStateToHsDefaultRuleEnabled: { - on: undefined, - loud: true, - off: false + on: ACTION_NOTIFY, + loud: ACTION_NOTIFY_RING_SOUND, + off: ACTION_DISABLED } }, // Notifications from bots - "im.vector.rule.notices": { + ".m.rule.suppress_notices": { kind: "override", - hsDefaultRuleId: ".m.rule.suppress_notices", description: "Messages sent by bot", - conditions: [{ - "kind": "event_match", - "key": "content.msgtype", - "pattern": "m.notice" - }], vectorStateToActions: { - on: undefined, // ON for vector UI means that the .m.rule.suppress_notices rule is disabled. - loud: [ - "notify", - { - "set_tweak": "sound", - "value": "ring" - } - ], - off: [ - "dont_notify" - ] - }, - vectorStateToHsDefaultRuleEnabled: { - on: false, // .m.rule.suppress_notices is a "negative" rule, we have to invert its enabled value for vector UI - loud: undefined, - off: true + // .m.rule.suppress_notices is a "negative" rule, we have to invert its enabled value for vector UI + on: ACTION_DISABLED, + loud: ACTION_NOTIFY_DEFAULT_SOUND, + off: ACTION_DONT_NOTIFY, } } }; @@ -295,9 +175,6 @@ module.exports = React.createClass({ }, componentWillMount: function() { - // Finalise the vector definitions - VectorPushRulesDefinitions["im.vector.rule.invite_for_me"].conditions[2].pattern = MatrixClientPeg.get().credentials.userId; - this._refreshFromServer(); }, @@ -389,13 +266,10 @@ module.exports = React.createClass({ _actionsFor: function(pushRuleVectorState) { if (pushRuleVectorState === PushRuleVectorState.ON) { - return ['notify']; + return ACTIONS_NOTIFY; } else if (pushRuleVectorState === PushRuleVectorState.LOUD) { - return ['notify', - {'set_tweak': 'sound', 'value': 'default'}, - {'set_tweak': 'highlight', 'value': 'true'} - ];; + return ACTIONS_HIGHLIGHT_DEFAULT_SOUND; } }, @@ -437,36 +311,18 @@ module.exports = React.createClass({ var ruleDefinition = VectorPushRulesDefinitions[rule.vectorRuleId]; if (rule.rule) { - if (undefined !== ruleDefinition.vectorStateToHsDefaultRuleEnabled[newPushRuleVectorState] && rule.hsDefaultRule) { - // The new state corresponds to the default hs rule - // Enable or disable it according to the rule definition - deferreds.push(cli.setPushRuleEnabled('global', rule.hsDefaultRule.kind, ruleDefinition.hsDefaultRuleId, - ruleDefinition.vectorStateToHsDefaultRuleEnabled[newPushRuleVectorState])); + var actions = ruleDefinition.vectorStateToActions(newPushRuleVectorState); - // Remove the vector rule if any - if (!rule.isHSDefaultRule) { - deferreds.push(cli.deletePushRule('global', rule.rule.kind, rule.rule.rule_id)) - } + if (actions === ACTIONS_DISABLED) { + // The new state corresponds to disabling the rule. + deferreds.push(cli.setPushRuleEnabled('global', rule.rule.kind, rule.rule.rule_id, false)); } else { - // The new state (and its implied actions) does not correspond to a default hs rule - // or the HS does not expose this default rule. - if (rule.isHSDefaultRule) { - // Create a new rule that will override the default one - deferreds.push(this._addOverridingVectorPushRule(rule.vectorRuleId, newPushRuleVectorState)); - } - else { - // Change the actions of the existing overriding Vector rule - deferreds.push(this._updatePushRuleActions(rule.rule, ruleDefinition.vectorStateToActions[newPushRuleVectorState])); - } + // The new state corresponds to enabling the rule and setting specific actions + deferreds.push(this._updatePushRuleActions(rule.rule, actions, true)); } } - else { - // This is a Vector rule which does not exist yet server side - // Create it - deferreds.push(this._addOverridingVectorPushRule(rule.vectorRuleId, newPushRuleVectorState)); - } - + q.all(deferreds).done(function() { self._refreshFromServer(); }, function(error) { @@ -690,8 +546,6 @@ module.exports = React.createClass({ // HS default rules var defaultRules = {master: [], vector: {}, others: []}; - // Push rules defined py Vector to override hs default rules - var vectorOverridingRules = {}; // Content/keyword rules var contentRules = {on: [], on_but_disabled:[], loud: [], loud_but_disabled: [], other: []}; @@ -701,29 +555,16 @@ module.exports = React.createClass({ var cat = rule_categories[r.rule_id]; r.kind = kind; if (r.rule_id[0] === '.') { - if (cat) { - if (cat === 'vector') { - // Remove disabled, useless actions - r.actions = r.actions.reduce(function(array, action){ - if (action.value !== false) { - array.push(action); - } - return array; - },[]); - - defaultRules.vector[r.rule_id] = r; - } - else { - defaultRules[cat].push(r); - } + if (cat === 'vector') { + defaultRules.vector[r.rule_id] = r; + } + else if (cat === 'master') { + defaultRules.master.push(r); } else { defaultRules['others'].push(r); } } - else if (r.rule_id.startsWith('im.vector')) { - vectorOverridingRules[r.rule_id] = r; - } else if (kind === 'content') { switch (self._contentRuleVectorStateKind(r)) { case PushRuleVectorState.ON: @@ -804,14 +645,14 @@ module.exports = React.createClass({ self.state.vectorPushRules = []; var vectorRuleIds = [ - 'im.vector.rule.contains_display_name', + '.m.rule.contains_display_name', '_keywords', - 'im.vector.rule.room_one_to_one', - 'im.vector.rule.room_message', - 'im.vector.rule.invite_for_me', + '.m.rule.room_one_to_one', + '.m.rule.room_message', + '.m.rule.invite_for_me', //'im.vector.rule.member_event', - 'im.vector.rule.call', - 'im.vector.rule.notices' + '.m.rule.call', + '.m.rule.notices' ]; for (var i in vectorRuleIds) { var vectorRuleId = vectorRuleIds[i]; @@ -828,13 +669,7 @@ module.exports = React.createClass({ }); } else { - var rule = vectorOverridingRules[vectorRuleId]; - var isHSDefaultRule = false; - if (!rule) { - // If the rule is not defined, look at the hs default one - rule = defaultRules.vector[ruleDefinition.hsDefaultRuleId]; - isHSDefaultRule = true; - } + var rule = defaultRules.vector[vectorRuleId]; // Translate the rule actions and its enabled value into vector state var vectorState; @@ -843,9 +678,9 @@ module.exports = React.createClass({ var state = PushRuleVectorState[stateKey]; var vectorStateToActions = ruleDefinition.vectorStateToActions[state]; - if (!vectorStateToActions) { + if (vectorStateToActions === ACTIONS_DISABLED) { // No defined actions means that this vector state expects a disabled default hs rule - if (isHSDefaultRule && rule.enabled === ruleDefinition.vectorStateToHsDefaultRuleEnabled[state]) { + if (rule.enabled === false) { vectorState = state; break; } @@ -853,14 +688,8 @@ module.exports = React.createClass({ else { // The actions must match to the ones expected by vector state if (JSON.stringify(rule.actions) === JSON.stringify(vectorStateToActions)) { - if (isHSDefaultRule) { - // In the case of a default hs push rule, the enabled value must also match - if (rule.enabled === ruleDefinition.vectorStateToHsDefaultRuleEnabled[state]) { - vectorState = state; - break; - } - } - else { + // And the rule must be enabled. + if (rule.enabled === true) { vectorState = state; break; } @@ -870,7 +699,7 @@ module.exports = React.createClass({ if (!vectorState) { console.error("Cannot translate rule actions into Vector rule state. Rule: " + rule); - vectorState = PushRuleVectorState.OFF; + vectorState = PushRuleVectorState.OFF; } } else { @@ -882,9 +711,7 @@ module.exports = React.createClass({ "description" : ruleDefinition.description, "rule": rule, "vectorState": vectorState, - "isHSDefaultRule": isHSDefaultRule, - "hsDefaultRule": defaultRules.vector[ruleDefinition.hsDefaultRuleId] - }); + }); } } @@ -913,39 +740,21 @@ module.exports = React.createClass({ }, _updatePushRuleActions: function(rule, actions, enabled) { - // Workaround for SYN-590 : Push rule update fails - // Remove the rule and recreate it with the new actions var cli = MatrixClientPeg.get(); var deferred = q.defer(); - - cli.deletePushRule('global', rule.kind, rule.rule_id).done(function() { - cli.addPushRule('global', rule.kind, rule.rule_id, { - conditions: rule.conditions, - actions: actions, - pattern: rule.pattern - }).done(function() { - // Then, if requested, enabled or disabled the rule - if (undefined != enabled) { - cli.setPushRuleEnabled('global', rule.kind, rule.rule_id, enabled).done(function() { - deferred.resolve(); - }, function(err) { - deferred.reject(err); - }); - } - else { - deferred.resolve(); - } - }, function(err) { - deferred.reject(err); - }); - }, function(err) { - deferred.reject(err); - }); - - return deferred.promise; + return cli.setPushRuleActions( + 'global', rule.kind, rule.rule_id, actions + ).then( function() { + // Then, if requested, enabled or disabled the rule + if (undefined != enabled) { + return cli.setPushRuleEnabled( + 'global', rule.kind, rule.rule_id, enabled + ); + } + }); }, - + renderNotifRulesTableRow: function(title, className, pushRuleVectorState) { return ( From 3b2d0a6c01b632d0209eb8ef792b293f6971b642 Mon Sep 17 00:00:00 2001 From: Mark Haines Date: Mon, 29 Feb 2016 16:56:33 +0000 Subject: [PATCH 2/5] Fix up reading the push rules --- .../views/settings/Notifications.js | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/src/components/views/settings/Notifications.js b/src/components/views/settings/Notifications.js index dc46e099..5588d277 100644 --- a/src/components/views/settings/Notifications.js +++ b/src/components/views/settings/Notifications.js @@ -38,13 +38,23 @@ var PushRuleVectorState = { }; -var ACTION_NOTIFY = [ "notify" ]; +var ACTION_NOTIFY = [ + "notify", + { + "set_tweak": "highlight", + "value": false + } +]; var ACTION_NOTIFY_DEFAULT_SOUND = [ "notify", { "set_tweak": "sound", "value": "default" + }, + { + "set_tweak": "highlight", + "value": false } ]; @@ -53,6 +63,10 @@ var ACTION_NOTIFY_RING_SOUND = [ { "set_tweak": "sound", "value": "ring" + }, + { + "set_tweak": "highlight", + "value": false } ]; @@ -87,7 +101,7 @@ var VectorPushRulesDefinitions = { description: "Messages containing my name", vectorStateToActions: { // The actions for each vector state, or null to disable the rule. on: ACTION_NOTIFY, - loud: ACTION_HIGHLIGHT_DEFAULT, + loud: ACTION_HIGHLIGHT_DEFAULT_SOUND, off: ACTION_DISABLED } }, @@ -106,7 +120,7 @@ var VectorPushRulesDefinitions = { // Messages just sent to a group chat room // 1:1 room messages are catched by the .m.rule.room_one_to_one rule if any defined // By opposition, all other room messages are from group chat rooms. - ".m.rule.room_message": { + ".m.rule.message": { kind: "underride", description: "Messages in group chats", vectorStateToActions: { @@ -266,10 +280,10 @@ module.exports = React.createClass({ _actionsFor: function(pushRuleVectorState) { if (pushRuleVectorState === PushRuleVectorState.ON) { - return ACTIONS_NOTIFY; + return ACTION_NOTIFY; } else if (pushRuleVectorState === PushRuleVectorState.LOUD) { - return ACTIONS_HIGHLIGHT_DEFAULT_SOUND; + return ACTION_HIGHLIGHT_DEFAULT_SOUND; } }, @@ -311,9 +325,9 @@ module.exports = React.createClass({ var ruleDefinition = VectorPushRulesDefinitions[rule.vectorRuleId]; if (rule.rule) { - var actions = ruleDefinition.vectorStateToActions(newPushRuleVectorState); + var actions = ruleDefinition.vectorStateToActions[newPushRuleVectorState]; - if (actions === ACTIONS_DISABLED) { + if (actions === ACTION_DISABLED) { // The new state corresponds to disabling the rule. deferreds.push(cli.setPushRuleEnabled('global', rule.rule.kind, rule.rule.rule_id, false)); } @@ -648,11 +662,11 @@ module.exports = React.createClass({ '.m.rule.contains_display_name', '_keywords', '.m.rule.room_one_to_one', - '.m.rule.room_message', + '.m.rule.message', '.m.rule.invite_for_me', //'im.vector.rule.member_event', '.m.rule.call', - '.m.rule.notices' + '.m.rule.suppress_notices' ]; for (var i in vectorRuleIds) { var vectorRuleId = vectorRuleIds[i]; @@ -678,7 +692,7 @@ module.exports = React.createClass({ var state = PushRuleVectorState[stateKey]; var vectorStateToActions = ruleDefinition.vectorStateToActions[state]; - if (vectorStateToActions === ACTIONS_DISABLED) { + if (vectorStateToActions === ACTION_DISABLED) { // No defined actions means that this vector state expects a disabled default hs rule if (rule.enabled === false) { vectorState = state; From 8b1444c954bea2e6329bb75ab1c286e5ca9bb909 Mon Sep 17 00:00:00 2001 From: Mark Haines Date: Tue, 15 Mar 2016 10:48:47 +0000 Subject: [PATCH 3/5] Port the legacy im.vector rules to the new format on startup --- .../views/settings/Notifications.js | 110 +++++++++++++++--- 1 file changed, 95 insertions(+), 15 deletions(-) diff --git a/src/components/views/settings/Notifications.js b/src/components/views/settings/Notifications.js index 5588d277..8f296c8c 100644 --- a/src/components/views/settings/Notifications.js +++ b/src/components/views/settings/Notifications.js @@ -85,12 +85,9 @@ var ACTION_DONT_NOTIFY = [ "dont_notify" ]; var ACTION_DISABLED = null; + /** * The descriptions of rules managed by the Vector UI. - * Each rule is described so that if the server does not have it in its default - * rules or if the user wants to use actions ('PushRuleVectorState') that are - * different from the hs one, the code will create a new rule that will override - * the hs one. */ var VectorPushRulesDefinitions = { @@ -165,6 +162,66 @@ var VectorPushRulesDefinitions = { } }; +/** + * Rules that Vector used to set in order to override the actions of default rules. + * These are used to port peoples existing overrides to match the current API. + * These can be removed and forgotten once everyone has moved to the new client. + */ +var LEGACY_RULES = { + "im.vector.rule.contains_display_name": ".m.rule.contains_display_name", + "im.vector.rule.room_one_to_one": ".m.rule.room_one_to_one", + "im.vector.rule.room_message": ".m.rule.message", + "im.vector.rule.invite_for_me": ".m.rule.invite_for_me", + "im.vector.rule.call": ".m.rule.call", + "im.vector.rule.notices": ".m.rule.suppress_notices" +}; + +function portLegacyActions(actions) { + var notify = false; + var sound = null; + var highlight = false; + var unknown_action = false; + + for (var i = 0; i < actions.length; ++i) { + var action = actions[i]; + if (action === "notify") { + notify = true; + } else if (action === "dont_notify") { + notify = false; + } else if (typeof action === 'object') { + if (action.set_tweak === "sound") { + sound = action.value + } else if (action.set_tweak === "highlight") { + highlight = action.value; + } else { + unknown_action = true; + } + } else { + unknown_action = true; + } + } + + // We don't regconise one of the actions here, so we don't try to + // canonicalise them. + if (unknown_action) return actions; + + if (notify) { + var new_actions = ["notify"]; + if (sound !== null) { + new_actions.push({"set_tweak": "sound", "value": sound}); + } + if (highlight) { + new_actions.push({"set_tweak": "highlight"}); + } else { + new_actions.push({"set_tweak": "highlight", "value": false}); + } + return new_actions; + } else { + return ACTION_DONT_NOTIFY; + } +} + + module.exports = React.createClass({ displayName: 'Notififications', @@ -521,23 +578,45 @@ module.exports = React.createClass({ return deferred.promise; }, - // Add a push rule server side according to the 'VectorPushRulesDefinitions' spec - _addOverridingVectorPushRule: function(vectorRuleId, vectorState) { + // Check if any legacy im.vector rules need to be ported to the new API + // for overriding the actions of default rules. + _portRulesToNewAPI: function(rulesets) { var self = this; + var needsUpdate = []; + var cli = MatrixClientPeg.get(); - // Create the rule as predefined - var ruleDefinition = VectorPushRulesDefinitions[vectorRuleId]; - var body = { - conditions: ruleDefinition.conditions, - actions: ruleDefinition.vectorStateToActions[vectorState] + for (var kind in rulesets.global) { + var ruleset = rulesets.global[kind]; + for (var i = 0; i < ruleset.length; ++i) { + var rule = ruleset[i]; + if (rule.rule_id in LEGACY_RULES) { + console.log("Porting legacy rule", rule); + needsUpdate.push( function(kind, rule) { + return cli.setPushRuleActions( + 'global', kind, LEGACY_RULES[rule.rule_id], portLegacyActions(rule.actions) + ).then( function() { + return cli.deletePushRule('global', kind, rule.rule_id); + }) + }(kind, rule)); + } + } + } + + if (needsUpdate.length > 0) { + // If somme of the rules need to be ported then wait for the porting + // to happen and then fetch the rules again. + return q.allSettled(needsUpdate).then( function() { + return cli.getPushRules(); + }); + } else { + // Otherwise return the rules that we already have. + return rulesets; } - - return MatrixClientPeg.get().addPushRule('global', ruleDefinition.kind, vectorRuleId, body); }, _refreshFromServer: function() { var self = this; - MatrixClientPeg.get().getPushRules().done(function(rulesets) { + MatrixClientPeg.get().getPushRules().then(self._portRulesToNewAPI).done(function(rulesets) { MatrixClientPeg.get().pushRules = rulesets; // Get homeserver default rules and triage them by categories @@ -568,6 +647,7 @@ module.exports = React.createClass({ var r = rulesets.global[kind][i]; var cat = rule_categories[r.rule_id]; r.kind = kind; + if (r.rule_id[0] === '.') { if (cat === 'vector') { defaultRules.vector[r.rule_id] = r; @@ -842,7 +922,7 @@ module.exports = React.createClass({ // When enabled, the master rule inhibits all existing rules // So do not show all notification settings - if (this.state.masterPushRule.enabled) { + if (this.state.masterPushRule && this.state.masterPushRule.enabled) { return (
{masterPushRuleDiv} From 3224a4e49e3079f33ac1b50f420e15543611913a Mon Sep 17 00:00:00 2001 From: Mark Haines Date: Tue, 15 Mar 2016 11:26:32 +0000 Subject: [PATCH 4/5] Add helper functions for decoding and encoding lists of push actions --- .../views/settings/Notifications.js | 151 +++++++++--------- 1 file changed, 73 insertions(+), 78 deletions(-) diff --git a/src/components/views/settings/Notifications.js b/src/components/views/settings/Notifications.js index 8f296c8c..73a056ee 100644 --- a/src/components/views/settings/Notifications.js +++ b/src/components/views/settings/Notifications.js @@ -37,52 +37,81 @@ var PushRuleVectorState = { OFF: "off" }; - -var ACTION_NOTIFY = [ - "notify", - { - "set_tweak": "highlight", - "value": false +// Encodes a dictionary of { +// "notify": true/false, +// "sound": string or undefined, +// "highlight: true/false, +// } +// to a list of push actions. +function encodeActions(action) { + var notify = action.notify; + var sound = action.sound; + var highlight = action.highlight; + if (notify) { + var actions = ["notify"]; + if (sound) { + actions.push({"set_tweak": "sound", "value": sound}); + } + if (highlight) { + actions.push({"set_tweak": "highlight"}); + } else { + actions.push({"set_tweak": "highlight", "value": false}); + } + return actions; + } else { + return ["dont_notify"]; } -]; +} -var ACTION_NOTIFY_DEFAULT_SOUND = [ - "notify", - { - "set_tweak": "sound", - "value": "default" - }, - { - "set_tweak": "highlight", - "value": false +// Decode a list of actions to a dictionary of { +// "notify": true/false, +// "sound": string or undefined, +// "highlight: true/false, +// } +// If the actions couldn't be decoded then returns null. +function decodeActions(actions) { + var notify = false; + var sound = null; + var highlight = false; + + for (var i = 0; i < actions.length; ++i) { + var action = actions[i]; + if (action === "notify") { + notify = true; + } else if (action === "dont_notify") { + notify = false; + } else if (typeof action === 'object') { + if (action.set_tweak === "sound") { + sound = action.value + } else if (action.set_tweak === "highlight") { + highlight = action.value; + } else { + // We don't understand this kind of tweak, so give up. + return null; + } + } else { + // We don't understand this kind of action, so give up. + return null; + } } -]; -var ACTION_NOTIFY_RING_SOUND = [ - "notify", - { - "set_tweak": "sound", - "value": "ring" - }, - { - "set_tweak": "highlight", - "value": false + if (highlight === undefined) { + // If a highlight tweak is missing a value then it defaults to true. + highlight = true; } -]; -var ACTION_HIGHLIGHT_DEFAULT_SOUND = [ - "notify", - { - "set_tweak": "sound", - "value": "default" - }, - { - "set_tweak":"highlight" + var result = {notify: notify, highlight: highlight}; + if (sound !== null) { + result.sound = sound; } -]; - -var ACTION_DONT_NOTIFY = [ "dont_notify" ]; + return result; +} +var ACTION_NOTIFY = encodeActions({notify: true}); +var ACTION_NOTIFY_DEFAULT_SOUND = encodeActions({notify: true, sound: "default"}); +var ACTION_NOTIFY_RING_SOUND = encodeActions({notify: true, sound: "ring"}); +var ACTION_HIGHLIGHT_DEFAULT_SOUND = encodeActions({notify: true, sound: "default", highlight: true}); +var ACTION_DONT_NOTIFY = encodeActions({notify: false}); var ACTION_DISABLED = null; @@ -177,47 +206,13 @@ var LEGACY_RULES = { }; function portLegacyActions(actions) { - var notify = false; - var sound = null; - var highlight = false; - var unknown_action = false; - - for (var i = 0; i < actions.length; ++i) { - var action = actions[i]; - if (action === "notify") { - notify = true; - } else if (action === "dont_notify") { - notify = false; - } else if (typeof action === 'object') { - if (action.set_tweak === "sound") { - sound = action.value - } else if (action.set_tweak === "highlight") { - highlight = action.value; - } else { - unknown_action = true; - } - } else { - unknown_action = true; - } - } - - // We don't regconise one of the actions here, so we don't try to - // canonicalise them. - if (unknown_action) return actions; - - if (notify) { - var new_actions = ["notify"]; - if (sound !== null) { - new_actions.push({"set_tweak": "sound", "value": sound}); - } - if (highlight) { - new_actions.push({"set_tweak": "highlight"}); - } else { - new_actions.push({"set_tweak": "highlight", "value": false}); - } - return new_actions; + var decoded = decodeActions(actions); + if (decoded !== null) { + return encodeActions(decoded); } else { - return ACTION_DONT_NOTIFY; + // We don't recognise one of the actions here, so we don't try to + // canonicalise them. + return actions; } } From 9b85d88036b47affd4d7e13cd18d2fbe746eeaab Mon Sep 17 00:00:00 2001 From: Mark Haines Date: Tue, 15 Mar 2016 15:23:18 +0000 Subject: [PATCH 5/5] s/somme/some/ --- src/components/views/settings/Notifications.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/settings/Notifications.js b/src/components/views/settings/Notifications.js index 73a056ee..616c8618 100644 --- a/src/components/views/settings/Notifications.js +++ b/src/components/views/settings/Notifications.js @@ -598,7 +598,7 @@ module.exports = React.createClass({ } if (needsUpdate.length > 0) { - // If somme of the rules need to be ported then wait for the porting + // If some of the rules need to be ported then wait for the porting // to happen and then fetch the rules again. return q.allSettled(needsUpdate).then( function() { return cli.getPushRules();