From 9541fc1033490c13a52fabc4eebd50474d0b855d Mon Sep 17 00:00:00 2001
From: Sylvia van Os <sylvia@hackerchick.me>
Date: Sun, 25 Jun 2017 22:13:00 +0200
Subject: [PATCH 01/20] Mirror local video feed

---
 .../vector/css/matrix-react-sdk/views/voip/_VideoView.scss     | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/skins/vector/css/matrix-react-sdk/views/voip/_VideoView.scss b/src/skins/vector/css/matrix-react-sdk/views/voip/_VideoView.scss
index 8f23ef6b..eb435383 100644
--- a/src/skins/vector/css/matrix-react-sdk/views/voip/_VideoView.scss
+++ b/src/skins/vector/css/matrix-react-sdk/views/voip/_VideoView.scss
@@ -42,4 +42,5 @@ limitations under the License.
 .mx_VideoView_localVideoFeed video {
     width: auto;
     height: 100%;
-}
\ No newline at end of file
+    transform: scale(-1, 1);
+}

From 84d1f838f01480987981786c6cfca4a568058489 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Mon, 23 Oct 2017 11:39:21 +0100
Subject: [PATCH 02/20] make filtering case-insensitive

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
 src/components/views/dialogs/DevtoolsDialog.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/components/views/dialogs/DevtoolsDialog.js b/src/components/views/dialogs/DevtoolsDialog.js
index 9c5948b4..9300a185 100644
--- a/src/components/views/dialogs/DevtoolsDialog.js
+++ b/src/components/views/dialogs/DevtoolsDialog.js
@@ -237,7 +237,7 @@ class RoomStateExplorer extends React.Component {
         if (this.state.eventType === null) {
             Object.keys(this.roomStateEvents).forEach((evType) => {
                 // Skip this entry if does not contain search query
-                if (this.state.query && !evType.includes(this.state.query)) return;
+                if (this.state.query && !evType.toLowerCase().includes(this.state.query.toLocaleLowerCase())) return;
 
                 const stateGroup = this.roomStateEvents[evType];
                 const stateKeys = Object.keys(stateGroup);
@@ -258,7 +258,7 @@ class RoomStateExplorer extends React.Component {
             const stateGroup = this.roomStateEvents[evType];
             Object.keys(stateGroup).forEach((stateKey) => {
                 // Skip this entry if does not contain search query
-                if (this.state.query && !stateKey.includes(this.state.query)) return;
+                if (this.state.query && !stateKey.toLowerCase().includes(this.state.query.toLocaleLowerCase())) return;
 
                 const ev = stateGroup[stateKey];
                 rows.push(<button className="mx_DevTools_RoomStateExplorer_button" key={stateKey}

From eb45b00b32b1dbc7d8a2612ab0265c9342a24d7a Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Mon, 23 Oct 2017 11:40:25 +0100
Subject: [PATCH 03/20] fix bad tab completion

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
 src/components/views/dialogs/DevtoolsDialog.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/components/views/dialogs/DevtoolsDialog.js b/src/components/views/dialogs/DevtoolsDialog.js
index 9300a185..7c61e462 100644
--- a/src/components/views/dialogs/DevtoolsDialog.js
+++ b/src/components/views/dialogs/DevtoolsDialog.js
@@ -237,7 +237,7 @@ class RoomStateExplorer extends React.Component {
         if (this.state.eventType === null) {
             Object.keys(this.roomStateEvents).forEach((evType) => {
                 // Skip this entry if does not contain search query
-                if (this.state.query && !evType.toLowerCase().includes(this.state.query.toLocaleLowerCase())) return;
+                if (this.state.query && !evType.toLowerCase().includes(this.state.query.toLowerCase())) return;
 
                 const stateGroup = this.roomStateEvents[evType];
                 const stateKeys = Object.keys(stateGroup);
@@ -258,7 +258,7 @@ class RoomStateExplorer extends React.Component {
             const stateGroup = this.roomStateEvents[evType];
             Object.keys(stateGroup).forEach((stateKey) => {
                 // Skip this entry if does not contain search query
-                if (this.state.query && !stateKey.toLowerCase().includes(this.state.query.toLocaleLowerCase())) return;
+                if (this.state.query && !stateKey.toLowerCase().includes(this.state.query.toLowerCase())) return;
 
                 const ev = stateGroup[stateKey];
                 rows.push(<button className="mx_DevTools_RoomStateExplorer_button" key={stateKey}

From b16d4280b7b3103cd8bc2934223bbebb992c7198 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Mon, 23 Oct 2017 11:42:06 +0100
Subject: [PATCH 04/20] delint

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
 src/components/views/dialogs/DevtoolsDialog.js | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/src/components/views/dialogs/DevtoolsDialog.js b/src/components/views/dialogs/DevtoolsDialog.js
index 7c61e462..7760ea84 100644
--- a/src/components/views/dialogs/DevtoolsDialog.js
+++ b/src/components/views/dialogs/DevtoolsDialog.js
@@ -57,7 +57,7 @@ class SendCustomEvent extends React.Component {
     _buttons() {
         return <div className="mx_Dialog_buttons">
             <button onClick={this.onBack}>{ _t('Back') }</button>
-            {!this.state.message && <button onClick={this._send}>{ _t('Send') }</button>}
+            { !this.state.message && <button onClick={this._send}>{ _t('Send') }</button> }
         </div>;
     }
 
@@ -83,7 +83,7 @@ class SendCustomEvent extends React.Component {
     }
 
     _additionalFields() {
-        return <div/>;
+        return <div />;
     }
 
     _onChange(e) {
@@ -94,15 +94,15 @@ class SendCustomEvent extends React.Component {
         if (this.state.message) {
             return <div>
                 <div className="mx_Dialog_content">
-                    {this.state.message}
+                    { this.state.message }
                 </div>
-                {this._buttons()}
+                { this._buttons() }
             </div>;
         }
 
         return <div>
             <div className="mx_Dialog_content">
-                {this._additionalFields()}
+                { this._additionalFields() }
                 <div className="mx_TextInputDialog_label">
                     <label htmlFor="eventType"> { _t('Event Type') } </label>
                 </div>
@@ -117,7 +117,7 @@ class SendCustomEvent extends React.Component {
                     <textarea id="evContent" onChange={this._onChange} value={this.state.input_evContent} className="mx_TextInputDialog_input" cols="63" rows="5" />
                 </div>
             </div>
-            {this._buttons()}
+            { this._buttons() }
         </div>;
     }
 }
@@ -223,7 +223,7 @@ class RoomStateExplorer extends React.Component {
         if (this.state.event) {
             return <div className="mx_ViewSource">
                 <div className="mx_Dialog_content">
-                    <pre>{JSON.stringify(this.state.event.event, null, 2)}</pre>
+                    <pre>{ JSON.stringify(this.state.event.event, null, 2) }</pre>
                 </div>
                 <div className="mx_Dialog_buttons">
                     <button onClick={this.onBack}>{ _t('Back') }</button>
@@ -271,7 +271,7 @@ class RoomStateExplorer extends React.Component {
         return <div>
             <div className="mx_Dialog_content">
                 <input onChange={this.onQuery} placeholder={_t('Filter results')} size="64" className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query" value={this.state.query} />
-                {rows}
+                { rows }
             </div>
             <div className="mx_Dialog_buttons">
                 <button onClick={this.onBack}>{ _t('Back') }</button>

From c8f89fbc9b80f5c0fa8b3532bbce3d0a5f484cd3 Mon Sep 17 00:00:00 2001
From: Richard Lewis <richard@smetco.co.uk>
Date: Mon, 23 Oct 2017 17:07:25 +0100
Subject: [PATCH 05/20] Added delete widget icon.

---
 src/skins/vector/img/icon-delete-pink.svg | 10 ++++++++++
 1 file changed, 10 insertions(+)
 create mode 100644 src/skins/vector/img/icon-delete-pink.svg

diff --git a/src/skins/vector/img/icon-delete-pink.svg b/src/skins/vector/img/icon-delete-pink.svg
new file mode 100644
index 00000000..9d9907d8
--- /dev/null
+++ b/src/skins/vector/img/icon-delete-pink.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
+<g>
+	<path fill="#FF0064" d="M85.633,454.889c0,31.168,25.553,56.661,56.79,56.661h227.156c31.234,0,56.787-25.493,56.787-56.661
+		V128.225H85.633V454.889z M468.958,43.042H362.479L326.828,0.45H185.173l-35.652,42.591H43.042v42.592h425.916V43.042z"/>
+</g>
+</svg>

From cc1fb8ac9470b5cfd24b3e01dfb68817f4f6cc83 Mon Sep 17 00:00:00 2001
From: Luke Barnard <lukeb@openmarket.com>
Date: Tue, 24 Oct 2017 10:22:13 +0100
Subject: [PATCH 06/20] Only show "Invite to this community" when viewing group
 members

---
 src/components/structures/RightPanel.js | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js
index 30142ad2..e7e8c3cf 100644
--- a/src/components/structures/RightPanel.js
+++ b/src/components/structures/RightPanel.js
@@ -318,7 +318,10 @@ module.exports = React.createClass({
         }
 
         if (this.props.groupId) {
-            inviteGroup = this.state.phase === this.Phase.GroupMemberList ? (
+            inviteGroup =  [
+                this.Phase.GroupMemberInfo,
+                this.Phase.GroupMemberList,
+            ].includes(this.state.phase) ? (
                 <AccessibleButton className="mx_RightPanel_invite" onClick={ this.onInviteButtonClick } >
                     <div className="mx_RightPanel_icon" >
                         <TintableSvg src="img/icon-invite-people.svg" width="35" height="35" />

From f1a700ef99594d16819d686717b54102b97afc5f Mon Sep 17 00:00:00 2001
From: Luke Barnard <lukeb@openmarket.com>
Date: Tue, 24 Oct 2017 10:38:40 +0100
Subject: [PATCH 07/20] Only show UI for adding rooms/users to groups to
 privileged users

---
 src/components/structures/RightPanel.js | 23 +++++++++++++++--------
 1 file changed, 15 insertions(+), 8 deletions(-)

diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js
index 30142ad2..b2511c70 100644
--- a/src/components/structures/RightPanel.js
+++ b/src/components/structures/RightPanel.js
@@ -21,11 +21,12 @@ import PropTypes from 'prop-types';
 import { _t } from 'matrix-react-sdk/lib/languageHandler';
 import sdk from 'matrix-react-sdk';
 import dis from 'matrix-react-sdk/lib/dispatcher';
-import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';
+import MatrixClient from 'matrix-js-sdk';
 import Analytics from 'matrix-react-sdk/lib/Analytics';
 import rate_limited_func from 'matrix-react-sdk/lib/ratelimitedfunc';
 import AccessibleButton from 'matrix-react-sdk/lib/components/views/elements/AccessibleButton';
 import { showGroupInviteDialog, showGroupAddRoomDialog } from 'matrix-react-sdk/lib/GroupAddressPicker';
+import GroupStoreCache from 'matrix-react-sdk/lib/stores/GroupStoreCache';
 
 class HeaderButton extends React.Component {
     constructor() {
@@ -80,6 +81,10 @@ module.exports = React.createClass({
         collapsed: React.PropTypes.bool, // currently unused property to request for a minimized view of the panel
     },
 
+    contextTypes: {
+        matrixClient: PropTypes.instanceOf(MatrixClient),
+    },
+
     Phase: {
         RoomMemberList: 'RoomMemberList',
         GroupMemberList: 'GroupMemberList',
@@ -92,14 +97,14 @@ module.exports = React.createClass({
 
     componentWillMount: function() {
         this.dispatcherRef = dis.register(this.onAction);
-        const cli = MatrixClientPeg.get();
+        const cli = this.context.matrixClient;
         cli.on("RoomState.members", this.onRoomStateMember);
     },
 
     componentWillUnmount: function() {
         dis.unregister(this.dispatcherRef);
-        if (MatrixClientPeg.get()) {
-            MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
+        if (this.context.matrixClient) {
+            this.context.matrixClient.removeListener("RoomState.members", this.onRoomStateMember);
         }
     },
 
@@ -122,7 +127,7 @@ module.exports = React.createClass({
     },
 
     onInviteButtonClick: function() {
-        if (MatrixClientPeg.get().isGuest()) {
+        if (this.context.matrixClient.isGuest()) {
             dis.dispatch({action: 'view_set_mxid'});
             return;
         }
@@ -222,13 +227,13 @@ module.exports = React.createClass({
         if ((this.state.phase == this.Phase.RoomMemberList || this.state.phase === this.Phase.RoomMemberInfo)
             && this.props.roomId
         ) {
-            const cli = MatrixClientPeg.get();
+            const cli = this.context.matrixClient;
             const room = cli.getRoom(this.props.roomId);
             let userIsInRoom;
             if (room) {
                 membersBadge = room.getJoinedMembers().length;
                 userIsInRoom = room.hasMembershipState(
-                    MatrixClientPeg.get().credentials.userId, 'join',
+                    this.context.matrixClient.credentials.userId, 'join',
                 );
             }
 
@@ -317,7 +322,9 @@ module.exports = React.createClass({
             panel = <div className="mx_RightPanel_blank"></div>;
         }
 
-        if (this.props.groupId) {
+        if (this.props.groupId &&
+            GroupStoreCache.getGroupStore(this.context.matrixClient, this.props.groupId).isUserPrivileged()
+        ) {
             inviteGroup = this.state.phase === this.Phase.GroupMemberList ? (
                 <AccessibleButton className="mx_RightPanel_invite" onClick={ this.onInviteButtonClick } >
                     <div className="mx_RightPanel_icon" >

From b5629ac3e6b3459236874e0b5fa8fe657ecf87e1 Mon Sep 17 00:00:00 2001
From: lub <github@lubiland.de>
Date: Tue, 24 Oct 2017 11:43:15 +0200
Subject: [PATCH 08/20] fix comma error of features example
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Simon Körner <github@lubiland.de>
---
 config.sample.json | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/config.sample.json b/config.sample.json
index ca5c020a..9cb2b5e7 100644
--- a/config.sample.json
+++ b/config.sample.json
@@ -6,8 +6,8 @@
     "integrations_rest_url": "https://scalar.vector.im/api",
     "bug_report_endpoint_url": "https://riot.im/bugreports/submit",
     "features": {
-        "feature_groups": "labs"
-        "feature_pinning": "labs",
+        "feature_groups": "labs",
+        "feature_pinning": "labs"
     },
     "default_federate": true,
     "roomDirectory": {

From 01d6577abd883fe761fccf58eaf23e3bff2417c0 Mon Sep 17 00:00:00 2001
From: Luke Barnard <lukeb@openmarket.com>
Date: Tue, 24 Oct 2017 15:27:11 +0100
Subject: [PATCH 09/20] CSS for preventing editing of UI requiring user
 privilege if user unprivileged

---
 .../structures/_GroupView.scss                | 33 +++++++++++--------
 1 file changed, 19 insertions(+), 14 deletions(-)

diff --git a/src/skins/vector/css/matrix-react-sdk/structures/_GroupView.scss b/src/skins/vector/css/matrix-react-sdk/structures/_GroupView.scss
index 35cb5acc..02d04f8e 100644
--- a/src/skins/vector/css/matrix-react-sdk/structures/_GroupView.scss
+++ b/src/skins/vector/css/matrix-react-sdk/structures/_GroupView.scss
@@ -159,8 +159,12 @@ limitations under the License.
     user-select: none;
 }
 
-.mx_GroupView_rooms_header h3 {
-    margin-bottom: 10px
+.mx_GroupView h3 {
+    text-transform: uppercase;
+    color: $h3-color;
+    font-weight: 600;
+    font-size: 13px;
+    margin-bottom: 10px;
 }
 
 .mx_GroupView_rooms_header .mx_AccessibleButton {
@@ -169,15 +173,24 @@ limitations under the License.
     height: 24px;
 }
 
-.mx_GroupView_rooms_header_addButton {
-    display: inline-block;
+.mx_GroupView_group {
+    border-top: 1px solid $primary-hairline-color;
 }
 
-.mx_GroupView_rooms_header_addButton object {
+.mx_GroupView_group_disabled {
+    opacity: 0.3;
     pointer-events: none;
 }
 
-.mx_GroupView_rooms_header_addButton_label {
+.mx_GroupView_rooms_header_addRow_button {
+    display: inline-block;
+}
+
+.mx_GroupView_rooms_header_addRow_button object {
+    pointer-events: none;
+}
+
+.mx_GroupView_rooms_header_addRow_label {
     display: inline-block;
     vertical-align: top;
     line-height: 24px;
@@ -222,14 +235,6 @@ limitations under the License.
     user-select: none;
 }
 
-.mx_GroupView_memberSettings h3 {
-    text-transform: uppercase;
-    color: $h3-color;
-    font-weight: 600;
-    font-size: 13px;
-    margin-bottom: 10px;
-}
-
 .mx_GroupView_memberSettings input {
     margin-right: 6px;
 }

From 858e1fa3419269c1e66efe9de2c363f2f5c80810 Mon Sep 17 00:00:00 2001
From: Luke Barnard <lukeb@openmarket.com>
Date: Tue, 24 Oct 2017 17:00:57 +0100
Subject: [PATCH 10/20] Fix #5361

Make it possible to click on the camera icon under group icon
---
 .../vector/css/matrix-react-sdk/structures/_GroupView.scss      | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/skins/vector/css/matrix-react-sdk/structures/_GroupView.scss b/src/skins/vector/css/matrix-react-sdk/structures/_GroupView.scss
index 02d04f8e..1a92fc10 100644
--- a/src/skins/vector/css/matrix-react-sdk/structures/_GroupView.scss
+++ b/src/skins/vector/css/matrix-react-sdk/structures/_GroupView.scss
@@ -33,10 +33,12 @@ limitations under the License.
     min-height: 70px;
     align-items: center;
     display: flex;
+    padding-bottom: 10px;
 }
 
 .mx_GroupView_header_view {
     border-bottom: 1px solid $primary-hairline-color;
+    padding-bottom: 0px;
 }
 
 .mx_GroupView_header_avatar, .mx_GroupView_header_info {

From bd15a88c89d2a11eecb638984b1cac51cf93a84e Mon Sep 17 00:00:00 2001
From: Luke Barnard <lukeb@openmarket.com>
Date: Tue, 24 Oct 2017 17:10:29 +0100
Subject: [PATCH 11/20] Use CSS to stop greyed Right/LeftPanel UI from being
 interactable

---
 src/components/structures/LeftPanel.js  | 11 +++++++++--
 src/components/structures/RightPanel.js |  7 ++++++-
 2 files changed, 15 insertions(+), 3 deletions(-)

diff --git a/src/components/structures/LeftPanel.js b/src/components/structures/LeftPanel.js
index 1988dc5e..b48d03be 100644
--- a/src/components/structures/LeftPanel.js
+++ b/src/components/structures/LeftPanel.js
@@ -182,8 +182,15 @@ var LeftPanel = React.createClass({
         }
 
         return (
-            <aside className={classes} style={{ opacity: this.props.opacity }}
-                   onKeyDown={ this._onKeyDown } onFocus={ this._onFocus } onBlur={ this._onBlur }>
+            <aside className={classes}
+                style={{
+                    opacity: this.props.opacity,
+                    pointerEvents: this.props.opacity < 1.0 ? 'none' : undefined,
+                }}
+                onKeyDown={ this._onKeyDown }
+                onFocus={ this._onFocus }
+                onBlur={ this._onBlur }
+            >
                 { topBox }
                 <CallPreview ConferenceHandler={VectorConferenceHandler} />
                 <RoomList
diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js
index 0225f765..c32c0646 100644
--- a/src/components/structures/RightPanel.js
+++ b/src/components/structures/RightPanel.js
@@ -351,7 +351,12 @@ module.exports = React.createClass({
         }
 
         return (
-            <aside className={classes} style={{ opacity: this.props.opacity }}>
+            <aside className={classes}
+                style={{
+                    opacity: this.props.opacity,
+                    pointerEvents: this.props.opacity < 1.0 ? 'none' : undefined,
+                }}
+            >
                 <div className="mx_RightPanel_header">
                     <div className="mx_RightPanel_headerButtonGroup">
                         {headerButtons}

From a747a78e06b4f71569421a926b3521364c63aa59 Mon Sep 17 00:00:00 2001
From: Luke Barnard <lukeb@openmarket.com>
Date: Tue, 24 Oct 2017 17:48:06 +0100
Subject: [PATCH 12/20] CSS to allow multi-line "Add rooms to this community"

---
 .../css/vector-web/structures/_RightPanel.scss       | 12 ++++--------
 1 file changed, 4 insertions(+), 8 deletions(-)

diff --git a/src/skins/vector/css/vector-web/structures/_RightPanel.scss b/src/skins/vector/css/vector-web/structures/_RightPanel.scss
index 91caed7b..85057410 100644
--- a/src/skins/vector/css/vector-web/structures/_RightPanel.scss
+++ b/src/skins/vector/css/vector-web/structures/_RightPanel.scss
@@ -94,30 +94,26 @@ limitations under the License.
 }
 
 .mx_RightPanel_footer .mx_RightPanel_invite {
-    line-height: 35px;
     font-size: 14px;
 	color: $primary-fg-color;
     padding-top: 13px;
     padding-left: 5px;
     cursor: pointer;
+    display: flex;
+    align-items: center;
 }
 
 .collapsed .mx_RightPanel_footer .mx_RightPanel_invite {
     display: none;
 }
 
-.mx_RightPanel_invite .mx_RightPanel_icon {
-    display: inline-block;
-}
-
 .mx_RightPanel_invite .mx_RightPanel_icon object {
     pointer-events: none;
 }
 
 .mx_RightPanel_invite .mx_RightPanel_message {
-    display: inline-block;
-    vertical-align: top;
-    padding-left: 10px
+    padding-left: 10px;
+    line-height: 18px;
 }
 
 .mx_MatrixChat_useCompactLayout {

From 0561c5bd4f785d1f2f1d7ccc3399823e9205c0ad Mon Sep 17 00:00:00 2001
From: Luke Barnard <lukeb@openmarket.com>
Date: Wed, 25 Oct 2017 10:29:57 +0100
Subject: [PATCH 13/20] Swap ui_opacity for panel_disabled

Simplify the API for disabling panels in the UI. `mx_fadable_faded` is applied instead of setting opacity.
---
 src/components/structures/LeftPanel.js     | 24 +++++++++-------------
 src/components/structures/RightPanel.js    | 19 ++++++++---------
 src/components/structures/RoomDirectory.js | 12 +++++------
 src/skins/vector/css/_common.scss          |  5 +++++
 4 files changed, 30 insertions(+), 30 deletions(-)

diff --git a/src/components/structures/LeftPanel.js b/src/components/structures/LeftPanel.js
index b48d03be..60f07a62 100644
--- a/src/components/structures/LeftPanel.js
+++ b/src/components/structures/LeftPanel.js
@@ -19,6 +19,7 @@ limitations under the License.
 import React from 'react';
 import { DragDropContext } from 'react-dnd';
 import HTML5Backend from 'react-dnd-html5-backend';
+import classNames from 'classnames';
 import KeyCode from 'matrix-react-sdk/lib/KeyCode';
 import sdk from 'matrix-react-sdk';
 import dis from 'matrix-react-sdk/lib/dispatcher';
@@ -55,7 +56,7 @@ var LeftPanel = React.createClass({
         // We just need to update if any of these things change.
         if (
             this.props.collapsed !== nextProps.collapsed ||
-            this.props.opacity !== nextProps.opacity
+            this.props.disabled !== nextProps.disabled
         ) {
             return true;
         }
@@ -176,21 +177,16 @@ var LeftPanel = React.createClass({
             topBox = <SearchBox collapsed={ this.props.collapsed } onSearch={ this.onSearch } />;
         }
 
-        let classes = "mx_LeftPanel mx_fadable";
-        if (this.props.collapsed) {
-            classes += " collapsed";
-        }
+        let classes = classNames(
+            "mx_LeftPanel", "mx_fadable",
+            {
+                "collapsed": this.props.collapsed,
+                "mx_fadable_faded": this.props.disabled,
+            }
+        );
 
         return (
-            <aside className={classes}
-                style={{
-                    opacity: this.props.opacity,
-                    pointerEvents: this.props.opacity < 1.0 ? 'none' : undefined,
-                }}
-                onKeyDown={ this._onKeyDown }
-                onFocus={ this._onFocus }
-                onBlur={ this._onBlur }
-            >
+            <aside className={classes} onKeyDown={ this._onKeyDown } onFocus={ this._onFocus } onBlur={ this._onBlur }>
                 { topBox }
                 <CallPreview ConferenceHandler={VectorConferenceHandler} />
                 <RoomList
diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js
index c32c0646..6b025ac9 100644
--- a/src/components/structures/RightPanel.js
+++ b/src/components/structures/RightPanel.js
@@ -18,6 +18,7 @@ limitations under the License.
 
 import React from 'react';
 import PropTypes from 'prop-types';
+import classNames from 'classnames';
 import { _t } from 'matrix-react-sdk/lib/languageHandler';
 import sdk from 'matrix-react-sdk';
 import dis from 'matrix-react-sdk/lib/dispatcher';
@@ -345,18 +346,16 @@ module.exports = React.createClass({
             );
         }
 
-        let classes = "mx_RightPanel mx_fadable";
-        if (this.props.collapsed) {
-            classes += " collapsed";
-        }
+        let classes = classNames(
+            "mx_RightPanel", "mx_fadable",
+            {
+                "collapsed": this.props.collapsed,
+                "mx_fadable_faded": this.props.disabled,
+            }
+        );
 
         return (
-            <aside className={classes}
-                style={{
-                    opacity: this.props.opacity,
-                    pointerEvents: this.props.opacity < 1.0 ? 'none' : undefined,
-                }}
-            >
+            <aside className={classes}>
                 <div className="mx_RightPanel_header">
                     <div className="mx_RightPanel_headerButtonGroup">
                         {headerButtons}
diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js
index cd9ac565..323af86c 100644
--- a/src/components/structures/RoomDirectory.js
+++ b/src/components/structures/RoomDirectory.js
@@ -89,17 +89,17 @@ module.exports = React.createClass({
         });
 
         // dis.dispatch({
-        //     action: 'ui_opacity',
-        //     sideOpacity: 0.3,
-        //     middleOpacity: 0.3,
+        //     action: 'panel_disable',
+        //     sideDisabled: true,
+        //     middleDisabled: true,
         // });
     },
 
     componentWillUnmount: function() {
         // dis.dispatch({
-        //     action: 'ui_opacity',
-        //     sideOpacity: 1.0,
-        //     middleOpacity: 1.0,
+        //     action: 'panel_disable',
+        //     sideDisabled: false,
+        //     middleDisabled: false,
         // });
         if (this.filterTimeout) {
             clearTimeout(this.filterTimeout);
diff --git a/src/skins/vector/css/_common.scss b/src/skins/vector/css/_common.scss
index bf742cd0..a9b80928 100644
--- a/src/skins/vector/css/_common.scss
+++ b/src/skins/vector/css/_common.scss
@@ -87,6 +87,11 @@ textarea {
     transition: opacity 0.2s ease-in-out;
 }
 
+.mx_fadable.mx_fadable_faded {
+    opacity: 0.3;
+    pointer-events: none;
+}
+
 /* XXX: critical hack to GeminiScrollbar to allow them to work in FF 42 and Chrome 48.
    Stop the scrollbar view from pushing out the container's overall sizing, which causes
    flexbox to adapt to the new size and cause the view to keep growing.

From 967905126956c896223d4a9fe772a9edd7f53693 Mon Sep 17 00:00:00 2001
From: Luke Barnard <lukeb@openmarket.com>
Date: Wed, 25 Oct 2017 15:16:28 +0100
Subject: [PATCH 14/20] Introduce mx_RoomView_body for fadable things in the
 RoomView

Part of simplifying use of mx_fadable
---
 .../structures/_RoomView.scss                 | 41 +++++++++----------
 1 file changed, 20 insertions(+), 21 deletions(-)

diff --git a/src/skins/vector/css/matrix-react-sdk/structures/_RoomView.scss b/src/skins/vector/css/matrix-react-sdk/structures/_RoomView.scss
index faed5b8b..dfd91885 100644
--- a/src/skins/vector/css/matrix-react-sdk/structures/_RoomView.scss
+++ b/src/skins/vector/css/matrix-react-sdk/structures/_RoomView.scss
@@ -80,17 +80,32 @@ limitations under the License.
     max-width: 1920px ! important;
 }
 
-.mx_RoomView_topUnreadMessagesBar {
+
+.mx_RoomView_body {
+    order: 3;
+    flex: 1 1 0;
+    flex-direction: column;
+    display: flex;
+}
+
+.mx_RoomView_body .mx_RoomView_topUnreadMessagesBar {
+    order: 1;
+}
+
+.mx_RoomView_body .mx_RoomView_messagePanel {
+    order: 2;
+}
+
+.mx_RoomView_body .mx_RoomView_statusArea {
     order: 3;
 }
 
-.mx_RoomView_messagePanel {
+.mx_RoomView_body .mx_MessageComposer {
     order: 4;
+}
 
-    flex: 1 1 0;
-
+.mx_RoomView_messagePanel {
     width: 100%;
-
     overflow-y: auto;
 }
 
@@ -131,18 +146,6 @@ limitations under the License.
     clear: both;
 }
 
-.mx_RoomView_invitePrompt {
-    order: 2;
-
-    min-width: 0px;
-    max-width: 960px;
-    width: 100%;
-    margin: auto;
-
-    margin-top: 12px;
-    margin-bottom: 12px;
-}
-
 li.mx_RoomView_myReadMarker_container {
     height: 0px;
     margin: 0px;
@@ -160,8 +163,6 @@ hr.mx_RoomView_myReadMarker {
 }
 
 .mx_RoomView_statusArea {
-    order: 5;
-
     width: 100%;
     flex: 0 0 auto;
 
@@ -236,8 +237,6 @@ hr.mx_RoomView_myReadMarker {
 }
 
 .mx_RoomView .mx_MessageComposer {
-    order: 6;
-
     width: 100%;
     flex: 0 0 auto;
     margin-right: 2px;

From cd0888ce5a983429c867f5266daf2606a15d529c Mon Sep 17 00:00:00 2001
From: Luke Barnard <lukeb@openmarket.com>
Date: Wed, 25 Oct 2017 15:27:23 +0100
Subject: [PATCH 15/20] Highlight group members icon in group member info

---
 src/components/structures/RightPanel.js | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js
index 0225f765..5ee27fb2 100644
--- a/src/components/structures/RightPanel.js
+++ b/src/components/structures/RightPanel.js
@@ -248,6 +248,11 @@ module.exports = React.createClass({
             }
         }
 
+        const isPhaseGroup = [
+            this.Phase.GroupMemberInfo,
+            this.Phase.GroupMemberList
+        ].includes(this.state.phase);
+
         let headerButtons = [];
         if (this.props.roomId) {
             headerButtons = [
@@ -271,7 +276,7 @@ module.exports = React.createClass({
         } else if (this.props.groupId) {
             headerButtons = [
                 <HeaderButton key="_groupMembersButton" title={_t('Members')} iconSrc="img/icons-people.svg"
-                    isHighlighted={this.state.phase === this.Phase.GroupMemberList}
+                    isHighlighted={isPhaseGroup}
                     clickPhase={this.Phase.GroupMemberList}
                     analytics={['Right Panel', 'Group Member List Button', 'click']}
                 />,
@@ -325,10 +330,7 @@ module.exports = React.createClass({
         if (this.props.groupId &&
             GroupStoreCache.getGroupStore(this.context.matrixClient, this.props.groupId).isUserPrivileged()
         ) {
-            inviteGroup =  [
-                this.Phase.GroupMemberInfo,
-                this.Phase.GroupMemberList,
-            ].includes(this.state.phase) ? (
+            inviteGroup = isPhaseGroup ? (
                 <AccessibleButton className="mx_RightPanel_invite" onClick={ this.onInviteButtonClick } >
                     <div className="mx_RightPanel_icon" >
                         <TintableSvg src="img/icon-invite-people.svg" width="35" height="35" />

From 044519f9ea2a021a2fc4dbb5b97879f575d6bc4c Mon Sep 17 00:00:00 2001
From: Luke Barnard <lukeb@openmarket.com>
Date: Wed, 25 Oct 2017 18:18:37 +0100
Subject: [PATCH 16/20] Add more specific css class to allow for optional
 flipping

---
 .../vector/css/matrix-react-sdk/views/voip/_VideoView.scss     | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/skins/vector/css/matrix-react-sdk/views/voip/_VideoView.scss b/src/skins/vector/css/matrix-react-sdk/views/voip/_VideoView.scss
index eb435383..feb60f47 100644
--- a/src/skins/vector/css/matrix-react-sdk/views/voip/_VideoView.scss
+++ b/src/skins/vector/css/matrix-react-sdk/views/voip/_VideoView.scss
@@ -42,5 +42,8 @@ limitations under the License.
 .mx_VideoView_localVideoFeed video {
     width: auto;
     height: 100%;
+}
+
+.mx_VideoView_localVideoFeed.mx_VideoView_localVideoFeed_flipped video {
     transform: scale(-1, 1);
 }

From 7588814ec5358a2f9c2384e0517321ff8825700d Mon Sep 17 00:00:00 2001
From: Vicky Koblinski <vickykoblinski@gmail.com>
Date: Thu, 26 Oct 2017 20:37:15 -0400
Subject: [PATCH 17/20] Leverages ES6 and fixes typo

---
 .../views/settings/Notifications.js           | 271 +++++++++---------
 1 file changed, 135 insertions(+), 136 deletions(-)

diff --git a/src/components/views/settings/Notifications.js b/src/components/views/settings/Notifications.js
index a58b5d58..3c017421 100644
--- a/src/components/views/settings/Notifications.js
+++ b/src/components/views/settings/Notifications.js
@@ -15,15 +15,15 @@ limitations under the License.
 */
 
 'use strict';
-var React = require('react');
+const React = require('react');
 import { _t, _tJsx } from 'matrix-react-sdk/lib/languageHandler';
 import Promise from 'bluebird';
-var sdk = require('matrix-react-sdk');
-var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
-var UserSettingsStore = require('matrix-react-sdk/lib/UserSettingsStore');
-var Modal = require('matrix-react-sdk/lib/Modal');
+const sdk = require('matrix-react-sdk');
+const MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
+const UserSettingsStore = require('matrix-react-sdk/lib/UserSettingsStore');
+const Modal = require('matrix-react-sdk/lib/Modal');
 
-var notifications = require('../../../notifications');
+const notifications = require('../../../notifications');
 
 // TODO: this "view" component still has far too much application logic in it,
 // which should be factored out to other files.
@@ -31,17 +31,17 @@ var notifications = require('../../../notifications');
 // 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;
+const NotificationUtils = notifications.NotificationUtils;
+const VectorPushRulesDefinitions = notifications.VectorPushRulesDefinitions;
+const PushRuleVectorState = notifications.PushRuleVectorState;
+const ContentRules = notifications.ContentRules;
 
 /**
  * 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 = {
+const 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",
@@ -51,7 +51,7 @@ var LEGACY_RULES = {
 };
 
 function portLegacyActions(actions) {
-    var decoded = NotificationUtils.decodeActions(actions);
+    const decoded = NotificationUtils.decodeActions(actions);
     if (decoded !== null) {
         return NotificationUtils.encodeActions(decoded);
     } else {
@@ -62,7 +62,7 @@ function portLegacyActions(actions) {
 }
 
 module.exports = React.createClass({
-    displayName: 'Notififications',
+    displayName: 'Notifications',
 
     phases: {
         LOADING: "LOADING", // The component is loading or sending data to the hs
@@ -102,7 +102,7 @@ module.exports = React.createClass({
     },
 
     onEnableNotificationsChange: function(event) {
-        var self = this;
+        const self = this;
         this.setState({
             phase: this.phases.LOADING
         });
@@ -117,20 +117,20 @@ module.exports = React.createClass({
     },
 
     onEnableEmailNotificationsChange: function(address, event) {
-        var emailPusherPromise;
+        let emailPusherPromise;
         if (event.target.checked) {
-            var data = {}
+            const data = {}
             data['brand'] = this.props.brand || 'Riot';
             emailPusherPromise = UserSettingsStore.addEmailPusher(address, data);
         } else {
-            var emailPusher = UserSettingsStore.getEmailPusher(this.state.pushers, address);
+            const emailPusher = UserSettingsStore.getEmailPusher(this.state.pushers, address);
             emailPusher.kind = null;
             emailPusherPromise = MatrixClientPeg.get().setPusher(emailPusher);
         }
         emailPusherPromise.done(() => {
             this._refreshFromServer();
         }, (error) => {
-            var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+            const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
             Modal.createTrackedDialog('Error saving email notification preferences', '', ErrorDialog, {
                 title: _t('Error saving email notification preferences'),
                 description: _t('An error occurred whilst saving your email notification preferences.'),
@@ -140,14 +140,14 @@ 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];
+        const vectorRuleId = event.target.className.split("-")[0];
+        const newPushRuleVectorState = event.target.className.split("-")[1];
 
         if ("_keywords" === vectorRuleId) {
             this._setKeywordsPushRuleVectorState(newPushRuleVectorState)
         }
         else {
-            var rule = this.getRule(vectorRuleId);
+            const rule = this.getRule(vectorRuleId);
             if (rule) {
                 this._setPushRuleVectorState(rule, newPushRuleVectorState);
             }
@@ -155,12 +155,12 @@ module.exports = React.createClass({
     },
 
     onKeywordsClicked: function(event) {
-        var self = this;
+        const self = this;
 
         // Compute the keywords list to display
-        var keywords = [];
-        for (var i in this.state.vectorContentRules.rules) {
-            var rule = this.state.vectorContentRules.rules[i];
+        let keywords = [];
+        for (let i in this.state.vectorContentRules.rules) {
+            const rule = this.state.vectorContentRules.rules[i];
             keywords.push(rule.pattern);
         }
         if (keywords.length) {
@@ -174,7 +174,7 @@ module.exports = React.createClass({
             keywords = "";
         }
 
-        var TextInputDialog = sdk.getComponent("dialogs.TextInputDialog");
+        const TextInputDialog = sdk.getComponent("dialogs.TextInputDialog");
         Modal.createTrackedDialog('Keywords Dialog', '', TextInputDialog, {
             title: _t('Keywords'),
             description: _t('Enter keywords separated by a comma:'),
@@ -183,8 +183,8 @@ module.exports = React.createClass({
             onFinished: function onFinished(should_leave, newValue) {
 
                 if (should_leave && newValue !== keywords) {
-                    var newKeywords = newValue.split(',');
-                    for (var i in newKeywords) {
+                    let newKeywords = newValue.split(',');
+                    for (let i in newKeywords) {
                         newKeywords[i] = newKeywords[i].trim();
                     }
 
@@ -203,8 +203,8 @@ module.exports = React.createClass({
     },
 
     getRule: function(vectorRuleId) {
-        for (var i in this.state.vectorPushRules) {
-            var rule = this.state.vectorPushRules[i];
+        for (let i in this.state.vectorPushRules) {
+            const rule = this.state.vectorPushRules[i];
             if (rule.vectorRuleId === vectorRuleId) {
                 return rule;
             }
@@ -218,13 +218,13 @@ module.exports = React.createClass({
                 phase: this.phases.LOADING
             });
 
-            var self = this;
-            var cli = MatrixClientPeg.get();
-            var deferreds = [];
-            var ruleDefinition = VectorPushRulesDefinitions[rule.vectorRuleId];
+            const self = this;
+            const cli = MatrixClientPeg.get();
+            const deferreds = [];
+            const ruleDefinition = VectorPushRulesDefinitions[rule.vectorRuleId];
 
             if (rule.rule) {
-                var actions = ruleDefinition.vectorStateToActions[newPushRuleVectorState];
+                const actions = ruleDefinition.vectorStateToActions[newPushRuleVectorState];
 
                 if (!actions) {
                     // The new state corresponds to disabling the rule.
@@ -239,7 +239,7 @@ module.exports = React.createClass({
             Promise.all(deferreds).done(function() {
                 self._refreshFromServer();
             }, function(error) {
-                var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+                const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
                 console.error("Failed to change settings: " + error);
                 Modal.createTrackedDialog('Failed to change settings', '', ErrorDialog, {
                     title: _t('Failed to change settings'),
@@ -257,19 +257,19 @@ module.exports = React.createClass({
             return;
         }
 
-        var self = this;
-        var cli = MatrixClientPeg.get();
+        const self = this;
+        const cli = MatrixClientPeg.get();
 
         this.setState({
             phase: this.phases.LOADING
         });
 
         // Update all rules in self.state.vectorContentRules
-        var deferreds = [];
-        for (var i in this.state.vectorContentRules.rules) {
-            var rule = this.state.vectorContentRules.rules[i];
+        const deferreds = [];
+        for (let i in this.state.vectorContentRules.rules) {
+            const rule = this.state.vectorContentRules.rules[i];
 
-            var enabled, actions;
+            let enabled, actions;
             switch (newPushRuleVectorState) {
                 case PushRuleVectorState.ON:
                     if (rule.actions.length !== 1) {
@@ -309,7 +309,7 @@ module.exports = React.createClass({
         Promise.all(deferreds).done(function(resps) {
             self._refreshFromServer();
         }, function(error) {
-            var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+            const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
             console.error("Can't update user notification settings: " + error);
             Modal.createTrackedDialog('Can\'t update user notifcation settings', '', ErrorDialog, {
                 title: _t('Can\'t update user notification settings'),
@@ -324,14 +324,14 @@ module.exports = React.createClass({
             phase: this.phases.LOADING
         });
 
-        var self = this;
-        var cli = MatrixClientPeg.get();
-        var removeDeferreds = [];
+        const self = this;
+        const cli = MatrixClientPeg.get();
+        const removeDeferreds = [];
 
         // Remove per-word push rules of keywords that are no more in the list
-        var vectorContentRulesPatterns = [];
-        for (var i in self.state.vectorContentRules.rules) {
-            var rule = self.state.vectorContentRules.rules[i];
+        const vectorContentRulesPatterns = [];
+        for (let i in self.state.vectorContentRules.rules) {
+            const rule = self.state.vectorContentRules.rules[i];
 
             vectorContentRulesPatterns.push(rule.pattern);
 
@@ -342,16 +342,16 @@ module.exports = React.createClass({
 
         // If the keyword is part of `externalContentRules`, remove the rule
         // before recreating it in the right Vector path
-        for (var i in self.state.externalContentRules) {
-            var rule = self.state.externalContentRules[i];
+        for (let i in self.state.externalContentRules) {
+            const rule = self.state.externalContentRules[i];
 
             if (newKeywords.indexOf(rule.pattern) >= 0) {
                 removeDeferreds.push(cli.deletePushRule('global', rule.kind, rule.rule_id));
             }
         }
 
-        var onError = function(error) {
-            var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+        const onError = function(error) {
+            const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
             console.error("Failed to update keywords: " + error);
             Modal.createTrackedDialog('Failed to update keywords', '', ErrorDialog, {
                 title: _t('Failed to update keywords'),
@@ -362,9 +362,9 @@ module.exports = React.createClass({
 
         // Then, add the new ones
         Promise.all(removeDeferreds).done(function(resps) {
-            var deferreds = [];
+            const deferreds = [];
 
-            var pushRuleVectorStateKind = self.state.vectorContentRules.vectorState;
+            let pushRuleVectorStateKind = self.state.vectorContentRules.vectorState;
             if (pushRuleVectorStateKind === PushRuleVectorState.OFF) {
                 // When the current global keywords rule is OFF, we need to look at
                 // the flavor of rules in 'vectorContentRules' to apply the same actions
@@ -379,8 +379,8 @@ module.exports = React.createClass({
                 }
             }
 
-            for (var i in newKeywords) {
-                var keyword = newKeywords[i];
+            for (let i in newKeywords) {
+                const keyword = newKeywords[i];
 
                 if (vectorContentRulesPatterns.indexOf(keyword) < 0) {
                     if (self.state.vectorContentRules.vectorState !== PushRuleVectorState.OFF) {
@@ -407,31 +407,31 @@ module.exports = React.createClass({
 
     // Create a push rule but disabled
     _addDisabledPushRule: function(scope, kind, ruleId, body) {
-        var cli = MatrixClientPeg.get();
-        return cli.addPushRule(scope, kind, ruleId, body).then(function() {
-            return cli.setPushRuleEnabled(scope, kind, ruleId, false);
-        });
+        const cli = MatrixClientPeg.get();
+        return cli.addPushRule(scope, kind, ruleId, body).then(() =>
+            cli.setPushRuleEnabled(scope, kind, ruleId, false)
+        );
     },
 
     // 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();
+        const self = this;
+        const needsUpdate = [];
+        const cli = MatrixClientPeg.get();
 
-        for (var kind in rulesets.global) {
-            var ruleset = rulesets.global[kind];
-            for (var i = 0; i < ruleset.length; ++i) {
-                var rule = ruleset[i];
+        for (let kind in rulesets.global) {
+            const ruleset = rulesets.global[kind];
+            for (let i = 0; i < ruleset.length; ++i) {
+                const 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);
-                        }).catch( (e) => {
+                        ).then(() => 
+                            cli.deletePushRule('global', kind, rule.rule_id)
+                        ).catch( (e) => {
                             console.warn(`Error when porting legacy rule: ${e}`);
                         });
                     }(kind, rule));
@@ -442,9 +442,9 @@ module.exports = React.createClass({
         if (needsUpdate.length > 0) {
             // If some of the rules need to be ported then wait for the porting
             // to happen and then fetch the rules again.
-            return Promise.all(needsUpdate).then( function() {
-                return cli.getPushRules();
-            });
+            return Promise.all(needsUpdate).then(() => 
+                cli.getPushRules()
+            );
         } else {
             // Otherwise return the rules that we already have.
             return rulesets;
@@ -452,15 +452,14 @@ module.exports = React.createClass({
     },
 
     _refreshFromServer: function() {
-        var self = this;
-        var pushRulesPromise = MatrixClientPeg.get().getPushRules().then(self._portRulesToNewAPI).then(function(rulesets) {
-            //console.log("resolving pushRulesPromise");
+        const self = this;
+        const pushRulesPromise = MatrixClientPeg.get().getPushRules().then(self._portRulesToNewAPI).then(function(rulesets) {
 
             /// XXX seriously? wtf is this?
             MatrixClientPeg.get().pushRules = rulesets;
 
             // Get homeserver default rules and triage them by categories
-            var rule_categories = {
+            const rule_categories = {
                 // The master rule (all notifications disabling)
                 '.m.rule.master': 'master',
 
@@ -478,12 +477,12 @@ module.exports = React.createClass({
             };
 
             // HS default rules
-            var defaultRules = {master: [], vector: {}, others: []};
+            const defaultRules = {master: [], vector: {}, others: []};
 
-            for (var kind in rulesets.global) {
-                for (var i = 0; i < Object.keys(rulesets.global[kind]).length; ++i) {
-                    var r = rulesets.global[kind][i];
-                    var cat = rule_categories[r.rule_id];
+            for (let kind in rulesets.global) {
+                for (let i = 0; i < Object.keys(rulesets.global[kind]).length; ++i) {
+                    const r = rulesets.global[kind][i];
+                    const cat = rule_categories[r.rule_id];
                     r.kind = kind;
 
                     if (r.rule_id[0] === '.') {
@@ -506,7 +505,7 @@ module.exports = React.createClass({
             }
 
             // parse the keyword rules into our state
-            var contentRules = ContentRules.parseContentRules(rulesets);
+            const contentRules = ContentRules.parseContentRules(rulesets);
             self.state.vectorContentRules = {
                 vectorState: contentRules.vectorState,
                 rules: contentRules.rules,
@@ -517,7 +516,7 @@ module.exports = React.createClass({
             self.state.vectorPushRules = [];
             self.state.externalPushRules = [];
 
-            var vectorRuleIds = [
+            const vectorRuleIds = [
                 '.m.rule.contains_display_name',
                 '.m.rule.contains_user_name',
                 '_keywords',
@@ -528,8 +527,8 @@ module.exports = React.createClass({
                 '.m.rule.call',
                 '.m.rule.suppress_notices'
             ];
-            for (var i in vectorRuleIds) {
-                var vectorRuleId = vectorRuleIds[i];
+            for (let i in vectorRuleIds) {
+                const vectorRuleId = vectorRuleIds[i];
 
                 if (vectorRuleId === '_keywords') {
                     // keywords needs a special handling
@@ -541,9 +540,8 @@ module.exports = React.createClass({
                             <span>
                             { _tJsx('Messages containing <span>keywords</span>',
                                 /<span>(.*?)<\/span>/,
-                                (sub) => {
-                                    return <span className="mx_UserNotifSettings_keywords" onClick={ self.onKeywordsClicked }>{sub}</span>;
-                                }
+                                (sub) =>
+                                    <span className="mx_UserNotifSettings_keywords" onClick={ self.onKeywordsClicked }>{sub}</span>
                             )}
                             </span>
                         ),
@@ -551,10 +549,10 @@ module.exports = React.createClass({
                     });
                 }
                 else {
-                    var ruleDefinition = VectorPushRulesDefinitions[vectorRuleId];
-                    var rule = defaultRules.vector[vectorRuleId];
+                    const ruleDefinition = VectorPushRulesDefinitions[vectorRuleId];
+                    const rule = defaultRules.vector[vectorRuleId];
 
-                    var vectorState = ruleDefinition.ruleToVectorState(rule);
+                    const vectorState = ruleDefinition.ruleToVectorState(rule);
 
                     //console.log("Refreshing vectorPushRules for " + vectorRuleId +", "+ ruleDefinition.description +", " + rule +", " + vectorState);
 
@@ -574,14 +572,14 @@ module.exports = React.createClass({
             }
 
             // Build the rules not managed by Vector UI
-            var otherRulesDescriptions = {
+            const otherRulesDescriptions = {
                 '.m.rule.message': _t('Notify for all other messages/rooms'),
                 '.m.rule.fallback': _t('Notify me for anything else'),
             };
 
-            for (var i in defaultRules.others) {
-                var rule = defaultRules.others[i];
-                var ruleDescription = otherRulesDescriptions[rule.rule_id];
+            for (let i in defaultRules.others) {
+                const rule = defaultRules.others[i];
+                const ruleDescription = otherRulesDescriptions[rule.rule_id];
 
                 // Show enabled default rules that was modified by the user
                 if (ruleDescription && rule.enabled && !rule.default) {
@@ -591,8 +589,7 @@ module.exports = React.createClass({
             }
         });
 
-        var pushersPromise = MatrixClientPeg.get().getPushers().then(function(resp) {
-            //console.log("resolving pushersPromise");
+        const pushersPromise = MatrixClientPeg.get().getPushers().then(function(resp) {
             self.setState({pushers: resp.pushers});
         });
 
@@ -618,7 +615,7 @@ module.exports = React.createClass({
     },
 
     _updatePushRuleActions: function(rule, actions, enabled) {
-        var cli = MatrixClientPeg.get();
+        const cli = MatrixClientPeg.get();
 
         return cli.setPushRuleActions(
             'global', rule.kind, rule.rule_id, actions
@@ -664,9 +661,9 @@ module.exports = React.createClass({
     },
 
     renderNotifRulesTableRows: function() {
-        var rows = [];
-        for (var i in this.state.vectorPushRules) {
-            var rule = this.state.vectorPushRules[i];
+        const rows = [];
+        for (let i in this.state.vectorPushRules) {
+            const rule = this.state.vectorPushRules[i];
             //console.log("rendering: " + rule.description + ", " + rule.vectorRuleId + ", " + rule.vectorState);
             rows.push(this.renderNotifRulesTableRow(rule.description, rule.vectorRuleId, rule.vectorState));
         }
@@ -692,30 +689,32 @@ module.exports = React.createClass({
     },
 
     render: function() {
-        var self = this;
-
-        var spinner;
+        const self = this;
+        
+        let spinner;
         if (this.state.phase === this.phases.LOADING) {
-            var Loader = sdk.getComponent("elements.Spinner");
+            const Loader = sdk.getComponent("elements.Spinner");
             spinner = <Loader />;
         }
-
+        
+        let masterPushRuleDiv;
         if (this.state.masterPushRule) {
-            var masterPushRuleDiv = (
+            masterPushRuleDiv = (
                 <div className="mx_UserNotifSettings_tableRow">
-                        <div className="mx_UserNotifSettings_inputCell">
-                            <input id="enableNotifications"
-                                ref="enableNotifications"
-                                type="checkbox"
-                                checked={ !this.state.masterPushRule.enabled }
-                                onChange={ this.onEnableNotificationsChange } />
-                        </div>
-                        <div className="mx_UserNotifSettings_labelCell">
-                            <label htmlFor="enableNotifications">
-                                { _t('Enable notifications for this account') }
-                            </label>
-                        </div>
+                    <div className="mx_UserNotifSettings_inputCell">
+                        <input id="enableNotifications"
+                            ref="enableNotifications"
+                            type="checkbox"
+                            checked={ !this.state.masterPushRule.enabled }
+                            onChange={ this.onEnableNotificationsChange } 
+                        />
                     </div>
+                    <div className="mx_UserNotifSettings_labelCell">
+                        <label htmlFor="enableNotifications">
+                            { _t('Enable notifications for this account') }
+                        </label>
+                    </div>
+                </div>
             );
         }
 
@@ -743,29 +742,29 @@ module.exports = React.createClass({
             // This only supports the first email address in your profile for now
             emailNotificationsRow = this.emailNotificationsRow(
                 emailThreepids[0].address,
-                _t('Enable email notifications') + ' (' + emailThreepids[0].address + ')'
+                `${_t('Enable email notifications')} (${emailThreepids[0].address})`
             );
         }
 
         // Build external push rules
-        var externalRules = [];
-        for (var i in this.state.externalPushRules) {
-            var rule = this.state.externalPushRules[i];
+        const externalRules = [];
+        for (let i in this.state.externalPushRules) {
+            const rule = this.state.externalPushRules[i];
             externalRules.push(<li>{ _t(rule.description) }</li>);
         }
 
         // Show keywords not displayed by the vector UI as a single external push rule
-        var externalKeyWords = [];
-        for (var i in this.state.externalContentRules) {
-            var rule = this.state.externalContentRules[i];
-            externalKeyWords.push(rule.pattern);
+        let externalKeywords = [];
+        for (let i in this.state.externalContentRules) {
+            const rule = this.state.externalContentRules[i];
+            externalKeywords.push(rule.pattern);
         }
-        if (externalKeyWords.length) {
-            externalKeyWords = externalKeyWords.join(", ");
-            externalRules.push(<li>{ _t('Notifications on the following keywords follow rules which can’t be displayed here:') } { externalKeyWords }</li>);
+        if (externalKeywords.length) {
+            externalKeywords = externalKeywords.join(", ");
+            externalRules.push(<li>{ _t('Notifications on the following keywords follow rules which can’t be displayed here:') } { externalKeywords }</li>);
         }
 
-        var devicesSection;
+        let devicesSection;
         if (this.state.pushers === undefined) {
             devicesSection = <div className="error">{ _t('Unable to fetch notification target list') }</div>
         } else if (this.state.pushers.length == 0) {
@@ -773,8 +772,8 @@ module.exports = React.createClass({
         } else {
             // TODO: It would be great to be able to delete pushers from here too,
             // and this wouldn't be hard to add.
-            var rows = [];
-            for (var i = 0; i < this.state.pushers.length; ++i) {
+            const rows = [];
+            for (let i = 0; i < this.state.pushers.length; ++i) {
                 rows.push(<tr key={ i }>
                     <td>{this.state.pushers[i].app_display_name}</td>
                     <td>{this.state.pushers[i].device_display_name}</td>
@@ -793,7 +792,7 @@ module.exports = React.createClass({
             </div>);
         }
 
-        var advancedSettings;
+        let advancedSettings;
         if (externalRules.length) {
             advancedSettings = (
                 <div>

From d188fdb9fea056db32eeea499ec983c669110381 Mon Sep 17 00:00:00 2001
From: Vicky Koblinski <vickykoblinski@gmail.com>
Date: Thu, 26 Oct 2017 21:23:16 -0400
Subject: [PATCH 18/20] Changes requires to imports for consistancy, removes
 'use strict'

---
 .../views/settings/Notifications.js           | 25 +++++++++----------
 1 file changed, 12 insertions(+), 13 deletions(-)

diff --git a/src/components/views/settings/Notifications.js b/src/components/views/settings/Notifications.js
index 3c017421..afc465d4 100644
--- a/src/components/views/settings/Notifications.js
+++ b/src/components/views/settings/Notifications.js
@@ -14,16 +14,19 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-'use strict';
-const React = require('react');
-import { _t, _tJsx } from 'matrix-react-sdk/lib/languageHandler';
+import React from 'react';
 import Promise from 'bluebird';
-const sdk = require('matrix-react-sdk');
-const MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
-const UserSettingsStore = require('matrix-react-sdk/lib/UserSettingsStore');
-const Modal = require('matrix-react-sdk/lib/Modal');
-
-const notifications = require('../../../notifications');
+import sdk from 'matrix-react-sdk';
+import { _t, _tJsx } from 'matrix-react-sdk/lib/languageHandler';
+import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';
+import UserSettingsStore from 'matrix-react-sdk/lib/UserSettingsStore';
+import Modal from 'matrix-react-sdk/lib/Modal';
+import {
+    NotificationUtils, 
+    VectorPushRulesDefinitions, 
+    PushRuleVectorState, 
+    ContentRules
+} from '../../../notifications';
 
 // TODO: this "view" component still has far too much application logic in it,
 // which should be factored out to other files.
@@ -31,10 +34,6 @@ const notifications = require('../../../notifications');
 // TODO: this component also does a lot of direct poking into this.state, which
 // is VERY NAUGHTY.
 
-const NotificationUtils = notifications.NotificationUtils;
-const VectorPushRulesDefinitions = notifications.VectorPushRulesDefinitions;
-const PushRuleVectorState = notifications.PushRuleVectorState;
-const ContentRules = notifications.ContentRules;
 
 /**
  * Rules that Vector used to set in order to override the actions of default rules.

From 7bae7fe9e5e0cb0877e1038a1efbe2acbe105399 Mon Sep 17 00:00:00 2001
From: Luke Barnard <lukeb@openmarket.com>
Date: Fri, 27 Oct 2017 14:23:45 +0100
Subject: [PATCH 19/20] Use correct group store state when rendering "Invite to
 this community"

This required slight rework of how RightPanel uses the group store, and now it knows an unfortunate amount about the group store. Food for thought on stores in general, I think.
---
 src/components/structures/RightPanel.js | 43 ++++++++++++++++++-------
 1 file changed, 32 insertions(+), 11 deletions(-)

diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js
index e6bfea83..18567b57 100644
--- a/src/components/structures/RightPanel.js
+++ b/src/components/structures/RightPanel.js
@@ -100,6 +100,7 @@ module.exports = React.createClass({
         this.dispatcherRef = dis.register(this.onAction);
         const cli = this.context.matrixClient;
         cli.on("RoomState.members", this.onRoomStateMember);
+        this._initGroupStore(this.props.groupId);
     },
 
     componentWillUnmount: function() {
@@ -107,20 +108,42 @@ module.exports = React.createClass({
         if (this.context.matrixClient) {
             this.context.matrixClient.removeListener("RoomState.members", this.onRoomStateMember);
         }
+        this._unregisterGroupStore();
     },
 
     getInitialState: function() {
-        if (this.props.groupId) {
-            return {
-                phase: this.Phase.GroupMemberList,
-            };
-        } else {
-            return {
-                phase: this.Phase.RoomMemberList,
-            };
+        return {
+            phase: this.props.groupId ? this.Phase.GroupMemberList : this.Phase.RoomMemberList,
+            isUserPrivilegedInGroup: null,
         }
     },
 
+    componentWillReceiveProps(newProps) {
+        if (newProps.groupId !== this.props.groupId) {
+            this._unregisterGroupStore();
+            this._initGroupStore(newProps.groupId);
+        }
+    },
+
+    _initGroupStore(groupId) {
+        this._groupStore = GroupStoreCache.getGroupStore(
+            this.context.matrixClient, this.props.groupId,
+        );
+        this._groupStore.registerListener(this.onGroupStoreUpdated);
+    },
+
+    _unregisterGroupStore() {
+        if (this._groupStore) {
+            this._groupStore.unregisterListener(this.onGroupStoreUpdated);
+        }
+    },
+
+    onGroupStoreUpdated: function(){
+        this.setState({
+            isUserPrivilegedInGroup: this._groupStore.isUserPrivileged(),
+        });
+    },
+
     onCollapseClick: function() {
         dis.dispatch({
             action: 'hide_right_panel',
@@ -328,9 +351,7 @@ module.exports = React.createClass({
             panel = <div className="mx_RightPanel_blank"></div>;
         }
 
-        if (this.props.groupId &&
-            GroupStoreCache.getGroupStore(this.context.matrixClient, this.props.groupId).isUserPrivileged()
-        ) {
+        if (this.props.groupId && this.state.isUserPrivilegedInGroup) {
             inviteGroup = isPhaseGroup ? (
                 <AccessibleButton className="mx_RightPanel_invite" onClick={ this.onInviteButtonClick } >
                     <div className="mx_RightPanel_icon" >

From 3dcc397f6746ae931a8fb66d357973c1d3ee895d Mon Sep 17 00:00:00 2001
From: Luke Barnard <lukeb@openmarket.com>
Date: Fri, 27 Oct 2017 15:40:03 +0100
Subject: [PATCH 20/20] Fix AuxPanel floating in the middle

When there is no RoomView_body to push it to the top of the flex container
---
 src/skins/vector/css/matrix-react-sdk/structures/_RoomView.scss | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/skins/vector/css/matrix-react-sdk/structures/_RoomView.scss b/src/skins/vector/css/matrix-react-sdk/structures/_RoomView.scss
index dfd91885..7944d01d 100644
--- a/src/skins/vector/css/matrix-react-sdk/structures/_RoomView.scss
+++ b/src/skins/vector/css/matrix-react-sdk/structures/_RoomView.scss
@@ -68,7 +68,7 @@ limitations under the License.
     min-width: 0px;
     max-width: 960px;
     width: 100%;
-    margin: auto;
+    margin: 0px auto;
 
     overflow: auto;
     border-bottom: 1px solid $primary-hairline-color;