diff --git a/CHANGELOG.md b/CHANGELOG.md
index c9faad2b..b0991182 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,27 @@
+Changes in [0.6.0](https://github.com/vector-im/vector-web/releases/tag/v0.6.0) (2016-04-19)
+============================================================================================
+[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.5.0...v0.6.0)
+
+ * Matthew/design tweaks
+ [\#1402](https://github.com/vector-im/vector-web/pull/1402)
+ * Improve handling of notification rules we can't parse
+ [\#1399](https://github.com/vector-im/vector-web/pull/1399)
+ * Do less mangling of jenkins builds
+ [\#1391](https://github.com/vector-im/vector-web/pull/1391)
+ * Start Notifications component refactor
+ [\#1386](https://github.com/vector-im/vector-web/pull/1386)
+ * make the UI fadable to help with decluttering
+ [\#1376](https://github.com/vector-im/vector-web/pull/1376)
+ * Get and display a user's pushers in settings
+ [\#1374](https://github.com/vector-im/vector-web/pull/1374)
+ * URL previewing support
+ [\#1343](https://github.com/vector-im/vector-web/pull/1343)
+ * 😄 Emoji autocomplete and unicode emoji to image conversion using emojione.
+ [\#1332](https://github.com/vector-im/vector-web/pull/1332)
+ * Show full-size avatar on MemberInfo avatar click
+ [\#1340](https://github.com/vector-im/vector-web/pull/1340)
+ * Numerous other changes via [matrix-react-sdk 0.5.1](https://github.com/matrix-org/matrix-react-sdk/blob/v0.5.1/CHANGELOG.md)
+
Changes in [0.5.0](https://github.com/vector-im/vector-web/releases/tag/v0.5.0) (2016-03-30)
============================================================================================
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.4.1...v0.5.0)
@@ -71,7 +95,7 @@ Changes in vector v0.1.2 (2015-10-28)
* Better hover-over on member list
* Support CAS auth
* Many other bug fixes
-
+
Changes in vector v0.1.1 (2015-08-10)
======================================
diff --git a/package.json b/package.json
index 6c85f552..43b17325 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "vector-web",
- "version": "0.5.0",
+ "version": "0.6.0",
"description": "Vector webapp",
"author": "matrix.org",
"repository": {
@@ -16,7 +16,7 @@
"build:css": "catw \"src/skins/vector/css/**/*.css\" -o vector/components.css --no-watch",
"build:compile": "babel --source-maps -d lib src",
"build:bundle": "NODE_ENV=production webpack -p lib/vector/index.js vector/bundle.js",
- "build:bundle:dev": "webpack --optimize-occurence-order lib/vector/index.js vector/bundle.js",
+ "build:bundle:dev": "NODE_ENV=production webpack --optimize-occurence-order lib/vector/index.js vector/bundle.js",
"build": "npm run build:css && npm run build:compile && npm run build:bundle",
"build:dev": "npm run build:css && npm run build:compile && npm run build:bundle:dev",
"package": "scripts/package.sh",
@@ -37,7 +37,7 @@
"extract-text-webpack-plugin": "^0.9.1",
"filesize": "^3.1.2",
"flux": "~2.0.3",
- "gemini-scrollbar": "matrix-org/gemini-scrollbar#7dc736d",
+ "gemini-scrollbar": "matrix-org/gemini-scrollbar#87ebaa7",
"gfm.css": "^1.1.1",
"highlight.js": "^9.0.0",
"linkifyjs": "^2.0.0-beta.4",
@@ -45,11 +45,11 @@
"matrix-react-sdk": "matrix-org/matrix-react-sdk#develop",
"modernizr": "^3.1.0",
"q": "^1.4.1",
- "react": "^0.14.8",
- "react-dnd": "^2.0.2",
- "react-dnd-html5-backend": "^2.0.0",
- "react-dom": "^0.14.2",
- "react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#869a86b",
+ "react": "^15.0.1",
+ "react-dnd": "^2.1.4",
+ "react-dnd-html5-backend": "^2.1.2",
+ "react-dom": "^15.0.1",
+ "react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#c3d942e",
"sanitize-html": "^1.11.1"
},
"devDependencies": {
@@ -72,7 +72,8 @@
"mocha": "^2.4.5",
"parallelshell": "^1.2.0",
"phantomjs-prebuilt": "^2.1.7",
- "react-addons-test-utils": "^0.14.8",
+ "react-addons-test-utils": "^15.0.1",
+ "react-addons-perf": "^15.0",
"rimraf": "^2.4.3",
"source-map-loader": "^0.1.5",
"webpack": "^1.12.14"
diff --git a/src/component-index.js b/src/component-index.js
index b25b5ef9..b3baf22a 100644
--- a/src/component-index.js
+++ b/src/component-index.js
@@ -19,6 +19,9 @@ limitations under the License.
* You can edit it you like, but your changes will be overwritten,
* so you'd just be trying to swim upstream like a salmon.
* You are not a salmon.
+ *
+ * To update it, run:
+ * ./reskindex.js -h header
*/
module.exports.components = require('matrix-react-sdk/lib/component-index').components;
@@ -29,6 +32,7 @@ module.exports.components['structures.LeftPanel'] = require('./components/struct
module.exports.components['structures.RightPanel'] = require('./components/structures/RightPanel');
module.exports.components['structures.RoomDirectory'] = require('./components/structures/RoomDirectory');
module.exports.components['structures.RoomSubList'] = require('./components/structures/RoomSubList');
+module.exports.components['structures.SearchBox'] = require('./components/structures/SearchBox');
module.exports.components['structures.ViewSource'] = require('./components/structures/ViewSource');
module.exports.components['views.elements.ImageView'] = require('./components/views/elements/ImageView');
module.exports.components['views.elements.Spinner'] = require('./components/views/elements/Spinner');
diff --git a/src/components/structures/BottomLeftMenu.js b/src/components/structures/BottomLeftMenu.js
index a4d89fcf..ae49a347 100644
--- a/src/components/structures/BottomLeftMenu.js
+++ b/src/components/structures/BottomLeftMenu.js
@@ -47,12 +47,19 @@ module.exports = React.createClass({
render: function() {
var BottomLeftMenuTile = sdk.getComponent('rooms.BottomLeftMenuTile');
+ var TintableSvg = sdk.getComponent('elements.TintableSvg');
return (
);
diff --git a/src/components/structures/LeftPanel.js b/src/components/structures/LeftPanel.js
index 5c27abc5..58347a06 100644
--- a/src/components/structures/LeftPanel.js
+++ b/src/components/structures/LeftPanel.js
@@ -31,6 +31,7 @@ var LeftPanel = React.createClass({
getInitialState: function() {
return {
showCallElement: null,
+ searchFilter: '',
};
},
@@ -84,9 +85,14 @@ var LeftPanel = React.createClass({
}
},
+ onSearch: function(term) {
+ this.setState({ searchFilter: term });
+ },
+
render: function() {
var RoomList = sdk.getComponent('rooms.RoomList');
var BottomLeftMenu = sdk.getComponent('structures.BottomLeftMenu');
+ var SearchBox = sdk.getComponent('structures.SearchBox');
var collapseButton;
var classes = "mx_LeftPanel mx_fadable";
@@ -110,11 +116,13 @@ var LeftPanel = React.createClass({
return (
+
{ collapseButton }
{ callPreview }
diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js
index eab75fe3..431bdba5 100644
--- a/src/components/structures/RightPanel.js
+++ b/src/components/structures/RightPanel.js
@@ -155,7 +155,10 @@ module.exports = React.createClass({
panel =
}
}
+ }
+ if (!panel) {
+ panel =
;
}
var classes = "mx_RightPanel mx_fadable";
@@ -169,6 +172,8 @@ module.exports = React.createClass({
{ buttonGroup }
{ panel }
+
+
);
}
diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js
index f3614092..d7808230 100644
--- a/src/components/structures/RoomDirectory.js
+++ b/src/components/structures/RoomDirectory.js
@@ -43,6 +43,14 @@ module.exports = React.createClass({
}
},
+ componentWillMount: function() {
+ // dis.dispatch({
+ // action: 'ui_opacity',
+ // sideOpacity: 0.3,
+ // middleOpacity: 0.3,
+ // });
+ },
+
componentDidMount: function() {
var self = this;
MatrixClientPeg.get().publicRooms(function (err, data) {
@@ -65,6 +73,14 @@ module.exports = React.createClass({
});
},
+ componentWillUnmount: function() {
+ // dis.dispatch({
+ // action: 'ui_opacity',
+ // sideOpacity: 1.0,
+ // middleOpacity: 1.0,
+ // });
+ },
+
showRoom: function(roomId) {
// extract the metadata from the publicRooms structure to pass
// as out-of-band data to view_room, because we get information
@@ -113,8 +129,8 @@ module.exports = React.createClass({
var rooms = this.state.publicRooms.filter(function(a) {
// FIXME: if incrementally typing, keep narrowing down the search set
// incrementally rather than starting over each time.
- return (((a.name && a.name.toLowerCase().search(filter.toLowerCase()) >= 0) ||
- (a.aliases && a.aliases[0].toLowerCase().search(filter.toLowerCase()) >= 0)) &&
+ return (((a.name && a.name.toLowerCase().search(filter.toLowerCase()) >= 0) ||
+ (a.aliases && a.aliases[0].toLowerCase().search(filter.toLowerCase()) >= 0)) &&
a.num_joined_members > 0);
}).sort(function(a,b) {
return a.num_joined_members - b.num_joined_members;
@@ -197,7 +213,8 @@ module.exports = React.createClass({
-
+
{ this.getRows(this.state.roomAlias) }
@@ -209,4 +226,3 @@ module.exports = React.createClass({
);
}
});
-
diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js
index 0b96ed18..497acdec 100644
--- a/src/components/structures/RoomSubList.js
+++ b/src/components/structures/RoomSubList.js
@@ -65,16 +65,12 @@ var RoomSubList = React.createClass({
selectedRoom: React.PropTypes.string.isRequired,
startAsHidden: React.PropTypes.bool,
showSpinner: React.PropTypes.bool, // true to show a spinner if 0 elements when expanded
-
- // TODO: Fix the name of this. This is too easily confused with the
- // "hidden" state which is the expanded (or not) view of the list of rooms.
- // What this prop *really* does is control whether the room name is displayed
- // so it should be named as such.
- collapsed: React.PropTypes.bool.isRequired,
+ collapsed: React.PropTypes.bool.isRequired, // is LeftPanel collapsed?
onHeaderClick: React.PropTypes.func,
alwaysShowHeader: React.PropTypes.bool,
incomingCall: React.PropTypes.object,
- onShowMoreRooms: React.PropTypes.func
+ onShowMoreRooms: React.PropTypes.func,
+ searchFilter: React.PropTypes.string,
},
getInitialState: function() {
@@ -93,13 +89,20 @@ var RoomSubList = React.createClass({
},
componentWillMount: function() {
- this.sortList(this.props.list, this.props.order);
+ this.sortList(this.applySearchFilter(this.props.list, this.props.searchFilter), this.props.order);
},
componentWillReceiveProps: function(newProps) {
// order the room list appropriately before we re-render
//if (debug) console.log("received new props, list = " + newProps.list);
- this.sortList(newProps.list, newProps.order);
+ this.sortList(this.applySearchFilter(newProps.list, newProps.searchFilter), newProps.order);
+ },
+
+ applySearchFilter: function(list, filter) {
+ if (filter === "") return list;
+ return list.filter((room) => {
+ return room.name && room.name.toLowerCase().indexOf(filter.toLowerCase()) >= 0
+ });
},
onClick: function(ev) {
@@ -278,7 +281,7 @@ var RoomSubList = React.createClass({
return (
{ this.props.collapsed ? '' : this.props.label }
-
diff --git a/src/components/structures/SearchBox.js b/src/components/structures/SearchBox.js
new file mode 100644
index 00000000..a49f845e
--- /dev/null
+++ b/src/components/structures/SearchBox.js
@@ -0,0 +1,109 @@
+/*
+Copyright 2015, 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.
+*/
+
+'use strict';
+
+var React = require('react');
+var sdk = require('matrix-react-sdk')
+var dis = require('matrix-react-sdk/lib/dispatcher');
+var rate_limited_func = require('matrix-react-sdk/lib/ratelimitedfunc');
+
+module.exports = React.createClass({
+ displayName: 'SearchBox',
+
+ propTypes: {
+ collapsed: React.PropTypes.bool,
+ onSearch: React.PropTypes.func,
+ },
+
+ getInitialState: function() {
+ return {
+ searchTerm: "",
+ };
+ },
+
+ onChange: function() {
+ if (!this.refs.search) return;
+ this.setState({ searchTerm: this.refs.search.value });
+ this.onSearch();
+ },
+
+ onSearch: new rate_limited_func(
+ function() {
+ this.props.onSearch(this.refs.search.value);
+ },
+ 100
+ ),
+
+ onToggleCollapse: function(show) {
+ if (show) {
+ dis.dispatch({
+ action: 'show_left_panel',
+ });
+ }
+ else {
+ dis.dispatch({
+ action: 'hide_left_panel',
+ });
+ }
+ },
+
+ render: function() {
+ var TintableSvg = sdk.getComponent('elements.TintableSvg');
+
+ var toggleCollapse;
+ if (this.props.collapsed) {
+ toggleCollapse =
+
+
+
+ }
+ else {
+ toggleCollapse =
+
+
+
+ }
+
+ var searchControls;
+ if (!this.props.collapsed) {
+ searchControls = [
+ ,
+
+ ];
+ }
+
+ var self = this;
+ return (
+
+ { searchControls }
+ { toggleCollapse }
+
+ );
+ }
+});
diff --git a/src/components/views/rooms/RoomTooltip.js b/src/components/views/rooms/RoomTooltip.js
index fbe2fa56..2f5de837 100644
--- a/src/components/views/rooms/RoomTooltip.js
+++ b/src/components/views/rooms/RoomTooltip.js
@@ -34,7 +34,7 @@ module.exports = React.createClass({
});
}
else {
- tooltip.style.top = tooltip.parentElement.getBoundingClientRect().top + "px";
+ tooltip.style.top = (70 + tooltip.parentElement.getBoundingClientRect().top) + "px";
tooltip.style.display = "block";
}
},
diff --git a/src/components/views/settings/Notifications.js b/src/components/views/settings/Notifications.js
index 46c47994..2a66fff4 100644
--- a/src/components/views/settings/Notifications.js
+++ b/src/components/views/settings/Notifications.js
@@ -27,9 +27,13 @@ var notifications = require('../../../notifications');
// TODO: this "view" component still has far to much application logic in it,
// which should be factored out to other files.
+// TODO: this component also does a lot of direct poking into this.state, which
+// is VERY NAUGHTY.
+
var NotificationUtils = notifications.NotificationUtils;
var VectorPushRulesDefinitions = notifications.VectorPushRulesDefinitions;
var PushRuleVectorState = notifications.PushRuleVectorState;
+var ContentRules = notifications.ContentRules;
/**
* Rules that Vector used to set in order to override the actions of default rules.
@@ -104,6 +108,7 @@ module.exports = React.createClass({
},
onNotifStateButtonClicked: function(event) {
+ // FIXME: use .bind() rather than className metadata here surely
var vectorRuleId = event.target.className.split("-")[0];
var newPushRuleVectorState = event.target.className.split("-")[1];
@@ -411,7 +416,10 @@ module.exports = React.createClass({
_refreshFromServer: function() {
var self = this;
- var pushRulesPromise = MatrixClientPeg.get().getPushRules().then(self._portRulesToNewAPI).done(function(rulesets) {
+ var pushRulesPromise = MatrixClientPeg.get().getPushRules().then(self._portRulesToNewAPI).then(function(rulesets) {
+ //console.log("resolving pushRulesPromise");
+
+ /// XXX seriously? wtf is this?
MatrixClientPeg.get().pushRules = rulesets;
// Get homeserver default rules and triage them by categories
@@ -434,8 +442,6 @@ module.exports = React.createClass({
// HS default rules
var defaultRules = {master: [], vector: {}, others: []};
- // Content/keyword rules
- var contentRules = {on: [], on_but_disabled:[], loud: [], loud_but_disabled: [], other: []};
for (var kind in rulesets.global) {
for (var i = 0; i < Object.keys(rulesets.global[kind]).length; ++i) {
@@ -454,84 +460,25 @@ module.exports = React.createClass({
defaultRules['others'].push(r);
}
}
- else if (kind === 'content') {
- switch (PushRuleVectorState.contentRuleVectorStateKind(r)) {
- case PushRuleVectorState.ON:
- if (r.enabled) {
- contentRules.on.push(r);
- }
- else {
- contentRules.on_but_disabled.push(r);
- }
- break;
- case PushRuleVectorState.LOUD:
- if (r.enabled) {
- contentRules.loud.push(r);
- }
- else {
- contentRules.loud_but_disabled.push(r);
- }
- break;
- default:
- contentRules.other.push(r);
- break;
- }
- }
}
}
- // Decide which content rules to display in Vector UI.
- // Vector displays a single global rule for a list of keywords
- // whereas Matrix has a push rule per keyword.
- // Vector can set the unique rule in ON, LOUD or OFF state.
- // Matrix has enabled/disabled plus a combination of (highlight, sound) tweaks.
-
- // The code below determines which set of user's content push rules can be
- // displayed by the vector UI.
- // Push rules that does not fit, ie defined by another Matrix client, ends
- // in self.state.externalContentRules.
- // There is priority in the determination of which set will be the displayed one.
- // The set with rules that have LOUD tweaks is the first choice. Then, the ones
- // with ON tweaks (no tweaks).
- if (contentRules.loud.length) {
- self.state.vectorContentRules = {
- vectorState: PushRuleVectorState.LOUD,
- rules: contentRules.loud
- }
- self.state.externalContentRules = [].concat(contentRules.loud_but_disabled, contentRules.on, contentRules.on_but_disabled, contentRules.other);
- }
- else if (contentRules.loud_but_disabled.length) {
- self.state.vectorContentRules = {
- vectorState: PushRuleVectorState.OFF,
- rules: contentRules.loud_but_disabled
- }
- self.state.externalContentRules = [].concat(contentRules.on, contentRules.on_but_disabled, contentRules.other);
- }
- else if (contentRules.on.length) {
- self.state.vectorContentRules = {
- vectorState: PushRuleVectorState.ON,
- rules: contentRules.on
- }
- self.state.externalContentRules = [].concat(contentRules.on_but_disabled, contentRules.other);
- }
- else if (contentRules.on_but_disabled.length) {
- self.state.vectorContentRules = {
- vectorState: PushRuleVectorState.OFF,
- rules: contentRules.on_but_disabled
- }
- self.state.externalContentRules = contentRules.other;
- }
- else {
- self.state.externalContentRules = contentRules.other;
- }
-
// Get the master rule if any defined by the hs
if (defaultRules.master.length > 0) {
self.state.masterPushRule = defaultRules.master[0];
}
+ // parse the keyword rules into our state
+ var contentRules = ContentRules.parseContentRules(rulesets);
+ self.state.vectorContentRules = {
+ vectorState: contentRules.vectorState,
+ rules: contentRules.rules,
+ };
+ self.state.externalContentRules = contentRules.externalRules;
+
// Build the rules displayed in the Vector UI matrix table
self.state.vectorPushRules = [];
+ self.state.externalPushRules = [];
var vectorRuleIds = [
'.m.rule.contains_display_name',
@@ -545,7 +492,6 @@ module.exports = React.createClass({
];
for (var i in vectorRuleIds) {
var vectorRuleId = vectorRuleIds[i];
- var ruleDefinition = VectorPushRulesDefinitions[vectorRuleId];
if (vectorRuleId === '_keywords') {
// keywords needs a special handling
@@ -558,42 +504,12 @@ module.exports = React.createClass({
});
}
else {
+ var ruleDefinition = VectorPushRulesDefinitions[vectorRuleId];
var rule = defaultRules.vector[vectorRuleId];
- // Translate the rule actions and its enabled value into vector state
- var vectorState;
- if (rule) {
- for (var stateKey in PushRuleVectorState) {
- var state = PushRuleVectorState[stateKey];
- var vectorStateToActions = ruleDefinition.vectorStateToActions[state];
+ var vectorState = ruleDefinition.ruleToVectorState(rule);
- if (!vectorStateToActions) {
- // No defined actions means that this vector state expects a disabled default hs rule
- if (rule.enabled === false) {
- vectorState = state;
- break;
- }
- }
- else {
- // The actions must match to the ones expected by vector state
- if (JSON.stringify(rule.actions) === JSON.stringify(vectorStateToActions)) {
- // And the rule must be enabled.
- if (rule.enabled === true) {
- vectorState = state;
- break;
- }
- }
- }
- }
-
- if (!vectorState) {
- console.error("Cannot translate rule actions into Vector rule state. Rule: " + rule);
- vectorState = PushRuleVectorState.OFF;
- }
- }
- else {
- vectorState = PushRuleVectorState.OFF;
- }
+ //console.log("Refreshing vectorPushRules for " + vectorRuleId +", "+ ruleDefinition.description +", " + rule +", " + vectorState);
self.state.vectorPushRules.push({
"vectorRuleId": vectorRuleId,
@@ -601,6 +517,12 @@ module.exports = React.createClass({
"rule": rule,
"vectorState": vectorState,
});
+
+ // if there was a rule which we couldn't parse, add it to the external list
+ if (rule && !vectorState) {
+ rule.description = ruleDefinition.description;
+ self.state.externalPushRules.push(rule);
+ }
}
}
@@ -610,7 +532,6 @@ module.exports = React.createClass({
'.m.rule.fallback': "Notify me for anything else"
};
- self.state.externalPushRules = [];
for (var i in defaultRules.others) {
var rule = defaultRules.others[i];
var ruleDescription = otherRulesDescriptions[rule.rule_id];
@@ -624,10 +545,11 @@ module.exports = React.createClass({
});
var pushersPromise = MatrixClientPeg.get().getPushers().then(function(resp) {
+ //console.log("resolving pushersPromise");
self.setState({pushers: resp.pushers});
});
- q.all([pushRulesPromise, pushersPromise]).done(function() {
+ q.all([pushRulesPromise, pushersPromise]).then(function() {
self.setState({
phase: self.phases.DISPLAY
});
@@ -635,7 +557,16 @@ module.exports = React.createClass({
self.setState({
phase: self.phases.ERROR
});
- });
+ }).finally(() => {
+ // actually explicitly update our state having been deep-manipulating it
+ self.setState({
+ masterPushRule: self.state.masterPushRule,
+ vectorContentRules: self.state.vectorContentRules,
+ vectorPushRules: self.state.vectorPushRules,
+ externalContentRules: self.state.externalContentRules,
+ externalPushRules: self.state.externalPushRules,
+ });
+ }).done();
},
_updatePushRuleActions: function(rule, actions, enabled) {
@@ -655,7 +586,7 @@ module.exports = React.createClass({
renderNotifRulesTableRow: function(title, className, pushRuleVectorState) {
return (
-
+
{title}
@@ -688,6 +619,7 @@ module.exports = React.createClass({
var rows = [];
for (var i in this.state.vectorPushRules) {
var rule = this.state.vectorPushRules[i];
+ //console.log("rendering: " + rule.description + ", " + rule.vectorRuleId + ", " + rule.vectorState);
rows.push(this.renderNotifRulesTableRow(rule.description, rule.vectorRuleId, rule.vectorState));
}
return rows;
@@ -731,13 +663,10 @@ module.exports = React.createClass({
render: function() {
var self = this;
+ var spinner;
if (this.state.phase === this.phases.LOADING) {
var Loader = sdk.getComponent("elements.Spinner");
- return (
-
-
-
- );
+ spinner = ;
}
if (this.state.masterPushRule) {
@@ -820,19 +749,21 @@ module.exports = React.createClass({
// and this wouldn't be hard to add.
var rows = [];
for (var i = 0; i < this.state.pushers.length; ++i) {
- var p = this.state.pushers[i];
-
- rows.push(
- {p.app_display_name}
- {p.device_display_name}
+ rows.push(
+ {this.state.pushers[i].app_display_name}
+ {this.state.pushers[i].device_display_name}
);
}
- devicesSection = (
-
- Application
- Device
-
- {rows}
+ devicesSection = (
+
+
+ Application
+ Device
+
+
+
+ {rows}
+
);
}
@@ -857,6 +788,8 @@ module.exports = React.createClass({
+ { spinner }
+
Off
On
-
Loud
+
Highlight & sound
diff --git a/src/notifications/ContentRules.js b/src/notifications/ContentRules.js
new file mode 100644
index 00000000..25a7bac9
--- /dev/null
+++ b/src/notifications/ContentRules.js
@@ -0,0 +1,125 @@
+/*
+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.
+*/
+
+'use strict';
+
+var PushRuleVectorState = require('./PushRuleVectorState');
+
+module.exports = {
+ /**
+ * Extract the keyword rules from a list of rules, and parse them
+ * into a form which is useful for Vector's UI.
+ *
+ * Returns an object containing:
+ * rules: the primary list of keyword rules
+ * vectorState: a PushRuleVectorState indicating whether those rules are
+ * OFF/ON/LOUD
+ * externalRules: a list of other keyword rules, with states other than
+ * vectorState
+ */
+ parseContentRules: function(rulesets) {
+ // first categorise the keyword rules in terms of their actions
+ var contentRules = this._categoriseContentRules(rulesets);
+
+ // Decide which content rules to display in Vector UI.
+ // Vector displays a single global rule for a list of keywords
+ // whereas Matrix has a push rule per keyword.
+ // Vector can set the unique rule in ON, LOUD or OFF state.
+ // Matrix has enabled/disabled plus a combination of (highlight, sound) tweaks.
+
+ // The code below determines which set of user's content push rules can be
+ // displayed by the vector UI.
+ // Push rules that does not fit, ie defined by another Matrix client, ends
+ // in externalRules.
+ // There is priority in the determination of which set will be the displayed one.
+ // The set with rules that have LOUD tweaks is the first choice. Then, the ones
+ // with ON tweaks (no tweaks).
+
+ if (contentRules.loud.length) {
+ return {
+ vectorState: PushRuleVectorState.LOUD,
+ rules: contentRules.loud,
+ externalRules: [].concat(contentRules.loud_but_disabled, contentRules.on, contentRules.on_but_disabled, contentRules.other),
+ };
+ }
+ else if (contentRules.loud_but_disabled.length) {
+ return {
+ vectorState: PushRuleVectorState.OFF,
+ rules: contentRules.loud_but_disabled,
+ externalRules: [].concat(contentRules.on, contentRules.on_but_disabled, contentRules.other),
+ };
+ }
+ else if (contentRules.on.length) {
+ return {
+ vectorState: PushRuleVectorState.ON,
+ rules: contentRules.on,
+ externalRules: [].concat(contentRules.on_but_disabled, contentRules.other),
+ };
+ }
+ else if (contentRules.on_but_disabled.length) {
+ return {
+ vectorState: PushRuleVectorState.OFF,
+ rules: contentRules.on_but_disabled,
+ externalRules: contentRules.other,
+ }
+ } else {
+ return {
+ vectorState: PushRuleVectorState.ON,
+ rules: [],
+ externalRules: contentRules.other,
+ }
+ }
+ },
+
+ _categoriseContentRules: function(rulesets) {
+ var contentRules = {on: [], on_but_disabled:[], loud: [], loud_but_disabled: [], other: []};
+ for (var kind in rulesets.global) {
+ for (var i = 0; i < Object.keys(rulesets.global[kind]).length; ++i) {
+ var r = rulesets.global[kind][i];
+
+ // check it's not a default rule
+ if (r.rule_id[0] === '.' || kind !== 'content') {
+ continue;
+ }
+
+ r.kind = kind; // is this needed? not sure
+
+ switch (PushRuleVectorState.contentRuleVectorStateKind(r)) {
+ case PushRuleVectorState.ON:
+ if (r.enabled) {
+ contentRules.on.push(r);
+ }
+ else {
+ contentRules.on_but_disabled.push(r);
+ }
+ break;
+ case PushRuleVectorState.LOUD:
+ if (r.enabled) {
+ contentRules.loud.push(r);
+ }
+ else {
+ contentRules.loud_but_disabled.push(r);
+ }
+ break;
+ default:
+ contentRules.other.push(r);
+ break;
+ }
+ }
+ }
+ return contentRules;
+ },
+};
diff --git a/src/notifications/PushRuleVectorState.js b/src/notifications/PushRuleVectorState.js
index 5c6934aa..c838aa20 100644
--- a/src/notifications/PushRuleVectorState.js
+++ b/src/notifications/PushRuleVectorState.js
@@ -16,12 +16,10 @@ limitations under the License.
'use strict';
-/**
- * Enum for state of a push rule as defined by the Vector UI.
- * @readonly
- * @enum {string}
- */
-module.exports = {
+var StandardActions = require('./StandardActions');
+var NotificationUtils = require('./NotificationUtils');
+
+var states = {
/** The push rule is disabled */
OFF: "off",
@@ -31,6 +29,16 @@ module.exports = {
/** The user will receive push notification for this rule with sound and
highlight if this is legitimate */
LOUD: "loud",
+};
+
+
+module.exports = {
+ /**
+ * Enum for state of a push rule as defined by the Vector UI.
+ * @readonly
+ * @enum {string}
+ */
+ states: states,
/**
* Convert a PushRuleVectorState to a list of actions
@@ -39,10 +47,10 @@ module.exports = {
*/
actionsFor: function(pushRuleVectorState) {
if (pushRuleVectorState === this.ON) {
- return ACTION_NOTIFY;
+ return StandardActions.ACTION_NOTIFY;
}
else if (pushRuleVectorState === this.LOUD) {
- return ACTION_HIGHLIGHT_DEFAULT_SOUND;
+ return StandardActions.ACTION_HIGHLIGHT_DEFAULT_SOUND;
}
},
@@ -51,20 +59,24 @@ module.exports = {
*
* Determines whether a content rule is in the PushRuleVectorState.ON
* category or in PushRuleVectorState.LOUD, regardless of its enabled
- * state. Returns undefined if it does not match these categories.
+ * state. Returns null if it does not match these categories.
*/
contentRuleVectorStateKind: function(rule) {
- var stateKind;
+ var decoded = NotificationUtils.decodeActions(rule.actions);
+
+ if (!decoded) {
+ return null;
+ }
// Count tweaks to determine if it is a ON or LOUD rule
var tweaks = 0;
- for (var j in rule.actions) {
- var action = rule.actions[j];
- if (action.set_tweak === 'sound' ||
- (action.set_tweak === 'highlight' && action.value)) {
- tweaks++;
- }
+ if (decoded.sound) {
+ tweaks++;
}
+ if (decoded.highlight) {
+ tweaks++;
+ }
+ var stateKind = null;
switch (tweaks) {
case 0:
stateKind = this.ON;
@@ -76,3 +88,7 @@ module.exports = {
return stateKind;
},
};
+
+for (var k in states) {
+ module.exports[k] = states[k];
+};
diff --git a/src/notifications/StandardActions.js b/src/notifications/StandardActions.js
new file mode 100644
index 00000000..22a8f1db
--- /dev/null
+++ b/src/notifications/StandardActions.js
@@ -0,0 +1,30 @@
+/*
+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.
+*/
+
+'use strict';
+
+var NotificationUtils = require('./NotificationUtils');
+
+var encodeActions = NotificationUtils.encodeActions;
+
+module.exports = {
+ ACTION_NOTIFY: encodeActions({notify: true}),
+ ACTION_NOTIFY_DEFAULT_SOUND: encodeActions({notify: true, sound: "default"}),
+ ACTION_NOTIFY_RING_SOUND: encodeActions({notify: true, sound: "ring"}),
+ ACTION_HIGHLIGHT_DEFAULT_SOUND: encodeActions({notify: true, sound: "default", highlight: true}),
+ ACTION_DONT_NOTIFY: encodeActions({notify: false}),
+ ACTION_DISABLED: null,
+};
diff --git a/src/notifications/VectorPushRulesDefinitions.js b/src/notifications/VectorPushRulesDefinitions.js
index 8e2a0a65..dfbc06c0 100644
--- a/src/notifications/VectorPushRulesDefinitions.js
+++ b/src/notifications/VectorPushRulesDefinitions.js
@@ -16,17 +16,47 @@ limitations under the License.
'use strict';
-var NotificationUtils = require('./NotificationUtils');
+var StandardActions = require('./StandardActions');
+var PushRuleVectorState = require('./PushRuleVectorState');
-var encodeActions = NotificationUtils.encodeActions;
-var decodeActions = NotificationUtils.decodeActions;
+class VectorPushRuleDefinition {
+ constructor(opts) {
+ this.kind = opts.kind;
+ this.description = opts.description;
+ this.vectorStateToActions = opts.vectorStateToActions;
+ }
-const ACTION_NOTIFY = encodeActions({notify: true});
-const ACTION_NOTIFY_DEFAULT_SOUND = encodeActions({notify: true, sound: "default"});
-const ACTION_NOTIFY_RING_SOUND = encodeActions({notify: true, sound: "ring"});
-const ACTION_HIGHLIGHT_DEFAULT_SOUND = encodeActions({notify: true, sound: "default", highlight: true});
-const ACTION_DONT_NOTIFY = encodeActions({notify: false});
-const ACTION_DISABLED = null;
+ // Translate the rule actions and its enabled value into vector state
+ ruleToVectorState(rule) {
+ var enabled = false;
+ var actions = null;
+ if (rule) {
+ enabled = rule.enabled;
+ actions = rule.actions;
+ }
+
+ for (var stateKey in PushRuleVectorState.states) {
+ var state = PushRuleVectorState.states[stateKey];
+ var vectorStateToActions = this.vectorStateToActions[state];
+
+ if (!vectorStateToActions) {
+ // No defined actions means that this vector state expects a disabled (or absent) rule
+ if (!enabled) {
+ return state;
+ }
+ } else {
+ // The actions must match to the ones expected by vector state
+ if (enabled && JSON.stringify(rule.actions) === JSON.stringify(vectorStateToActions)) {
+ return state;
+ }
+ }
+ }
+
+ console.error("Cannot translate rule actions into Vector rule state. Rule: " +
+ JSON.stringify(rule));
+ return undefined;
+ }
+};
/**
* The descriptions of rules managed by the Vector UI.
@@ -34,71 +64,71 @@ const ACTION_DISABLED = null;
module.exports = {
// Messages containing user's display name
// (skip contains_user_name which is too geeky)
- ".m.rule.contains_display_name": {
+ ".m.rule.contains_display_name": new VectorPushRuleDefinition({
kind: "underride",
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_SOUND,
- off: ACTION_DISABLED
+ on: StandardActions.ACTION_NOTIFY,
+ loud: StandardActions.ACTION_HIGHLIGHT_DEFAULT_SOUND,
+ off: StandardActions.ACTION_DISABLED
}
- },
+ }),
// Messages just sent to the user in a 1:1 room
- ".m.rule.room_one_to_one": {
+ ".m.rule.room_one_to_one": new VectorPushRuleDefinition({
kind: "underride",
description: "Messages in one-to-one chats",
vectorStateToActions: {
- on: ACTION_NOTIFY,
- loud: ACTION_NOTIFY_DEFAULT_SOUND,
- off: ACTION_DONT_NOTIFY
+ on: StandardActions.ACTION_NOTIFY,
+ loud: StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
+ off: StandardActions.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.
- ".m.rule.message": {
+ ".m.rule.message": new VectorPushRuleDefinition({
kind: "underride",
description: "Messages in group chats",
vectorStateToActions: {
- on: ACTION_NOTIFY,
- loud: ACTION_NOTIFY_DEFAULT_SOUND,
- off: ACTION_DONT_NOTIFY
+ on: StandardActions.ACTION_NOTIFY,
+ loud: StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
+ off: StandardActions.ACTION_DONT_NOTIFY
}
- },
+ }),
// Invitation for the user
- ".m.rule.invite_for_me": {
+ ".m.rule.invite_for_me": new VectorPushRuleDefinition({
kind: "underride",
description: "When I'm invited to a room",
vectorStateToActions: {
- on: ACTION_NOTIFY,
- loud: ACTION_NOTIFY_DEFAULT_SOUND,
- off: ACTION_DISABLED
+ on: StandardActions.ACTION_NOTIFY,
+ loud: StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
+ off: StandardActions.ACTION_DISABLED
}
- },
+ }),
// Incoming call
- ".m.rule.call": {
+ ".m.rule.call": new VectorPushRuleDefinition({
kind: "underride",
description: "Call invitation",
vectorStateToActions: {
- on: ACTION_NOTIFY,
- loud: ACTION_NOTIFY_RING_SOUND,
- off: ACTION_DISABLED
+ on: StandardActions.ACTION_NOTIFY,
+ loud: StandardActions.ACTION_NOTIFY_RING_SOUND,
+ off: StandardActions.ACTION_DISABLED
}
- },
+ }),
// Notifications from bots
- ".m.rule.suppress_notices": {
+ ".m.rule.suppress_notices": new VectorPushRuleDefinition({
kind: "override",
description: "Messages sent by bot",
vectorStateToActions: {
// .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,
+ on: StandardActions.ACTION_DISABLED,
+ loud: StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
+ off: StandardActions.ACTION_DONT_NOTIFY,
}
- }
+ }),
};
diff --git a/src/notifications/index.js b/src/notifications/index.js
index 9672b67c..8ed77e9d 100644
--- a/src/notifications/index.js
+++ b/src/notifications/index.js
@@ -20,4 +20,5 @@ module.exports = {
NotificationUtils: require('./NotificationUtils'),
PushRuleVectorState: require('./PushRuleVectorState'),
VectorPushRulesDefinitions: require('./VectorPushRulesDefinitions'),
+ ContentRules: require('./ContentRules'),
};
diff --git a/src/skins/vector/css/matrix-react-sdk/structures/RoomStatusBar.css b/src/skins/vector/css/matrix-react-sdk/structures/RoomStatusBar.css
index 0f6955ce..4d91755c 100644
--- a/src/skins/vector/css/matrix-react-sdk/structures/RoomStatusBar.css
+++ b/src/skins/vector/css/matrix-react-sdk/structures/RoomStatusBar.css
@@ -1,7 +1,23 @@
+/*
+Copyright 2015, 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.
+*/
+
.mx_RoomStatusBar {
- margin-top: 5px;
+ margin-top: 15px;
margin-left: 65px;
- min-height: 24px;
+ min-height: 34px;
}
/* position the indicator in the same place horizontally as .mx_EventTile_avatar. */
@@ -17,8 +33,9 @@
.mx_RoomStatusBar_placeholderIndicator span {
color: #4a4a4a;
opacity: 0.5;
-/*
position: relative;
+ top: -4px;
+/*
animation-duration: 1s;
animation-name: bounce;
animation-direction: alternate;
@@ -99,7 +116,7 @@
.mx_RoomStatusBar_tabCompleteWrapper {
display: flex;
display: -webkit-flex;
- height: 24px;
+ height: 26px;
}
.mx_RoomStatusBar_tabCompleteWrapper .mx_TabCompleteBar {
diff --git a/src/skins/vector/css/matrix-react-sdk/structures/RoomView.css b/src/skins/vector/css/matrix-react-sdk/structures/RoomView.css
index 4c014461..c8772b49 100644
--- a/src/skins/vector/css/matrix-react-sdk/structures/RoomView.css
+++ b/src/skins/vector/css/matrix-react-sdk/structures/RoomView.css
@@ -36,8 +36,8 @@ limitations under the License.
-webkit-order: 1;
order: 1;
- -webkit-flex: 0 0 83px;
- flex: 0 0 83px;
+ -webkit-flex: 0 0 70px;
+ flex: 0 0 70px;
}
.mx_RoomView_fileDropTarget {
@@ -64,7 +64,7 @@ limitations under the License.
border: 2px #e1dddd solid;
border-bottom: none;
position: absolute;
- top: 83px;
+ top: 70px;
bottom: 0px;
z-index: 3000;
}
@@ -89,7 +89,7 @@ limitations under the License.
margin: auto;
overflow: auto;
- border-bottom: 1px solid #ccc;
+ border-bottom: 1px solid #e5e5e5;
-webkit-flex: 0 0 auto;
flex: 0 0 auto;
@@ -158,7 +158,7 @@ limitations under the License.
margin-bottom: 8px;
margin-left: 63px;
padding-bottom: 6px;
- border-bottom: 1px solid #eee;
+ border-bottom: 1px solid #e5e5e5;
}
.mx_RoomView_invitePrompt {
@@ -207,11 +207,12 @@ hr.mx_RoomView_myReadMarker {
.mx_RoomView_statusAreaBox {
max-width: 960px;
margin: auto;
- min-height: 36px;
+ min-height: 60px;
}
.mx_RoomView_statusAreaBox_line {
- border-top: 1px solid #eee;
+ margin-left: 65px;
+ border-top: 1px solid #e5e5e5;
height: 1px;
}
diff --git a/src/skins/vector/css/matrix-react-sdk/structures/SearchBox.css b/src/skins/vector/css/matrix-react-sdk/structures/SearchBox.css
new file mode 100644
index 00000000..93e6d7c8
--- /dev/null
+++ b/src/skins/vector/css/matrix-react-sdk/structures/SearchBox.css
@@ -0,0 +1,63 @@
+/*
+Copyright 2015, 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.
+*/
+
+.mx_SearchBox {
+ height: 24px;
+ margin-left: 16px;
+ margin-right: 20px;
+ padding-top: 24px;
+ padding-bottom: 22px;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+
+ display: flex;
+ display: -webkit-flex;
+}
+
+.mx_SearchBox_searchButton {
+ margin-right: 10px;
+}
+
+.mx_SearchBox_search {
+ flex: 1 1 auto;
+ -webkit-flex: 1 1 auto;
+ width: 0px;
+ font-family: 'Open Sans', Arial, Helvetica, Sans-Serif;
+ font-size: 12px;
+ margin-top: -2px;
+ height: 24px;
+ border: 0px ! important;
+ /* border-bottom: 1px solid rgba(0, 0, 0, 0.1) ! important; */
+ background-color: transparent;
+ border: 0px;
+}
+
+.mx_SearchBox_minimise,
+.mx_SearchBox_maximise {
+ margin-top: 3px;
+ cursor: pointer;
+}
+
+.mx_SearchBox_minimise {
+ margin-left: 10px;
+}
+
+.mx_SearchBox_maximise {
+ margin-left: 9px;
+}
+
+.mx_SearchBox object {
+ pointer-events: none;
+}
\ No newline at end of file
diff --git a/src/skins/vector/css/matrix-react-sdk/structures/UploadBar.css b/src/skins/vector/css/matrix-react-sdk/structures/UploadBar.css
index 5a22e91c..b489e132 100644
--- a/src/skins/vector/css/matrix-react-sdk/structures/UploadBar.css
+++ b/src/skins/vector/css/matrix-react-sdk/structures/UploadBar.css
@@ -1,16 +1,33 @@
+/*
+Copyright 2015, 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.
+*/
+
.mx_UploadBar {
position: relative;
}
.mx_UploadBar_uploadProgressOuter {
- height: 4px;
+ height: 5px;
margin-left: 63px;
margin-top: -1px;
+ padding-bottom: 5px;
}
.mx_UploadBar_uploadProgressInner {
background-color: #76cfa6;
- height: 4px;
+ height: 5px;
}
.mx_UploadBar_uploadFilename {
@@ -22,7 +39,7 @@
.mx_UploadBar_uploadIcon {
float: left;
- margin-top: 1px;
+ margin-top: 5px;
margin-left: 14px;
}
diff --git a/src/skins/vector/css/matrix-react-sdk/structures/UserSettings.css b/src/skins/vector/css/matrix-react-sdk/structures/UserSettings.css
index 71a9b299..2ab1f5eb 100644
--- a/src/skins/vector/css/matrix-react-sdk/structures/UserSettings.css
+++ b/src/skins/vector/css/matrix-react-sdk/structures/UserSettings.css
@@ -36,8 +36,8 @@ limitations under the License.
-webkit-order: 1;
order: 1;
- -webkit-flex: 0 0 83px;
- flex: 0 0 83px;
+ -webkit-flex: 0 0 70px;
+ flex: 0 0 70px;
}
.mx_UserSettings_body {
@@ -50,9 +50,25 @@ limitations under the License.
-webkit-flex: 1 1 0;
flex: 1 1 0;
+ margin-top: -20px;
overflow-y: auto;
}
+.mx_UserSettings h3 {
+ clear: both;
+ margin-left: 63px;
+ text-transform: uppercase;
+ color: #3d3b39;
+ font-weight: 600;
+ font-size: 13px;
+ margin-top: 26px;
+ margin-bottom: 10px;
+}
+
+.mx_UserSettings_section h3 {
+ margin-left: 0px;
+}
+
.mx_UserSettings_spinner {
display: inline-block;
vertical-align: middle;
@@ -78,22 +94,6 @@ limitations under the License.
cursor: pointer;
}
-.mx_UserSettings h2 {
- clear: both;
- margin-top: 32px;
- margin-bottom: 8px;
- margin-left: 63px;
- padding-bottom: 6px;
- border-bottom: 1px solid #eee;
-}
-
-.mx_UserSettings h3 {
- font-weight: bold;
- font-size: 15px;
- margin-top: 4px;
- margin-bottom: 4px;
-}
-
.mx_UserSettings_section {
margin-left: 63px;
margin-top: 28px;
@@ -106,6 +106,10 @@ limitations under the License.
display: table;
}
+.mx_UserSettings_notifTable .mx_Spinner {
+ position: absolute;
+}
+
.mx_UserSettings_profileTable
{
display: table;
diff --git a/src/skins/vector/css/matrix-react-sdk/views/rooms/MemberInfo.css b/src/skins/vector/css/matrix-react-sdk/views/rooms/MemberInfo.css
index e6a340df..30de9324 100644
--- a/src/skins/vector/css/matrix-react-sdk/views/rooms/MemberInfo.css
+++ b/src/skins/vector/css/matrix-react-sdk/views/rooms/MemberInfo.css
@@ -15,6 +15,7 @@ limitations under the License.
*/
.mx_MemberInfo {
+ margin-top: 20px;
height: 100%;
}
diff --git a/src/skins/vector/css/matrix-react-sdk/views/rooms/MemberList.css b/src/skins/vector/css/matrix-react-sdk/views/rooms/MemberList.css
index 283addcb..88f7fafe 100644
--- a/src/skins/vector/css/matrix-react-sdk/views/rooms/MemberList.css
+++ b/src/skins/vector/css/matrix-react-sdk/views/rooms/MemberList.css
@@ -17,6 +17,8 @@ limitations under the License.
.mx_MemberList {
height: 100%;
+ margin-top: 12px;
+
-webkit-flex: 1;
flex: 1;
@@ -77,17 +79,6 @@ limitations under the License.
}
*/
-.mx_MemberList_bottom {
- order: 4;
- flex: 0 0 72px;
- -webkit-flex: 0 0 72px;
-}
-
-.mx_MemberList_bottomRule {
- border-top: 2px solid #e1dddd;
- margin-right: 15px;
-}
-
.mx_MemberList_invited h2 {
text-transform: uppercase;
color: #3d3b39;
diff --git a/src/skins/vector/css/matrix-react-sdk/views/rooms/MessageComposer.css b/src/skins/vector/css/matrix-react-sdk/views/rooms/MessageComposer.css
index 9ed5f1cb..daf15001 100644
--- a/src/skins/vector/css/matrix-react-sdk/views/rooms/MessageComposer.css
+++ b/src/skins/vector/css/matrix-react-sdk/views/rooms/MessageComposer.css
@@ -18,7 +18,7 @@ limitations under the License.
max-width: 960px;
vertical-align: middle;
margin: auto;
- border-top: 2px solid #e1dddd;
+ border-top: 1px solid #e5e5e5;
}
.mx_MessageComposer_row {
@@ -45,7 +45,7 @@ limitations under the License.
display: table-cell;
width: 100%;
vertical-align: middle;
- height: 70px;
+ height: 60px;
text-align: center;
font-style: italic;
color: #888;
@@ -55,7 +55,7 @@ limitations under the License.
display: table-cell;
width: 100%;
vertical-align: middle;
- height: 70px;
+ height: 60px;
}
.mx_MessageComposer_input textarea {
diff --git a/src/skins/vector/css/matrix-react-sdk/views/rooms/RoomHeader.css b/src/skins/vector/css/matrix-react-sdk/views/rooms/RoomHeader.css
index eeb45b4d..c0e919b8 100644
--- a/src/skins/vector/css/matrix-react-sdk/views/rooms/RoomHeader.css
+++ b/src/skins/vector/css/matrix-react-sdk/views/rooms/RoomHeader.css
@@ -23,8 +23,7 @@ limitations under the License.
.mx_RoomHeader_wrapper {
max-width: 960px;
margin: auto;
- height: 83px;
- border-bottom: 1px solid #eeeeee;
+ height: 70px;
-webkit-align-items: center;
align-items: center;
@@ -36,10 +35,6 @@ limitations under the License.
display: flex;
}
-.mx_RoomHeader_editing .mx_RoomHeader_wrapper {
- border-bottom: 1px solid transparent;
-}
-
.mx_RoomHeader_leftRow {
margin-left: -2px;
@@ -123,7 +118,7 @@ limitations under the License.
}
.mx_RoomHeader_simpleHeader {
- line-height: 83px;
+ line-height: 70px;
color: #454545;
font-size: 22px;
font-weight: bold;
@@ -133,11 +128,8 @@ limitations under the License.
width: 100%;
}
-.mx_RoomHeader_simpleHeaderCancel {
+.mx_RoomHeader_simpleHeader .mx_RoomHeader_cancelButton {
float: right;
- margin-top: 8px;
- padding: 24px;
- cursor: pointer;
}
.mx_RoomHeader_name {
@@ -215,8 +207,9 @@ limitations under the License.
vertical-align: bottom;
float: left;
max-height: 42px;
- color: #454545;
+ color: #A2A2A2;
font-weight: 300;
+ font-size: 13px;
margin-left: 19px;
margin-right: 16px;
overflow: hidden;
diff --git a/src/skins/vector/css/matrix-react-sdk/views/rooms/RoomList.css b/src/skins/vector/css/matrix-react-sdk/views/rooms/RoomList.css
index 1a4ec869..a2241853 100644
--- a/src/skins/vector/css/matrix-react-sdk/views/rooms/RoomList.css
+++ b/src/skins/vector/css/matrix-react-sdk/views/rooms/RoomList.css
@@ -15,7 +15,7 @@ limitations under the License.
*/
.mx_RoomList {
- padding-top: 24px;
+ padding-top: 8px;
padding-bottom: 12px;
min-height: 400px;
}
diff --git a/src/skins/vector/css/matrix-react-sdk/views/rooms/RoomTile.css b/src/skins/vector/css/matrix-react-sdk/views/rooms/RoomTile.css
index d62c5d17..27b2d229 100644
--- a/src/skins/vector/css/matrix-react-sdk/views/rooms/RoomTile.css
+++ b/src/skins/vector/css/matrix-react-sdk/views/rooms/RoomTile.css
@@ -23,10 +23,10 @@ limitations under the License.
.mx_RoomTile_avatar {
display: table-cell;
- padding-right: 8px;
+ padding-right: 11px;
padding-top: 6px;
padding-bottom: 6px;
- padding-left: 18px;
+ padding-left: 20px;
width: 24px;
height: 24px;
position: relative;
@@ -38,8 +38,8 @@ limitations under the License.
width: 100%;
vertical-align: middle;
overflow: hidden;
- text-overflow: ellipsis;
- padding-right: 16px;
+ word-break: break-word;
+ padding-right: 19px;
color: rgba(69, 69, 69, 0.8);
}
@@ -98,11 +98,13 @@ limitations under the License.
.mx_RoomTile_badge {
background-color: #ff0064;
- width: 4px;
+ width: 8px;
+ height: 8px;
position: absolute;
- left: 0px;
- top: 5px;
- bottom: 5px;
+ left: 7px;
+ top: 50%;
+ margin-top: -4px;
+ border-radius: 4px;
}
.mx_RoomTile_unreadNotify .mx_RoomTile_badge {
@@ -119,18 +121,35 @@ limitations under the License.
}
.mx_RoomTile_selected .mx_RoomTile_name {
- color: #76cfa6 ! important;
+ padding-right: 23px;
}
.mx_RoomTile_highlight .mx_RoomTile_name {
color: #ff0064 ! important;
}
+.mx_RoomTile.mx_RoomTile_selected .mx_RoomTile_avatar {
+ padding-right: 7px;
+}
+
+.mx_RoomTile.mx_RoomTile_selected .mx_RoomTile_name span {
+ display: inline-block;
+ position: relative;
+ width: 100%;
+ padding: 4px;
+ margin-top: -4px;
+ margin-bottom: -4px;
+ border-radius: 2px;
+ background-color: rgba(118,207,166,0.2);
+}
+
+/*
.mx_RoomTile.mx_RoomTile_selected .mx_RoomTile_name {
background: url('img/selected.png');
background-repeat: no-repeat;
background-position: right center;
}
+*/
.mx_RoomTile_arrow {
position: absolute;
diff --git a/src/skins/vector/css/matrix-react-sdk/views/rooms/TabCompleteBar.css b/src/skins/vector/css/matrix-react-sdk/views/rooms/TabCompleteBar.css
index e0d0965e..f7f4a0bd 100644
--- a/src/skins/vector/css/matrix-react-sdk/views/rooms/TabCompleteBar.css
+++ b/src/skins/vector/css/matrix-react-sdk/views/rooms/TabCompleteBar.css
@@ -21,6 +21,7 @@ limitations under the License.
.mx_TabCompleteBar_item {
display: inline-block;
margin-right: 15px;
+ margin-bottom: 2px;
cursor: pointer;
}
@@ -37,6 +38,7 @@ limitations under the License.
.mx_TabCompleteBar_command .mx_TabCompleteBar_text {
opacity: 1.0;
+ vertical-align: initial;
color: #fff;
}
@@ -47,5 +49,6 @@ limitations under the License.
.mx_TabCompleteBar_text {
color: #4a4a4a;
+ vertical-align: middle;
opacity: 0.5;
}
diff --git a/src/skins/vector/css/matrix-react-sdk/views/rooms/TopUnreadMessagesBar.css b/src/skins/vector/css/matrix-react-sdk/views/rooms/TopUnreadMessagesBar.css
index ef639e2e..77184d42 100644
--- a/src/skins/vector/css/matrix-react-sdk/views/rooms/TopUnreadMessagesBar.css
+++ b/src/skins/vector/css/matrix-react-sdk/views/rooms/TopUnreadMessagesBar.css
@@ -19,7 +19,7 @@ limitations under the License.
max-width: 960px;
padding-top: 5px;
padding-bottom: 5px;
- border-bottom: 1px solid #eee;
+ border-bottom: 1px solid #e5e5e5;
}
.mx_TopUnreadMessagesBar_scrollUp {
diff --git a/src/skins/vector/css/vector-web/structures/LeftPanel.css b/src/skins/vector/css/vector-web/structures/LeftPanel.css
index 4ee44426..c78dfd33 100644
--- a/src/skins/vector/css/vector-web/structures/LeftPanel.css
+++ b/src/skins/vector/css/vector-web/structures/LeftPanel.css
@@ -58,23 +58,40 @@ limitations under the License.
-webkit-order: 3;
order: 3;
- -webkit-flex: 0 0 140px;
- flex: 0 0 140px;
-
- background-color: rgba(118,207,166,0.2);
+ border-top: 1px solid rgba(0, 0, 0, 0.1);
+ margin-left: 20px;
+ margin-right: 20px;
+ -webkit-flex: 0 0 60px;
+ flex: 0 0 60px;
}
-.mx_LeftPanel .mx_BottomLeftMenu .mx_RoomTile {
- color: #454545;
+.mx_LeftPanel .mx_BottomLeftMenu_options {
+ margin-top: 18px;
}
-.mx_LeftPanel .mx_BottomLeftMenu .mx_BottomLeftMenu_options {
- margin-top: 15px;
- width: 100%;
+.mx_BottomLeftMenu_options object {
+ pointer-events: none;
}
-.mx_LeftPanel .mx_BottomLeftMenu img {
- border-radius: 0px;
- background-color: transparent;
- vertical-align: middle;
-}
\ No newline at end of file
+.mx_LeftPanel .mx_BottomLeftMenu_createRoom,
+.mx_LeftPanel .mx_BottomLeftMenu_directory,
+.mx_LeftPanel .mx_BottomLeftMenu_settings {
+ display: inline-block;
+ cursor: pointer;
+}
+
+.collapsed .mx_BottomLeftMenu_createRoom,
+.collapsed .mx_BottomLeftMenu_directory,
+.collapsed .mx_BottomLeftMenu_settings {
+ margin-left: 0px ! important;
+ padding-top: 3px ! important;
+ padding-bottom: 3px ! important;
+}
+
+.mx_LeftPanel .mx_BottomLeftMenu_directory {
+ margin-left: 10px;
+}
+
+.mx_LeftPanel .mx_BottomLeftMenu_settings {
+ float: right;
+}
diff --git a/src/skins/vector/css/vector-web/structures/RightPanel.css b/src/skins/vector/css/vector-web/structures/RightPanel.css
index 7cad2649..7257d8b4 100644
--- a/src/skins/vector/css/vector-web/structures/RightPanel.css
+++ b/src/skins/vector/css/vector-web/structures/RightPanel.css
@@ -33,14 +33,17 @@ limitations under the License.
-webkit-order: 1;
order: 1;
- -webkit-flex: 0 0 83px;
- flex: 0 0 83px;
+ border-bottom: 1px solid #e5e5e5;
+ margin-right: 20px;
+
+ -webkit-flex: 0 0 70px;
+ flex: 0 0 70px;
}
/** Fixme - factor this out with the main header **/
.mx_RightPanel_headerButtonGroup {
- margin-top: 32px;
+ margin-top: 25px;
float: left;
background-color: #fff;
margin-left: -4px;
@@ -83,10 +86,27 @@ limitations under the License.
}
.mx_RightPanel .mx_MemberList,
-.mx_RightPanel .mx_MemberInfo {
+.mx_RightPanel .mx_MemberInfo,
+.mx_RightPanel_blank {
-webkit-box-ordinal-group: 2;
-moz-box-ordinal-group: 2;
-ms-flex-order: 2;
-webkit-order: 2;
order: 2;
+ flex: 1;
+ -webkit-flex: 1;
+}
+
+.mx_RightPanel_footer {
+ -webkit-box-ordinal-group: 3;
+ -moz-box-ordinal-group: 3;
+ -ms-flex-order: 3;
+ -webkit-order: 3;
+ order: 3;
+
+ border-top: 1px solid #e5e5e5;
+ margin-right: 20px;
+
+ -webkit-flex: 0 0 60px;
+ flex: 0 0 60px;
}
diff --git a/src/skins/vector/css/vector-web/structures/RoomDirectory.css b/src/skins/vector/css/vector-web/structures/RoomDirectory.css
index 2f75724d..c745706b 100644
--- a/src/skins/vector/css/vector-web/structures/RoomDirectory.css
+++ b/src/skins/vector/css/vector-web/structures/RoomDirectory.css
@@ -22,6 +22,8 @@ limitations under the License.
margin-bottom: 12px;
color: #4a4a4a;
+ border-top: 1px solid #c5c5c5;
+
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
diff --git a/src/skins/vector/css/vector-web/structures/RoomSubList.css b/src/skins/vector/css/vector-web/structures/RoomSubList.css
index d385397b..8f9db8fe 100644
--- a/src/skins/vector/css/vector-web/structures/RoomSubList.css
+++ b/src/skins/vector/css/vector-web/structures/RoomSubList.css
@@ -29,13 +29,14 @@ limitations under the License.
padding-right: 12px;
margin-top: 8px;
margin-bottom: 4px;
+ cursor: pointer;
}
.mx_RoomSubList_chevron {
- padding-left: 5px;
+ padding-left: 4px;
pointer-events: none;
}
.collapsed .mx_RoomSubList_chevron {
- padding-left: 13px;
+ padding-left: 12px;
}
diff --git a/src/skins/vector/img/icons-create-room.svg b/src/skins/vector/img/icons-create-room.svg
new file mode 100644
index 00000000..6ed94b4b
--- /dev/null
+++ b/src/skins/vector/img/icons-create-room.svg
@@ -0,0 +1,20 @@
+
+
+
+ icons_create_room
+ Created with sketchtool.
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/skins/vector/img/icons-directory.svg b/src/skins/vector/img/icons-directory.svg
new file mode 100644
index 00000000..00869b9b
--- /dev/null
+++ b/src/skins/vector/img/icons-directory.svg
@@ -0,0 +1,21 @@
+
+
+
+ icons_directory
+ Created with sketchtool.
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/skins/vector/img/icons-settings.svg b/src/skins/vector/img/icons-settings.svg
new file mode 100644
index 00000000..60969530
--- /dev/null
+++ b/src/skins/vector/img/icons-settings.svg
@@ -0,0 +1,17 @@
+
+
+
+ icons_settings
+ Created with sketchtool.
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/skins/vector/img/list-close.svg b/src/skins/vector/img/list-close.svg
index eb60864e..cd88b2a8 100644
--- a/src/skins/vector/img/list-close.svg
+++ b/src/skins/vector/img/list-close.svg
@@ -1,10 +1,15 @@
-
-
-
- Slice 1
- Created with Sketch.
-
-
-
-
-
\ No newline at end of file
+
+
+
+
+Slice 1
+Created with Sketch.
+
+
+
+
diff --git a/src/skins/vector/img/list-open.svg b/src/skins/vector/img/list-open.svg
index a682ec90..e180be88 100644
--- a/src/skins/vector/img/list-open.svg
+++ b/src/skins/vector/img/list-open.svg
@@ -1,10 +1,15 @@
-
-
-
- Slice 1
- Created with Sketch.
-
-
-
-
-
\ No newline at end of file
+
+
+
+
+Slice 1
+Created with Sketch.
+
+
+
+
diff --git a/src/skins/vector/img/maximise.svg b/src/skins/vector/img/maximise.svg
new file mode 100644
index 00000000..79c6c0ab
--- /dev/null
+++ b/src/skins/vector/img/maximise.svg
@@ -0,0 +1,19 @@
+
+
+
+minimise
+Created with sketchtool.
+
+
+
+
+
+
+
+
diff --git a/src/skins/vector/img/minimise.svg b/src/skins/vector/img/minimise.svg
new file mode 100644
index 00000000..491756b1
--- /dev/null
+++ b/src/skins/vector/img/minimise.svg
@@ -0,0 +1,18 @@
+
+
+
+ minimise
+ Created with sketchtool.
+
+
+
+
+
+
+
+
+
diff --git a/src/skins/vector/img/right_search.svg b/src/skins/vector/img/right_search.svg
new file mode 100644
index 00000000..b430a6be
--- /dev/null
+++ b/src/skins/vector/img/right_search.svg
@@ -0,0 +1,17 @@
+
+
+
+ right_search
+ Created with Sketch.
+
+
+
+
+
\ No newline at end of file
diff --git a/src/vector/index.js b/src/vector/index.js
index 6dd975d2..e3178c96 100644
--- a/src/vector/index.js
+++ b/src/vector/index.js
@@ -27,8 +27,15 @@ require('gemini-scrollbar/gemini-scrollbar.css');
require('gfm.css/gfm.css');
require('highlight.js/styles/github.css');
+
+ // add React and ReactPerf to the global namespace, to make them easier to
+ // access via the console
+global.React = require("react");
+if (process.env.NODE_ENV !== 'production') {
+ global.ReactPerf = require("react-addons-perf");
+}
+
var RunModernizrTests = require("./modernizr"); // this side-effects a global
-var React = require("react");
var ReactDOM = require("react-dom");
var sdk = require("matrix-react-sdk");
sdk.loadSkin(require('../component-index'));
diff --git a/test/unit-tests/notifications/ContentRules-test.js b/test/unit-tests/notifications/ContentRules-test.js
new file mode 100644
index 00000000..e7928147
--- /dev/null
+++ b/test/unit-tests/notifications/ContentRules-test.js
@@ -0,0 +1,117 @@
+/*
+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.
+*/
+
+var notifications = require('notifications');
+
+var ContentRules = notifications.ContentRules;
+var PushRuleVectorState = notifications.PushRuleVectorState;
+
+var expect = require('expect');
+var test_utils = require('../../test-utils');
+
+var NORMAL_RULE = {
+ actions: [
+ "notify",
+ { set_tweak: "highlight", value: false },
+ ],
+ enabled: true,
+ pattern: "vdh2",
+ rule_id: "vdh2",
+};
+
+var LOUD_RULE = {
+ actions: [
+ "notify",
+ { set_tweak: "highlight" },
+ { set_tweak: "sound", value: "default" },
+ ],
+ enabled: true,
+ pattern: "vdh2",
+ rule_id: "vdh2",
+};
+
+var USERNAME_RULE = {
+ actions: [
+ "notify",
+ { set_tweak: "sound", value: "default" },
+ { set_tweak: "highlight" },
+ ],
+ default: true,
+ enabled: true,
+ pattern: "richvdh",
+ rule_id: ".m.rule.contains_user_name",
+};
+
+
+
+describe("ContentRules", function() {
+ beforeEach(function() {
+ test_utils.beforeEach(this);
+ });
+
+ describe("parseContentRules", function() {
+ it("should handle there being no keyword rules", function() {
+ var rules = { 'global': { 'content': [
+ USERNAME_RULE,
+ ]}};
+ var parsed = ContentRules.parseContentRules(rules);
+ expect(parsed.rules).toEqual([]);
+ expect(parsed.vectorState).toEqual(PushRuleVectorState.ON);
+ expect(parsed.externalRules).toEqual([]);
+ });
+
+ it("should parse regular keyword notifications", function() {
+ var rules = { 'global': { 'content': [
+ NORMAL_RULE,
+ USERNAME_RULE,
+ ]}};
+
+ var parsed = ContentRules.parseContentRules(rules);
+ expect(parsed.rules.length).toEqual(1);
+ expect(parsed.rules[0]).toEqual(NORMAL_RULE);
+ expect(parsed.vectorState).toEqual(PushRuleVectorState.ON);
+ expect(parsed.externalRules).toEqual([]);
+ });
+
+ it("should parse loud keyword notifications", function() {
+ var rules = { 'global': { 'content': [
+ LOUD_RULE,
+ USERNAME_RULE,
+ ]}};
+
+ var parsed = ContentRules.parseContentRules(rules);
+ expect(parsed.rules.length).toEqual(1);
+ expect(parsed.rules[0]).toEqual(LOUD_RULE);
+ expect(parsed.vectorState).toEqual(PushRuleVectorState.LOUD);
+ expect(parsed.externalRules).toEqual([]);
+ });
+
+ it("should parse mixed keyword notifications", function() {
+ var rules = { 'global': { 'content': [
+ LOUD_RULE,
+ NORMAL_RULE,
+ USERNAME_RULE,
+ ]}};
+
+ var parsed = ContentRules.parseContentRules(rules);
+ expect(parsed.rules.length).toEqual(1);
+ expect(parsed.rules[0]).toEqual(LOUD_RULE);
+ expect(parsed.vectorState).toEqual(PushRuleVectorState.LOUD);
+ expect(parsed.externalRules.length).toEqual(1);
+ expect(parsed.externalRules[0]).toEqual(NORMAL_RULE);
+ });
+ });
+});
diff --git a/test/unit-tests/notifications/PushRuleVectorState-test.js b/test/unit-tests/notifications/PushRuleVectorState-test.js
index 48084f08..6b0f81c6 100644
--- a/test/unit-tests/notifications/PushRuleVectorState-test.js
+++ b/test/unit-tests/notifications/PushRuleVectorState-test.js
@@ -23,8 +23,40 @@ var expect = require('expect');
describe("PushRuleVectorState", function() {
describe("contentRuleVectorStateKind", function() {
it("should understand normal notifications", function () {
- expect(prvs.contentRuleVectorStateKind(["notify"])).
+ var rule = {
+ actions: [
+ "notify",
+ ],
+ };
+
+ expect(prvs.contentRuleVectorStateKind(rule)).
toEqual(prvs.ON);
});
+
+ it("should handle loud notifications", function () {
+ var rule = {
+ actions: [
+ "notify",
+ { set_tweak: "highlight", value: true },
+ { set_tweak: "sound", value: "default" },
+ ]
+ };
+
+ expect(prvs.contentRuleVectorStateKind(rule)).
+ toEqual(prvs.LOUD);
+ });
+
+ it("should understand missing highlight.value", function () {
+ var rule = {
+ actions: [
+ "notify",
+ { set_tweak: "highlight" },
+ { set_tweak: "sound", value: "default" },
+ ]
+ };
+
+ expect(prvs.contentRuleVectorStateKind(rule)).
+ toEqual(prvs.LOUD);
+ });
});
});
diff --git a/webpack.config.js b/webpack.config.js
index dfe127d6..297881f3 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -41,6 +41,7 @@ module.exports = {
// alias any requires to the react module to the one in our path, otherwise
// we tend to get the react source included twice when using npm link.
react: path.resolve('./node_modules/react'),
+ "react-addons-perf": path.resolve('./node_modules/react-addons-perf'),
// same goes for js-sdk
"matrix-js-sdk": path.resolve('./node_modules/matrix-js-sdk'),