From 738313384609a8ed05072c0a97723c0a9350d648 Mon Sep 17 00:00:00 2001
From: Travis Ralston <travpc@gmail.com>
Date: Fri, 19 Oct 2018 13:30:38 -0600
Subject: [PATCH 01/34] Support parsing matrix.to links in the timeline with
 ?via= in them

This ends up being translated to ?server_name= in the matrix-js-sdk, although that has a bug at the time of writing. It converts `server_name: ['a', 'b']` to `?server_name=a,b` instead of `?server_name=a&server_name=b`

For reference: the `viaServers` option is routed through the 'join_room' action to RoomViewStore#_joinRoom which is passed directly to the js-sdk http-api#joinRoom function.

Next steps:
* Fix the js-sdk parsing
* Make the SDK generate matrix.to links with ?via=
---
 src/components/structures/LoggedInView.js |  4 ++++
 src/components/structures/MatrixChat.js   | 13 +++++++++++++
 src/components/structures/RoomView.js     |  7 +++++--
 3 files changed, 22 insertions(+), 2 deletions(-)

diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js
index 180a348434..d95d5cd652 100644
--- a/src/components/structures/LoggedInView.js
+++ b/src/components/structures/LoggedInView.js
@@ -64,6 +64,9 @@ const LoggedInView = React.createClass({
 
         teamToken: PropTypes.string,
 
+        // Used by the RoomView to handle joining rooms
+        viaServers: PropTypes.arrayOf(PropTypes.string),
+
         // and lots and lots of other stuff.
     },
 
@@ -389,6 +392,7 @@ const LoggedInView = React.createClass({
                         onRegistered={this.props.onRegistered}
                         thirdPartyInvite={this.props.thirdPartyInvite}
                         oobData={this.props.roomOobData}
+                        viaServers={this.props.viaServers}
                         eventPixelOffset={this.props.initialEventPixelOffset}
                         key={this.props.currentRoomId || 'roomview'}
                         disabled={this.props.middleDisabled}
diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js
index f385aacd40..5dbafbd2a7 100644
--- a/src/components/structures/MatrixChat.js
+++ b/src/components/structures/MatrixChat.js
@@ -840,6 +840,7 @@ export default React.createClass({
             page_type: PageTypes.RoomView,
             thirdPartyInvite: roomInfo.third_party_invite,
             roomOobData: roomInfo.oob_data,
+            viaServers: roomInfo.via_servers,
         };
 
         if (roomInfo.room_alias) {
@@ -1488,9 +1489,21 @@ export default React.createClass({
                 inviterName: params.inviter_name,
             };
 
+            // on our URLs there might be a ?via=matrix.org or similar to help
+            // joins to the room succeed. We'll pass these through as an array
+            // to other levels. If there's just one ?via= then params.via is a
+            // single string. If someone does something like ?via=one.com&via=two.com
+            // then params.via is an array of strings.
+            let via = [];
+            if (params.via) {
+                if (typeof(params.via) === 'string') via = [params.via];
+                else via = params.via;
+            }
+
             const payload = {
                 action: 'view_room',
                 event_id: eventId,
+                via_servers: via,
                 // If an event ID is given in the URL hash, notify RoomViewStore to mark
                 // it as highlighted, which will propagate to RoomView and highlight the
                 // associated EventTile.
diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js
index 8e226bdfcf..5d51b9f9a0 100644
--- a/src/components/structures/RoomView.js
+++ b/src/components/structures/RoomView.js
@@ -88,6 +88,9 @@ module.exports = React.createClass({
 
         // is the RightPanel collapsed?
         collapsedRhs: PropTypes.bool,
+
+        // Servers the RoomView can use to try and assist joins
+        viaServers: PropTypes.arrayOf(PropTypes.string),
     },
 
     getInitialState: function() {
@@ -833,7 +836,7 @@ module.exports = React.createClass({
                 action: 'do_after_sync_prepared',
                 deferred_action: {
                     action: 'join_room',
-                    opts: { inviteSignUrl: signUrl },
+                    opts: { inviteSignUrl: signUrl, viaServers: this.props.viaServers },
                 },
             });
 
@@ -875,7 +878,7 @@ module.exports = React.createClass({
                 this.props.thirdPartyInvite.inviteSignUrl : undefined;
             dis.dispatch({
                 action: 'join_room',
-                opts: { inviteSignUrl: signUrl },
+                opts: { inviteSignUrl: signUrl, viaServers: this.props.viaServers },
             });
             return Promise.resolve();
         });

From 7ef08314b8d329dda6abac2e56749b233fd2860a Mon Sep 17 00:00:00 2001
From: Travis Ralston <travpc@gmail.com>
Date: Fri, 19 Oct 2018 16:22:20 -0600
Subject: [PATCH 02/34] Redirect widgets to another location before deleting
 them

This is so that shutdown hooks in the widget can correctly fire, such as Jitsi's hook to abandon its hold on the webcam.

Fixes https://github.com/vector-im/riot-web/issues/7351
---
 src/components/views/elements/AppTile.js | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js
index 7be0bab33c..71cd65c89f 100644
--- a/src/components/views/elements/AppTile.js
+++ b/src/components/views/elements/AppTile.js
@@ -318,6 +318,19 @@ export default class AppTile extends React.Component {
                         }
                         this.setState({deleting: true});
 
+                        // HACK: This is a really dirty way to ensure that Jitsi cleans up
+                        // its hold on the webcam. Without this, the widget holds a media
+                        // stream open, even after death. See https://github.com/vector-im/riot-web/issues/7351
+                        if (this.refs.appFrame) {
+                            // In practice we could just do `+= ''` to trick the browser
+                            // into thinking the URL changed, however I can foresee this
+                            // being optimized out by a browser. Instead, we'll just point
+                            // the iframe at a page that is reasonably safe to use in the
+                            // event the iframe doesn't wink away.
+                            // This is relative to where the Riot instance is located.
+                            this.refs.appFrame.src = '/config.json';
+                        }
+
                         WidgetUtils.setRoomWidget(
                             this.props.room.roomId,
                             this.props.id,

From 819fab319832ec1ac907e92e7b65b34a76db7e65 Mon Sep 17 00:00:00 2001
From: David Baker <dave@matrix.org>
Date: Wed, 24 Oct 2018 11:20:39 +0100
Subject: [PATCH 03/34] js-sdk rc.1

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index dabaefe0ad..44d3b41ba6 100644
--- a/package.json
+++ b/package.json
@@ -75,7 +75,7 @@
     "linkifyjs": "^2.1.6",
     "lodash": "^4.13.1",
     "lolex": "2.3.2",
-    "matrix-js-sdk": "matrix-org/matrix-js-sdk#develop",
+    "matrix-js-sdk": "0.12.1-rc.1",
     "optimist": "^0.6.1",
     "pako": "^1.0.5",
     "prop-types": "^15.5.8",

From 5f8eaa37821671fba1bcd6eff034753ea3e57cfc Mon Sep 17 00:00:00 2001
From: David Baker <dave@matrix.org>
Date: Wed, 24 Oct 2018 11:23:12 +0100
Subject: [PATCH 04/34] Prepare changelog for v0.14.2-rc.1

---
 CHANGELOG.md | 37 +++++++++++++++++++++++++++++++++++++
 1 file changed, 37 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 00b035ac9c..b7e5ca7adf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,40 @@
+Changes in [0.14.2-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.14.2-rc.1) (2018-10-24)
+===============================================================================================================
+[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.14.1...v0.14.2-rc.1)
+
+ * Update from Weblate.
+   [\#2244](https://github.com/matrix-org/matrix-react-sdk/pull/2244)
+ * Show the group member list again
+   [\#2223](https://github.com/matrix-org/matrix-react-sdk/pull/2223)
+ * lint: make colorScheme camel case
+   [\#2237](https://github.com/matrix-org/matrix-react-sdk/pull/2237)
+ * Change leave room button text, OK -> Leave
+   [\#2236](https://github.com/matrix-org/matrix-react-sdk/pull/2236)
+ * Move all dialog buttons to the right and fix their order
+   [\#2231](https://github.com/matrix-org/matrix-react-sdk/pull/2231)
+ * Add a bit of text to explain the purpose of the RoomPreviewSpinner
+   [\#2225](https://github.com/matrix-org/matrix-react-sdk/pull/2225)
+ * Move the login box from the left sidebar to where the composer is
+   [\#2219](https://github.com/matrix-org/matrix-react-sdk/pull/2219)
+ * Fix an error where React doesn't like value=null on a select
+   [\#2230](https://github.com/matrix-org/matrix-react-sdk/pull/2230)
+ * add missing sticker translation
+   [\#2216](https://github.com/matrix-org/matrix-react-sdk/pull/2216)
+ * Support m.login.terms during registration
+   [\#2221](https://github.com/matrix-org/matrix-react-sdk/pull/2221)
+ * Don't show the invite nag bar when peeking
+   [\#2220](https://github.com/matrix-org/matrix-react-sdk/pull/2220)
+ * Apply the user's tint once the MatrixClientPeg is moderately ready
+   [\#2214](https://github.com/matrix-org/matrix-react-sdk/pull/2214)
+ * Make rageshake use less memory
+   [\#2217](https://github.com/matrix-org/matrix-react-sdk/pull/2217)
+ * Fix autocomplete
+   [\#2212](https://github.com/matrix-org/matrix-react-sdk/pull/2212)
+ * Explain feature states in a lot more detail
+   [\#2211](https://github.com/matrix-org/matrix-react-sdk/pull/2211)
+ * Fix various lint errors
+   [\#2213](https://github.com/matrix-org/matrix-react-sdk/pull/2213)
+
 Changes in [0.14.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.14.1) (2018-10-19)
 =====================================================================================================
 [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.14.0...v0.14.1)

From 43702a48875f184cd9e7b96da2a93e345e6bca43 Mon Sep 17 00:00:00 2001
From: David Baker <dave@matrix.org>
Date: Wed, 24 Oct 2018 11:23:57 +0100
Subject: [PATCH 05/34] v0.14.2-rc.1

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 44d3b41ba6..63f275a609 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "matrix-react-sdk",
-  "version": "0.14.1",
+  "version": "0.14.2-rc.1",
   "description": "SDK for matrix.org using React",
   "author": "matrix.org",
   "repository": {

From a8782120fe8a591fb126cee2dc16b8c9dba18c0e Mon Sep 17 00:00:00 2001
From: Travis Ralston <travpc@gmail.com>
Date: Wed, 24 Oct 2018 16:57:16 -0600
Subject: [PATCH 06/34] Install memfs because webpack is made of fail

---
 package.json | 1 +
 1 file changed, 1 insertion(+)

diff --git a/package.json b/package.json
index dabaefe0ad..03311a50e3 100644
--- a/package.json
+++ b/package.json
@@ -76,6 +76,7 @@
     "lodash": "^4.13.1",
     "lolex": "2.3.2",
     "matrix-js-sdk": "matrix-org/matrix-js-sdk#develop",
+    "memfs": "^2.10.1",
     "optimist": "^0.6.1",
     "pako": "^1.0.5",
     "prop-types": "^15.5.8",

From e8cb636631b8830002f3b429d896608ce5182e6c Mon Sep 17 00:00:00 2001
From: Travis Ralston <travpc@gmail.com>
Date: Wed, 24 Oct 2018 18:01:08 -0600
Subject: [PATCH 07/34] Pick servers for ?via on matrix.to links based on some
 heuristics

---
 src/matrix-to.js       |  84 ++++++++++++++++++++++-
 test/matrix-to-test.js | 148 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 230 insertions(+), 2 deletions(-)
 create mode 100644 test/matrix-to-test.js

diff --git a/src/matrix-to.js b/src/matrix-to.js
index 90b0a66090..706363a251 100644
--- a/src/matrix-to.js
+++ b/src/matrix-to.js
@@ -14,11 +14,14 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
+import MatrixClientPeg from "./MatrixClientPeg";
+
 export const host = "matrix.to";
 export const baseUrl = `https://${host}`;
 
 export function makeEventPermalink(roomId, eventId) {
-    return `${baseUrl}/#/${roomId}/${eventId}`;
+    const serverCandidates = pickServerCandidates(roomId);
+    return `${baseUrl}/#/${roomId}/${eventId}?${encodeServerCandidates(serverCandidates)}`;
 }
 
 export function makeUserPermalink(userId) {
@@ -26,9 +29,86 @@ export function makeUserPermalink(userId) {
 }
 
 export function makeRoomPermalink(roomId) {
-    return `${baseUrl}/#/${roomId}`;
+    const serverCandidates = pickServerCandidates(roomId);
+    return `${baseUrl}/#/${roomId}?${encodeServerCandidates(serverCandidates)}`;
 }
 
 export function makeGroupPermalink(groupId) {
     return `${baseUrl}/#/${groupId}`;
 }
+
+export function encodeServerCandidates(candidates) {
+    if (!candidates) return '';
+    return `via=${candidates.map(c => encodeURIComponent(c)).join("&via=")}`
+}
+
+export function pickServerCandidates(roomId) {
+    const client = MatrixClientPeg.get();
+    const room = client.getRoom(roomId);
+    if (!room) return [];
+
+    // Permalinks can have servers appended to them so that the user
+    // receiving them can have a fighting chance at joining the room.
+    // These servers are called "candidates" at this point because
+    // it is unclear whether they are going to be useful to actually
+    // join in the future.
+    //
+    // We pick 3 servers based on the following criteria:
+    //
+    //   Server 1: The highest power level user in the room, provided
+    //   they are at least PL 50. We don't calculate "what is a moderator"
+    //   here because it is less relevant for the vast majority of rooms.
+    //   We also want to ensure that we get an admin or high-ranking mod
+    //   as they are less likely to leave the room. If no user happens
+    //   to meet this criteria, we'll pick the most popular server in the
+    //   room.
+    //
+    //   Server 2: The next most popular server in the room (in user
+    //   distribution). This will probably be matrix.org in most cases
+    //   although it is certainly possible to be some other server. This
+    //   cannot be the same as Server 1. If no other servers are available
+    //   then we'll only return Server 1.
+    //
+    //   Server 3: The next most popular server by user distribution. This
+    //   has the same rules as Server 2, with the added exception that it
+    //   must be unique from Server 1 and 2.
+
+    // Rationale for popular servers: It's hard to get rid of people when
+    // they keep flocking in from a particular server. Sure, the server could
+    // be ACL'd in the future or for some reason be evicted from the room
+    // however an event like that is unlikely the larger the room gets.
+
+    // Note: Users receiving permalinks that happen to have all 3 potential
+    // servers fail them (in terms of joining) are somewhat expected to hunt
+    // down the person who gave them the link to ask for a participating server.
+    // The receiving user can then manually append the known-good server to
+    // the list and magically have the link work.
+
+    const populationMap: {[server:string]:number} = {};
+    const highestPlUser = {userId:null, powerLevel: 0, serverName: null};
+
+    for (const member of room.getJoinedMembers()) {
+        const serverName = member.userId.split(":").splice(1).join(":");
+        if (member.powerLevel > highestPlUser.powerLevel) {
+            highestPlUser.userId = member.userId;
+            highestPlUser.powerLevel = member.powerLevel;
+            highestPlUser.serverName = serverName;
+        }
+
+        if (!populationMap[serverName]) populationMap[serverName] = 0;
+        populationMap[serverName]++;
+    }
+
+    const candidates = [];
+    if (highestPlUser.powerLevel >= 50) candidates.push(highestPlUser.serverName);
+
+    const maxCandidates = 3;
+    const serversByPopulation = Object.keys(populationMap)
+        .sort((a, b) => populationMap[a] - populationMap[b])
+        .filter(a => !candidates.includes(a));
+    while(candidates.length < maxCandidates && candidates.length <= serversByPopulation.length) {
+        candidates.push(serversByPopulation[Math.max(0, candidates.length - 1)]);
+    }
+
+    return candidates;
+}
\ No newline at end of file
diff --git a/test/matrix-to-test.js b/test/matrix-to-test.js
new file mode 100644
index 0000000000..9b17a37860
--- /dev/null
+++ b/test/matrix-to-test.js
@@ -0,0 +1,148 @@
+/*
+Copyright 2018 New Vector Ltd
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+    http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import expect from 'expect';
+import peg from '../src/MatrixClientPeg';
+import {pickServerCandidates} from "../src/matrix-to";
+
+
+describe('matrix-to', function () {
+    it('should pick no candidate servers when the room is not found', function () {
+        //peg.getRoom = () => null;
+        const pickedServers = pickServerCandidates("!somewhere:example.org");
+        expect(pickedServers).toExist();
+        expect(pickedServers.length).toBe(0);
+    });
+    it('should pick no candidate servers when the room has no members', function () {
+        peg.getRoom = () => {
+            return {
+                getJoinedMembers: () => [],
+            }
+        };
+        const pickedServers = pickServerCandidates("!somewhere:example.org");
+        expect(pickedServers).toExist();
+        expect(pickedServers.length).toBe(0);
+    });
+    it('should pick no candidate servers when no users have enough power level', function () {
+        peg.getRoom = () => {
+            return {
+                getJoinedMembers: () => [
+                    {
+                        userId: "@alice:example.org",
+                        powerLevel: 0,
+                    },
+                    {
+                        userId: "@bob:example.org",
+                        powerLevel: 25,
+                    }
+                ],
+            }
+        };
+        const pickedServers = pickServerCandidates("!somewhere:example.org");
+        expect(pickedServers).toExist();
+        expect(pickedServers.length).toBe(0);
+    });
+    it('should pick a candidate server for the highest power level user in the room', function () {
+        peg.getRoom = () => {
+            return {
+                getJoinedMembers: () => [
+                    {
+                        userId: "@alice:pl_50",
+                        powerLevel: 50,
+                    },
+                    {
+                        userId: "@alice:pl_75",
+                        powerLevel: 75,
+                    },
+                    {
+                        userId: "@alice:pl_95",
+                        powerLevel: 95,
+                    }
+                ],
+            }
+        };
+        const pickedServers = pickServerCandidates("!somewhere:example.org");
+        expect(pickedServers).toExist();
+        expect(pickedServers.length).toBe(3);
+        expect(pickedServers[0]).toBe("pl_95");
+        // we don't check the 2nd and 3rd servers because that is done by the next test
+    });
+    it('should pick candidate servers based on user population', function () {
+        peg.getRoom = () => {
+            return {
+                getJoinedMembers: () => [
+                    {
+                        userId: "@alice:first",
+                        powerLevel: 0,
+                    },
+                    {
+                        userId: "@bob:first",
+                        powerLevel: 0,
+                    },
+                    {
+                        userId: "@charlie:first",
+                        powerLevel: 0,
+                    },
+                    {
+                        userId: "@alice:second",
+                        powerLevel: 0,
+                    },
+                    {
+                        userId: "@bob:second",
+                        powerLevel: 0,
+                    },
+                    {
+                        userId: "@charlie:third",
+                        powerLevel: 0,
+                    }
+                ],
+            }
+        };
+        const pickedServers = pickServerCandidates("!somewhere:example.org");
+        expect(pickedServers).toExist();
+        expect(pickedServers.length).toBe(3);
+        expect(pickedServers[0]).toBe("first");
+        expect(pickedServers[1]).toBe("second");
+        expect(pickedServers[2]).toBe("third");
+    });
+    it('should pick prefer candidate servers with higher power levels', function () {
+        peg.getRoom = () => {
+            return {
+                getJoinedMembers: () => [
+                    {
+                        userId: "@alice:first",
+                        powerLevel: 100,
+                    },
+                    {
+                        userId: "@alice:second",
+                        powerLevel: 0,
+                    },
+                    {
+                        userId: "@bob:second",
+                        powerLevel: 0,
+                    },
+                    {
+                        userId: "@charlie:third",
+                        powerLevel: 0,
+                    }
+                ],
+            }
+        };
+        const pickedServers = pickServerCandidates("!somewhere:example.org");
+        expect(pickedServers).toExist();
+        expect(pickedServers.length).toBe(3);
+        expect(pickedServers[0]).toBe("first");
+        expect(pickedServers[1]).toBe("second");
+        expect(pickedServers[2]).toBe("third");
+    });
+});

From 206ad3159c6f1b41ebf1770dcb394d7cea77da30 Mon Sep 17 00:00:00 2001
From: Bruno Windels <brunow@matrix.org>
Date: Thu, 25 Oct 2018 17:47:38 +0200
Subject: [PATCH 08/34] disable e2e tests for PRs targeted at experimental
 (redesign)

---
 .travis-test-riot.sh | 21 ++++++++++++---------
 1 file changed, 12 insertions(+), 9 deletions(-)

diff --git a/.travis-test-riot.sh b/.travis-test-riot.sh
index 7f2660a906..6cd073a1f8 100755
--- a/.travis-test-riot.sh
+++ b/.travis-test-riot.sh
@@ -27,12 +27,15 @@ npm run build
 npm run test
 popd
 
-# run end to end tests
-git clone https://github.com/matrix-org/matrix-react-end-to-end-tests.git --branch master
-pushd matrix-react-end-to-end-tests
-ln -s $REACT_SDK_DIR/$RIOT_WEB_DIR riot/riot-web
-# PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true ./install.sh
-# CHROME_PATH=$(which google-chrome-stable) ./run.sh
-./install.sh
-./run.sh --travis
-popd
+if [ "$TRAVIS_BRANCH" != "experimental" ]
+then
+    # run end to end tests
+    git clone https://github.com/matrix-org/matrix-react-end-to-end-tests.git --branch master
+    pushd matrix-react-end-to-end-tests
+    ln -s $REACT_SDK_DIR/$RIOT_WEB_DIR riot/riot-web
+    # PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true ./install.sh
+    # CHROME_PATH=$(which google-chrome-stable) ./run.sh
+    ./install.sh
+    ./run.sh --travis
+    popd
+fi

From 54ff5d8f256cfb1c34f518430ab62ab6df9c6ba0 Mon Sep 17 00:00:00 2001
From: Travis Ralston <travpc@gmail.com>
Date: Thu, 25 Oct 2018 15:21:18 -0600
Subject: [PATCH 09/34] Fix Karma/Webpack so it can build the tests

---
 karma.conf.js | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/karma.conf.js b/karma.conf.js
index 4d699599cb..41ddbdf249 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -199,12 +199,25 @@ module.exports = function (config) {
 
                     'matrix-react-sdk': path.resolve('test/skinned-sdk.js'),
                     'sinon': 'sinon/pkg/sinon.js',
+
+                    // To make webpack happy
+                    // Related: https://github.com/request/request/issues/1529
+                    // (there's no mock available for fs, so we fake a mock by using
+                    // an in-memory version of fs)
+                    "fs": "memfs",
                 },
                 modules: [
                     path.resolve('./test'),
                     "node_modules"
                 ],
             },
+            node: {
+                // Because webpack is made of fail
+                // https://github.com/request/request/issues/1529
+                // Note: 'mock' is the new 'empty'
+                net: 'mock',
+                tls: 'mock'
+            },
             devtool: 'inline-source-map',
             externals: {
                 // Don't try to bundle electron: leave it as a commonjs dependency

From b9bfbdc22d70ef8af96a3b331d6f24a09ee1363b Mon Sep 17 00:00:00 2001
From: Travis Ralston <travpc@gmail.com>
Date: Thu, 25 Oct 2018 15:22:28 -0600
Subject: [PATCH 10/34] Fix the tests so they actually test something

---
 test/matrix-to-test.js | 46 ++++++++++++++++++++----------------------
 1 file changed, 22 insertions(+), 24 deletions(-)

diff --git a/test/matrix-to-test.js b/test/matrix-to-test.js
index 9b17a37860..3bf6b5af6f 100644
--- a/test/matrix-to-test.js
+++ b/test/matrix-to-test.js
@@ -14,17 +14,31 @@ limitations under the License.
 import expect from 'expect';
 import peg from '../src/MatrixClientPeg';
 import {pickServerCandidates} from "../src/matrix-to";
+import * as testUtils from "./test-utils";
 
 
 describe('matrix-to', function () {
+    let sandbox;
+
+    beforeEach(function() {
+        testUtils.beforeEach(this);
+        sandbox = testUtils.stubClient();
+        peg.get().credentials = { userId: "@test:example.com" };
+    });
+
+    afterEach(function() {
+        sandbox.restore();
+    });
+
     it('should pick no candidate servers when the room is not found', function () {
-        //peg.getRoom = () => null;
+        peg.get().getRoom = () => null;
         const pickedServers = pickServerCandidates("!somewhere:example.org");
         expect(pickedServers).toExist();
         expect(pickedServers.length).toBe(0);
     });
+
     it('should pick no candidate servers when the room has no members', function () {
-        peg.getRoom = () => {
+        peg.get().getRoom = () => {
             return {
                 getJoinedMembers: () => [],
             }
@@ -33,27 +47,9 @@ describe('matrix-to', function () {
         expect(pickedServers).toExist();
         expect(pickedServers.length).toBe(0);
     });
-    it('should pick no candidate servers when no users have enough power level', function () {
-        peg.getRoom = () => {
-            return {
-                getJoinedMembers: () => [
-                    {
-                        userId: "@alice:example.org",
-                        powerLevel: 0,
-                    },
-                    {
-                        userId: "@bob:example.org",
-                        powerLevel: 25,
-                    }
-                ],
-            }
-        };
-        const pickedServers = pickServerCandidates("!somewhere:example.org");
-        expect(pickedServers).toExist();
-        expect(pickedServers.length).toBe(0);
-    });
+
     it('should pick a candidate server for the highest power level user in the room', function () {
-        peg.getRoom = () => {
+        peg.get().getRoom = () => {
             return {
                 getJoinedMembers: () => [
                     {
@@ -77,8 +73,9 @@ describe('matrix-to', function () {
         expect(pickedServers[0]).toBe("pl_95");
         // we don't check the 2nd and 3rd servers because that is done by the next test
     });
+
     it('should pick candidate servers based on user population', function () {
-        peg.getRoom = () => {
+        peg.get().getRoom = () => {
             return {
                 getJoinedMembers: () => [
                     {
@@ -115,8 +112,9 @@ describe('matrix-to', function () {
         expect(pickedServers[1]).toBe("second");
         expect(pickedServers[2]).toBe("third");
     });
+
     it('should pick prefer candidate servers with higher power levels', function () {
-        peg.getRoom = () => {
+        peg.get().getRoom = () => {
             return {
                 getJoinedMembers: () => [
                     {

From 43980addd008aa78d353bc632c43f1ca3d3751e5 Mon Sep 17 00:00:00 2001
From: Travis Ralston <travpc@gmail.com>
Date: Thu, 25 Oct 2018 15:22:52 -0600
Subject: [PATCH 11/34] Add hostname sanity tests

In the event someone changes how the hostname parsing works.
---
 test/matrix-to-test.js | 85 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 85 insertions(+)

diff --git a/test/matrix-to-test.js b/test/matrix-to-test.js
index 3bf6b5af6f..dbe7fa55ff 100644
--- a/test/matrix-to-test.js
+++ b/test/matrix-to-test.js
@@ -143,4 +143,89 @@ describe('matrix-to', function () {
         expect(pickedServers[1]).toBe("second");
         expect(pickedServers[2]).toBe("third");
     });
+
+    it('should work with IPv4 hostnames', function () {
+        peg.get().getRoom = () => {
+            return {
+                getJoinedMembers: () => [
+                    {
+                        userId: "@alice:127.0.0.1",
+                        powerLevel: 100,
+                    }
+                ],
+            }
+        };
+        const pickedServers = pickServerCandidates("!somewhere:example.org");
+        expect(pickedServers).toExist();
+        expect(pickedServers.length).toBe(1);
+        expect(pickedServers[0]).toBe("127.0.0.1");
+    });
+
+    it('should work with IPv6 hostnames', function () {
+        peg.get().getRoom = () => {
+            return {
+                getJoinedMembers: () => [
+                    {
+                        userId: "@alice:[::1]",
+                        powerLevel: 100,
+                    }
+                ],
+            }
+        };
+        const pickedServers = pickServerCandidates("!somewhere:example.org");
+        expect(pickedServers).toExist();
+        expect(pickedServers.length).toBe(1);
+        expect(pickedServers[0]).toBe("[::1]");
+    });
+
+    it('should work with IPv4 hostnames with ports', function () {
+        peg.get().getRoom = () => {
+            return {
+                getJoinedMembers: () => [
+                    {
+                        userId: "@alice:127.0.0.1:8448",
+                        powerLevel: 100,
+                    }
+                ],
+            }
+        };
+        const pickedServers = pickServerCandidates("!somewhere:example.org");
+        expect(pickedServers).toExist();
+        expect(pickedServers.length).toBe(1);
+        expect(pickedServers[0]).toBe("127.0.0.1:8448");
+    });
+
+    it('should work with IPv6 hostnames with ports', function () {
+        peg.get().getRoom = () => {
+            return {
+                getJoinedMembers: () => [
+                    {
+                        userId: "@alice:[::1]:8448",
+                        powerLevel: 100,
+                    }
+                ],
+            }
+        };
+        const pickedServers = pickServerCandidates("!somewhere:example.org");
+        expect(pickedServers).toExist();
+        expect(pickedServers.length).toBe(1);
+        expect(pickedServers[0]).toBe("[::1]:8448");
+    });
+
+    it('should work with hostnames with ports', function () {
+        peg.get().getRoom = () => {
+            return {
+                getJoinedMembers: () => [
+                    {
+                        userId: "@alice:example.org:8448",
+                        powerLevel: 100,
+                    }
+                ],
+            }
+        };
+        const pickedServers = pickServerCandidates("!somewhere:example.org");
+        expect(pickedServers).toExist();
+        expect(pickedServers.length).toBe(1);
+        expect(pickedServers[0]).toBe("example.org:8448");
+    });
 });

From 52094964a01897a0c65ca82f1ef8123808efc6f3 Mon Sep 17 00:00:00 2001
From: Travis Ralston <travpc@gmail.com>
Date: Thu, 25 Oct 2018 15:23:15 -0600
Subject: [PATCH 12/34] Fix the server candidate picker to actually work

Tests do wonders.
---
 src/matrix-to.js | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/src/matrix-to.js b/src/matrix-to.js
index 706363a251..881972410f 100644
--- a/src/matrix-to.js
+++ b/src/matrix-to.js
@@ -102,12 +102,15 @@ export function pickServerCandidates(roomId) {
     const candidates = [];
     if (highestPlUser.powerLevel >= 50) candidates.push(highestPlUser.serverName);
 
+    const beforePopulation = candidates.length;
     const maxCandidates = 3;
     const serversByPopulation = Object.keys(populationMap)
-        .sort((a, b) => populationMap[a] - populationMap[b])
+        .sort((a, b) => populationMap[b] - populationMap[a])
         .filter(a => !candidates.includes(a));
-    while(candidates.length < maxCandidates && candidates.length <= serversByPopulation.length) {
-        candidates.push(serversByPopulation[Math.max(0, candidates.length - 1)]);
+    for (let i = beforePopulation; i <= maxCandidates; i++) {
+        const idx = i - beforePopulation;
+        if (idx >= serversByPopulation.length) break;
+        candidates.push(serversByPopulation[idx]);
     }
 
     return candidates;

From aaeb6e49785fdbf5b2e823ddb7e319d9052f2c0e Mon Sep 17 00:00:00 2001
From: Travis Ralston <travpc@gmail.com>
Date: Thu, 25 Oct 2018 15:59:42 -0600
Subject: [PATCH 13/34] Use about:blank instead

---
 src/components/views/elements/AppTile.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js
index 71cd65c89f..23b24adbb4 100644
--- a/src/components/views/elements/AppTile.js
+++ b/src/components/views/elements/AppTile.js
@@ -328,7 +328,7 @@ export default class AppTile extends React.Component {
                             // the iframe at a page that is reasonably safe to use in the
                             // event the iframe doesn't wink away.
                             // This is relative to where the Riot instance is located.
-                            this.refs.appFrame.src = '/config.json';
+                            this.refs.appFrame.src = 'about:blank';
                         }
 
                         WidgetUtils.setRoomWidget(

From 2dc335798d0fcfb0212a24e46ed40aa16ad2b3c9 Mon Sep 17 00:00:00 2001
From: "J. Ryan Stinnett" <jryans@gmail.com>
Date: Fri, 26 Oct 2018 14:15:16 +0200
Subject: [PATCH 14/34] Repair DevTools button padding by centralizing styles

This moves the padding styles for dialog content to the .mx_Dialog rule. In
addition, it fixes vector-im/riot-web#7548 where the DevTools buttons had double
padding.

Signed-off-by: J. Ryan Stinnett <jryans@gmail.com>
---
 res/css/_common.scss                            | 6 ++----
 res/css/views/dialogs/_DevtoolsDialog.scss      | 4 ----
 res/css/views/dialogs/_ShareDialog.scss         | 5 -----
 res/css/views/dialogs/_UnknownDeviceDialog.scss | 5 +----
 src/components/views/dialogs/DevtoolsDialog.js  | 4 ++--
 5 files changed, 5 insertions(+), 19 deletions(-)

diff --git a/res/css/_common.scss b/res/css/_common.scss
index bf67edc1c3..9123e5ba8d 100644
--- a/res/css/_common.scss
+++ b/res/css/_common.scss
@@ -170,8 +170,7 @@ textarea {
     font-weight: 300;
     font-size: 15px;
     position: relative;
-    padding-left: 58px;
-    padding-bottom: 36px;
+    padding: 0 58px 36px;
     width: 60%;
     max-width: 704px;
     box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.2);
@@ -216,14 +215,13 @@ textarea {
 }
 
 .mx_Dialog_content {
-    margin: 24px 58px 68px 0;
+    margin: 24px 0 68px;
     font-size: 14px;
     color: $primary-fg-color;
     word-wrap: break-word;
 }
 
 .mx_Dialog_buttons {
-    padding-right: 58px;
     text-align: right;
 }
 
diff --git a/res/css/views/dialogs/_DevtoolsDialog.scss b/res/css/views/dialogs/_DevtoolsDialog.scss
index 3764bb13b3..a4a868bd11 100644
--- a/res/css/views/dialogs/_DevtoolsDialog.scss
+++ b/res/css/views/dialogs/_DevtoolsDialog.scss
@@ -14,10 +14,6 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-.mx_DevTools_dialog {
-    padding-right: 58px;
-}
-
 .mx_DevTools_content {
     margin: 10px 0;
 }
diff --git a/res/css/views/dialogs/_ShareDialog.scss b/res/css/views/dialogs/_ShareDialog.scss
index 116bef8dfd..9a2f67dea3 100644
--- a/res/css/views/dialogs/_ShareDialog.scss
+++ b/res/css/views/dialogs/_ShareDialog.scss
@@ -14,11 +14,6 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-.mx_ShareDialog {
-    // this is to center the content
-    padding-right: 58px;
-}
-
 .mx_ShareDialog hr {
     margin-top: 25px;
     margin-bottom: 25px;
diff --git a/res/css/views/dialogs/_UnknownDeviceDialog.scss b/res/css/views/dialogs/_UnknownDeviceDialog.scss
index 3457e50b92..e3801e3550 100644
--- a/res/css/views/dialogs/_UnknownDeviceDialog.scss
+++ b/res/css/views/dialogs/_UnknownDeviceDialog.scss
@@ -20,9 +20,6 @@ limitations under the License.
     // is a pain in the ass. plus might as well make the dialog big given how
     // important it is.
     height: 100%;
-
-    // position the gemini scrollbar nicely
-    padding-right: 58px;
 }
 
 .mx_UnknownDeviceDialog {
@@ -51,4 +48,4 @@ limitations under the License.
 .mx_UnknownDeviceDialog .mx_UnknownDeviceDialog_deviceList li {
     height: 40px;
     border-bottom: 1px solid $primary-hairline-color;
-}
\ No newline at end of file
+}
diff --git a/src/components/views/dialogs/DevtoolsDialog.js b/src/components/views/dialogs/DevtoolsDialog.js
index 22ee44f81a..ea198461c5 100644
--- a/src/components/views/dialogs/DevtoolsDialog.js
+++ b/src/components/views/dialogs/DevtoolsDialog.js
@@ -625,7 +625,7 @@ export default class DevtoolsDialog extends React.Component {
         let body;
 
         if (this.state.mode) {
-            body = <div className="mx_DevTools_dialog">
+            body = <div>
                 <div className="mx_DevTools_label_left">{ this.state.mode.getLabel() }</div>
                 <div className="mx_DevTools_label_right">Room ID: { this.props.roomId }</div>
                 <div className="mx_DevTools_label_bottom" />
@@ -634,7 +634,7 @@ export default class DevtoolsDialog extends React.Component {
         } else {
             const classes = "mx_DevTools_RoomStateExplorer_button";
             body = <div>
-                <div className="mx_DevTools_dialog">
+                <div>
                     <div className="mx_DevTools_label_left">{ _t('Toolbox') }</div>
                     <div className="mx_DevTools_label_right">Room ID: { this.props.roomId }</div>
                     <div className="mx_DevTools_label_bottom" />

From 156ffd1334fd465ded3c99dbd643264e47a9557d Mon Sep 17 00:00:00 2001
From: David Baker <dave@matrix.org>
Date: Fri, 26 Oct 2018 17:01:44 +0100
Subject: [PATCH 15/34] Fix autoreplacement of ascii emoji

More slate API updates in code paths I didn't test

Fixes https://github.com/vector-im/riot-web/issues/7509
---
 src/components/views/rooms/MessageComposerInput.js | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js
index 570cb8a59b..6f56d35105 100644
--- a/src/components/views/rooms/MessageComposerInput.js
+++ b/src/components/views/rooms/MessageComposerInput.js
@@ -544,7 +544,7 @@ export default class MessageComposerInput extends React.Component {
 
         if (editorState.startText !== null) {
             const text = editorState.startText.text;
-            const currentStartOffset = editorState.startOffset;
+            const currentStartOffset = editorState.selection.start.offset;
 
             // Automatic replacement of plaintext emoji to Unicode emoji
             if (SettingsStore.getValue('MessageComposerInput.autoReplaceEmoji')) {
@@ -558,11 +558,11 @@ export default class MessageComposerInput extends React.Component {
 
                     const range = Range.create({
                         anchor: {
-                            key: editorState.selection.startKey,
+                            key: editorState.startText.key,
                             offset: currentStartOffset - emojiMatch[1].length - 1,
                         },
                         focus: {
-                            key: editorState.selection.startKey,
+                            key: editorState.startText.key,
                             offset: currentStartOffset - 1,
                         },
                     });

From ef8c9246aaef1bd1308fc58974c1a70758a8d82f Mon Sep 17 00:00:00 2001
From: Travis Ralston <travpc@gmail.com>
Date: Fri, 26 Oct 2018 10:07:21 -0600
Subject: [PATCH 16/34] Maybe fix UserSettings?

---
 src/components/structures/UserSettings.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js
index f32026511b..772eb70dde 100644
--- a/src/components/structures/UserSettings.js
+++ b/src/components/structures/UserSettings.js
@@ -1298,7 +1298,7 @@ module.exports = React.createClass({
         // If the olmVersion is not defined then either crypto is disabled, or
         // we are using a version old version of olm. We assume the former.
         let olmVersionString = "<not-enabled>";
-        if (olmVersion !== undefined) {
+        if (olmVersion) {
             olmVersionString = `${olmVersion[0]}.${olmVersion[1]}.${olmVersion[2]}`;
         }
 

From c389540522067978298eaf487596ee0fd30948a2 Mon Sep 17 00:00:00 2001
From: Travis Ralston <travpc@gmail.com>
Date: Fri, 26 Oct 2018 10:22:18 -0600
Subject: [PATCH 17/34] Appease the linter

---
 src/matrix-to.js       |  6 ++---
 test/matrix-to-test.js | 56 +++++++++++++++++++++---------------------
 2 files changed, 31 insertions(+), 31 deletions(-)

diff --git a/src/matrix-to.js b/src/matrix-to.js
index 881972410f..e513cbb14c 100644
--- a/src/matrix-to.js
+++ b/src/matrix-to.js
@@ -39,7 +39,7 @@ export function makeGroupPermalink(groupId) {
 
 export function encodeServerCandidates(candidates) {
     if (!candidates) return '';
-    return `via=${candidates.map(c => encodeURIComponent(c)).join("&via=")}`
+    return `via=${candidates.map(c => encodeURIComponent(c)).join("&via=")}`;
 }
 
 export function pickServerCandidates(roomId) {
@@ -85,7 +85,7 @@ export function pickServerCandidates(roomId) {
     // the list and magically have the link work.
 
     const populationMap: {[server:string]:number} = {};
-    const highestPlUser = {userId:null, powerLevel: 0, serverName: null};
+    const highestPlUser = {userId: null, powerLevel: 0, serverName: null};
 
     for (const member of room.getJoinedMembers()) {
         const serverName = member.userId.split(":").splice(1).join(":");
@@ -114,4 +114,4 @@ export function pickServerCandidates(roomId) {
     }
 
     return candidates;
-}
\ No newline at end of file
+}
diff --git a/test/matrix-to-test.js b/test/matrix-to-test.js
index dbe7fa55ff..d199588b9a 100644
--- a/test/matrix-to-test.js
+++ b/test/matrix-to-test.js
@@ -17,7 +17,7 @@ import {pickServerCandidates} from "../src/matrix-to";
 import * as testUtils from "./test-utils";
 
 
-describe('matrix-to', function () {
+describe('matrix-to', function() {
     let sandbox;
 
     beforeEach(function() {
@@ -30,25 +30,25 @@ describe('matrix-to', function () {
         sandbox.restore();
     });
 
-    it('should pick no candidate servers when the room is not found', function () {
+    it('should pick no candidate servers when the room is not found', function() {
         peg.get().getRoom = () => null;
         const pickedServers = pickServerCandidates("!somewhere:example.org");
         expect(pickedServers).toExist();
         expect(pickedServers.length).toBe(0);
     });
 
-    it('should pick no candidate servers when the room has no members', function () {
+    it('should pick no candidate servers when the room has no members', function() {
         peg.get().getRoom = () => {
             return {
                 getJoinedMembers: () => [],
-            }
+            };
         };
         const pickedServers = pickServerCandidates("!somewhere:example.org");
         expect(pickedServers).toExist();
         expect(pickedServers.length).toBe(0);
     });
 
-    it('should pick a candidate server for the highest power level user in the room', function () {
+    it('should pick a candidate server for the highest power level user in the room', function() {
         peg.get().getRoom = () => {
             return {
                 getJoinedMembers: () => [
@@ -63,9 +63,9 @@ describe('matrix-to', function () {
                     {
                         userId: "@alice:pl_95",
                         powerLevel: 95,
-                    }
+                    },
                 ],
-            }
+            };
         };
         const pickedServers = pickServerCandidates("!somewhere:example.org");
         expect(pickedServers).toExist();
@@ -74,7 +74,7 @@ describe('matrix-to', function () {
         // we don't check the 2nd and 3rd servers because that is done by the next test
     });
 
-    it('should pick candidate servers based on user population', function () {
+    it('should pick candidate servers based on user population', function() {
         peg.get().getRoom = () => {
             return {
                 getJoinedMembers: () => [
@@ -101,9 +101,9 @@ describe('matrix-to', function () {
                     {
                         userId: "@charlie:third",
                         powerLevel: 0,
-                    }
+                    },
                 ],
-            }
+            };
         };
         const pickedServers = pickServerCandidates("!somewhere:example.org");
         expect(pickedServers).toExist();
@@ -113,7 +113,7 @@ describe('matrix-to', function () {
         expect(pickedServers[2]).toBe("third");
     });
 
-    it('should pick prefer candidate servers with higher power levels', function () {
+    it('should pick prefer candidate servers with higher power levels', function() {
         peg.get().getRoom = () => {
             return {
                 getJoinedMembers: () => [
@@ -132,9 +132,9 @@ describe('matrix-to', function () {
                     {
                         userId: "@charlie:third",
                         powerLevel: 0,
-                    }
+                    },
                 ],
-            }
+            };
         };
         const pickedServers = pickServerCandidates("!somewhere:example.org");
         expect(pickedServers).toExist();
@@ -144,16 +144,16 @@ describe('matrix-to', function () {
         expect(pickedServers[2]).toBe("third");
     });
 
-    it('should work with IPv4 hostnames', function () {
+    it('should work with IPv4 hostnames', function() {
         peg.get().getRoom = () => {
             return {
                 getJoinedMembers: () => [
                     {
                         userId: "@alice:127.0.0.1",
                         powerLevel: 100,
-                    }
+                    },
                 ],
-            }
+            };
         };
         const pickedServers = pickServerCandidates("!somewhere:example.org");
         expect(pickedServers).toExist();
@@ -161,16 +161,16 @@ describe('matrix-to', function () {
         expect(pickedServers[0]).toBe("127.0.0.1");
     });
 
-    it('should work with IPv6 hostnames', function () {
+    it('should work with IPv6 hostnames', function() {
         peg.get().getRoom = () => {
             return {
                 getJoinedMembers: () => [
                     {
                         userId: "@alice:[::1]",
                         powerLevel: 100,
-                    }
+                    },
                 ],
-            }
+            };
         };
         const pickedServers = pickServerCandidates("!somewhere:example.org");
         expect(pickedServers).toExist();
@@ -178,16 +178,16 @@ describe('matrix-to', function () {
         expect(pickedServers[0]).toBe("[::1]");
     });
 
-    it('should work with IPv4 hostnames with ports', function () {
+    it('should work with IPv4 hostnames with ports', function() {
         peg.get().getRoom = () => {
             return {
                 getJoinedMembers: () => [
                     {
                         userId: "@alice:127.0.0.1:8448",
                         powerLevel: 100,
-                    }
+                    },
                 ],
-            }
+            };
         };
         const pickedServers = pickServerCandidates("!somewhere:example.org");
         expect(pickedServers).toExist();
@@ -195,16 +195,16 @@ describe('matrix-to', function () {
         expect(pickedServers[0]).toBe("127.0.0.1:8448");
     });
 
-    it('should work with IPv6 hostnames with ports', function () {
+    it('should work with IPv6 hostnames with ports', function() {
         peg.get().getRoom = () => {
             return {
                 getJoinedMembers: () => [
                     {
                         userId: "@alice:[::1]:8448",
                         powerLevel: 100,
-                    }
+                    },
                 ],
-            }
+            };
         };
         const pickedServers = pickServerCandidates("!somewhere:example.org");
         expect(pickedServers).toExist();
@@ -212,16 +212,16 @@ describe('matrix-to', function () {
         expect(pickedServers[0]).toBe("[::1]:8448");
     });
 
-    it('should work with hostnames with ports', function () {
+    it('should work with hostnames with ports', function() {
         peg.get().getRoom = () => {
             return {
                 getJoinedMembers: () => [
                     {
                         userId: "@alice:example.org:8448",
                         powerLevel: 100,
-                    }
+                    },
                 ],
-            }
+            };
         };
         const pickedServers = pickServerCandidates("!somewhere:example.org");
         expect(pickedServers).toExist();

From d802ee0fa2ce0a68986f848b9924ac352ededf54 Mon Sep 17 00:00:00 2001
From: Travis Ralston <travpc@gmail.com>
Date: Fri, 26 Oct 2018 10:25:23 -0600
Subject: [PATCH 18/34] Move the max candidates constant out of the function

---
 src/matrix-to.js | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/src/matrix-to.js b/src/matrix-to.js
index e513cbb14c..adcbdf8111 100644
--- a/src/matrix-to.js
+++ b/src/matrix-to.js
@@ -19,6 +19,10 @@ import MatrixClientPeg from "./MatrixClientPeg";
 export const host = "matrix.to";
 export const baseUrl = `https://${host}`;
 
+// The maximum number of servers to pick when working out which servers
+// to add to permalinks. The servers are appended as ?via=example.org
+const MAX_SERVER_CANDIDATES = 3;
+
 export function makeEventPermalink(roomId, eventId) {
     const serverCandidates = pickServerCandidates(roomId);
     return `${baseUrl}/#/${roomId}/${eventId}?${encodeServerCandidates(serverCandidates)}`;
@@ -103,11 +107,10 @@ export function pickServerCandidates(roomId) {
     if (highestPlUser.powerLevel >= 50) candidates.push(highestPlUser.serverName);
 
     const beforePopulation = candidates.length;
-    const maxCandidates = 3;
     const serversByPopulation = Object.keys(populationMap)
         .sort((a, b) => populationMap[b] - populationMap[a])
         .filter(a => !candidates.includes(a));
-    for (let i = beforePopulation; i <= maxCandidates; i++) {
+    for (let i = beforePopulation; i <= MAX_SERVER_CANDIDATES; i++) {
         const idx = i - beforePopulation;
         if (idx >= serversByPopulation.length) break;
         candidates.push(serversByPopulation[idx]);

From 3734c8ab9169d861b9935d254b13838dfc9f3f4a Mon Sep 17 00:00:00 2001
From: Travis Ralston <travpc@gmail.com>
Date: Fri, 26 Oct 2018 12:21:02 -0600
Subject: [PATCH 19/34] Don't mention that matrix.org is likely to be popular

This is untrue for quite a few real scenarios, including private federations and a ton of rooms.
---
 src/matrix-to.js | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/src/matrix-to.js b/src/matrix-to.js
index adcbdf8111..62ad7c3e6f 100644
--- a/src/matrix-to.js
+++ b/src/matrix-to.js
@@ -68,10 +68,8 @@ export function pickServerCandidates(roomId) {
     //   room.
     //
     //   Server 2: The next most popular server in the room (in user
-    //   distribution). This will probably be matrix.org in most cases
-    //   although it is certainly possible to be some other server. This
-    //   cannot be the same as Server 1. If no other servers are available
-    //   then we'll only return Server 1.
+    //   distribution). This cannot be the same as Server 1. If no other
+    //   servers are available then we'll only return Server 1.
     //
     //   Server 3: The next most popular server by user distribution. This
     //   has the same rules as Server 2, with the added exception that it

From 0857e2c5e9f88657b4f792507233721613fc51d5 Mon Sep 17 00:00:00 2001
From: Travis Ralston <travpc@gmail.com>
Date: Fri, 26 Oct 2018 12:21:27 -0600
Subject: [PATCH 20/34] Clarify why we pick popular servers over the one that
 created the room

---
 src/matrix-to.js | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/src/matrix-to.js b/src/matrix-to.js
index 62ad7c3e6f..944446c4cc 100644
--- a/src/matrix-to.js
+++ b/src/matrix-to.js
@@ -80,6 +80,12 @@ export function pickServerCandidates(roomId) {
     // be ACL'd in the future or for some reason be evicted from the room
     // however an event like that is unlikely the larger the room gets.
 
+    // Note: we don't pick the server the room was created on because the
+    // homeserver should already be using that server as a last ditch attempt
+    // and there's less of a guarantee that the server is a resident server.
+    // Instead, we actively figure out which servers are likely to be residents
+    // in the future and try to use those.
+
     // Note: Users receiving permalinks that happen to have all 3 potential
     // servers fail them (in terms of joining) are somewhat expected to hunt
     // down the person who gave them the link to ask for a participating server.

From 3bc5e2beb3f57cce1170e2e9813bf9fe92a85d92 Mon Sep 17 00:00:00 2001
From: Travis Ralston <travpc@gmail.com>
Date: Fri, 26 Oct 2018 19:47:53 -0600
Subject: [PATCH 21/34] Fix and test matrix.to alias permalinks

Fixes https://github.com/vector-im/riot-web/issues/7614
Regression of https://github.com/matrix-org/matrix-react-sdk/pull/2250
---
 src/matrix-to.js       |  17 ++++--
 test/matrix-to-test.js | 120 ++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 133 insertions(+), 4 deletions(-)

diff --git a/src/matrix-to.js b/src/matrix-to.js
index 944446c4cc..c0b57e725d 100644
--- a/src/matrix-to.js
+++ b/src/matrix-to.js
@@ -24,8 +24,13 @@ export const baseUrl = `https://${host}`;
 const MAX_SERVER_CANDIDATES = 3;
 
 export function makeEventPermalink(roomId, eventId) {
+    const permalinkBase = `${baseUrl}/#/${roomId}/${eventId}`;
+
+    // If the roomId isn't actually a room ID, don't try to list the servers.
+    // Aliases are already routable, and don't need extra information.
+    if (roomId[0] !== '!') return permalinkBase;
     const serverCandidates = pickServerCandidates(roomId);
-    return `${baseUrl}/#/${roomId}/${eventId}?${encodeServerCandidates(serverCandidates)}`;
+    return `${permalinkBase}${encodeServerCandidates(serverCandidates)}`;
 }
 
 export function makeUserPermalink(userId) {
@@ -33,8 +38,14 @@ export function makeUserPermalink(userId) {
 }
 
 export function makeRoomPermalink(roomId) {
+    const permalinkBase = `${baseUrl}/#/${roomId}`;
+
+    // If the roomId isn't actually a room ID, don't try to list the servers.
+    // Aliases are already routable, and don't need extra information.
+    if (roomId[0] !== '!') return permalinkBase;
+
     const serverCandidates = pickServerCandidates(roomId);
-    return `${baseUrl}/#/${roomId}?${encodeServerCandidates(serverCandidates)}`;
+    return `${permalinkBase}${encodeServerCandidates(serverCandidates)}`;
 }
 
 export function makeGroupPermalink(groupId) {
@@ -43,7 +54,7 @@ export function makeGroupPermalink(groupId) {
 
 export function encodeServerCandidates(candidates) {
     if (!candidates) return '';
-    return `via=${candidates.map(c => encodeURIComponent(c)).join("&via=")}`;
+    return `?via=${candidates.map(c => encodeURIComponent(c)).join("&via=")}`;
 }
 
 export function pickServerCandidates(roomId) {
diff --git a/test/matrix-to-test.js b/test/matrix-to-test.js
index d199588b9a..c581880158 100644
--- a/test/matrix-to-test.js
+++ b/test/matrix-to-test.js
@@ -13,7 +13,13 @@ limitations under the License.
 
 import expect from 'expect';
 import peg from '../src/MatrixClientPeg';
-import {pickServerCandidates} from "../src/matrix-to";
+import {
+    makeEventPermalink,
+    makeGroupPermalink,
+    makeRoomPermalink,
+    makeUserPermalink,
+    pickServerCandidates
+} from "../src/matrix-to";
 import * as testUtils from "./test-utils";
 
 
@@ -228,4 +234,116 @@ describe('matrix-to', function() {
         expect(pickedServers.length).toBe(1);
         expect(pickedServers[0]).toBe("example.org:8448");
     });
+
+    it('should generate an event permalink for room IDs with no candidate servers', function() {
+        peg.get().getRoom = () => null;
+        const result = makeEventPermalink("!somewhere:example.org", "$something:example.com");
+        expect(result).toBe("https://matrix.to/#/!somewhere:example.org/$something:example.com");
+    });
+
+    it('should generate an event permalink for room IDs with some candidate servers', function() {
+        peg.get().getRoom = () => {
+            return {
+                getJoinedMembers: () => [
+                    {
+                        userId: "@alice:first",
+                        powerLevel: 100,
+                    },
+                    {
+                        userId: "@bob:second",
+                        powerLevel: 0,
+                    },
+                ],
+            };
+        };
+        const result = makeEventPermalink("!somewhere:example.org", "$something:example.com");
+        expect(result).toBe("https://matrix.to/#/!somewhere:example.org/$something:example.com?via=first&via=second");
+    });
+
+    it('should generate a room permalink for room IDs with no candidate servers', function() {
+        peg.get().getRoom = () => null;
+        const result = makeRoomPermalink("!somewhere:example.org");
+        expect(result).toBe("https://matrix.to/#/!somewhere:example.org");
+    });
+
+    it('should generate a room permalink for room IDs with some candidate servers', function() {
+        peg.get().getRoom = () => {
+            return {
+                getJoinedMembers: () => [
+                    {
+                        userId: "@alice:first",
+                        powerLevel: 100,
+                    },
+                    {
+                        userId: "@bob:second",
+                        powerLevel: 0,
+                    },
+                ],
+            };
+        };
+        const result = makeRoomPermalink("!somewhere:example.org");
+        expect(result).toBe("https://matrix.to/#/!somewhere:example.org?via=first&via=second");
+    });
+
+    // Technically disallowed but we'll test it anyways
+    it('should generate an event permalink for room aliases with no candidate servers', function() {
+        peg.get().getRoom = () => null;
+        const result = makeEventPermalink("#somewhere:example.org", "$something:example.com");
+        expect(result).toBe("https://matrix.to/#/#somewhere:example.org/$something:example.com");
+    });
+
+    // Technically disallowed but we'll test it anyways
+    it('should generate an event permalink for room aliases without candidate servers even when some are available', function() {
+        peg.get().getRoom = () => {
+            return {
+                getJoinedMembers: () => [
+                    {
+                        userId: "@alice:first",
+                        powerLevel: 100,
+                    },
+                    {
+                        userId: "@bob:second",
+                        powerLevel: 0,
+                    },
+                ],
+            };
+        };
+        const result = makeEventPermalink("#somewhere:example.org", "$something:example.com");
+        expect(result).toBe("https://matrix.to/#/#somewhere:example.org/$something:example.com");
+    });
+
+    it('should generate a room permalink for room aliases with no candidate servers', function() {
+        peg.get().getRoom = () => null;
+        const result = makeRoomPermalink("#somewhere:example.org");
+        expect(result).toBe("https://matrix.to/#/#somewhere:example.org");
+    });
+
+    it('should generate a room permalink for room aliases without candidate servers even when some are available', function() {
+        peg.get().getRoom = () => {
+            return {
+                getJoinedMembers: () => [
+                    {
+                        userId: "@alice:first",
+                        powerLevel: 100,
+                    },
+                    {
+                        userId: "@bob:second",
+                        powerLevel: 0,
+                    },
+                ],
+            };
+        };
+        const result = makeRoomPermalink("#somewhere:example.org");
+        expect(result).toBe("https://matrix.to/#/#somewhere:example.org");
+    });
+
+    it('should generate a user permalink', function() {
+        const result = makeUserPermalink("@someone:example.org");
+        expect(result).toBe("https://matrix.to/#/@someone:example.org");
+    });
+
+    it('should generate a group permalink', function() {
+        const result = makeGroupPermalink("+community:example.org");
+        expect(result).toBe("https://matrix.to/#/+community:example.org");
+    });
 });

From 5b22d157a7871440b4fcc593313aada12b852faa Mon Sep 17 00:00:00 2001
From: Travis Ralston <travpc@gmail.com>
Date: Fri, 26 Oct 2018 20:34:06 -0600
Subject: [PATCH 22/34] Fix candidate server encoding

---
 src/matrix-to.js | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/matrix-to.js b/src/matrix-to.js
index c0b57e725d..b5827f671a 100644
--- a/src/matrix-to.js
+++ b/src/matrix-to.js
@@ -29,6 +29,7 @@ export function makeEventPermalink(roomId, eventId) {
     // If the roomId isn't actually a room ID, don't try to list the servers.
     // Aliases are already routable, and don't need extra information.
     if (roomId[0] !== '!') return permalinkBase;
+
     const serverCandidates = pickServerCandidates(roomId);
     return `${permalinkBase}${encodeServerCandidates(serverCandidates)}`;
 }
@@ -53,7 +54,7 @@ export function makeGroupPermalink(groupId) {
 }
 
 export function encodeServerCandidates(candidates) {
-    if (!candidates) return '';
+    if (!candidates || candidates.length === 0) return '';
     return `?via=${candidates.map(c => encodeURIComponent(c)).join("&via=")}`;
 }
 

From 0cdc44a2054e424fa17b7e01e1c545736c145d89 Mon Sep 17 00:00:00 2001
From: Travis Ralston <travpc@gmail.com>
Date: Fri, 26 Oct 2018 20:49:01 -0600
Subject: [PATCH 23/34] Appease the linter

---
 test/matrix-to-test.js | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/test/matrix-to-test.js b/test/matrix-to-test.js
index c581880158..70533575c4 100644
--- a/test/matrix-to-test.js
+++ b/test/matrix-to-test.js
@@ -18,7 +18,7 @@ import {
     makeGroupPermalink,
     makeRoomPermalink,
     makeUserPermalink,
-    pickServerCandidates
+    pickServerCandidates,
 } from "../src/matrix-to";
 import * as testUtils from "./test-utils";
 
@@ -293,7 +293,7 @@ describe('matrix-to', function() {
     });
 
     // Technically disallowed but we'll test it anyways
-    it('should generate an event permalink for room aliases without candidate servers even when some are available', function() {
+    it('should generate an event permalink for room aliases without candidate servers', function() {
         peg.get().getRoom = () => {
             return {
                 getJoinedMembers: () => [
@@ -318,7 +318,7 @@ describe('matrix-to', function() {
         expect(result).toBe("https://matrix.to/#/#somewhere:example.org");
     });
 
-    it('should generate a room permalink for room aliases without candidate servers even when some are available', function() {
+    it('should generate a room permalink for room aliases without candidate servers', function() {
         peg.get().getRoom = () => {
             return {
                 getJoinedMembers: () => [

From 790ecbcbb09e28376301b5085e72392d8e040356 Mon Sep 17 00:00:00 2001
From: Aaron Raimist <aaron@raim.ist>
Date: Fri, 26 Oct 2018 22:07:10 -0500
Subject: [PATCH 24/34] Update babel-eslint to 8.1.1

Signed-off-by: Aaron Raimist <aaron@raim.ist>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 03311a50e3..7934cc2bcb 100644
--- a/package.json
+++ b/package.json
@@ -101,7 +101,7 @@
   "devDependencies": {
     "babel-cli": "^6.26.0",
     "babel-core": "^6.26.3",
-    "babel-eslint": "^6.1.2",
+    "babel-eslint": "^8.1.1",
     "babel-loader": "^7.1.5",
     "babel-plugin-add-module-exports": "^0.2.1",
     "babel-plugin-transform-async-to-bluebird": "^1.1.1",

From 952bdba9791c4c7bd5680c845104dc4f7f2fb96f Mon Sep 17 00:00:00 2001
From: Aaron Raimist <aaron@raim.ist>
Date: Fri, 26 Oct 2018 22:48:11 -0500
Subject: [PATCH 25/34] Update more eslint related packages

Signed-off-by: Aaron Raimist <aaron@raim.ist>
---
 package.json | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/package.json b/package.json
index 7934cc2bcb..8cd612f84b 100644
--- a/package.json
+++ b/package.json
@@ -101,7 +101,7 @@
   "devDependencies": {
     "babel-cli": "^6.26.0",
     "babel-core": "^6.26.3",
-    "babel-eslint": "^8.1.1",
+    "babel-eslint": "^10.0.1",
     "babel-loader": "^7.1.5",
     "babel-plugin-add-module-exports": "^0.2.1",
     "babel-plugin-transform-async-to-bluebird": "^1.1.1",
@@ -115,9 +115,9 @@
     "babel-preset-react": "^6.24.1",
     "chokidar": "^1.6.1",
     "concurrently": "^4.0.1",
-    "eslint": "^3.13.1",
+    "eslint": "^5.8.0",
     "eslint-config-google": "^0.7.1",
-    "eslint-plugin-babel": "^4.1.2",
+    "eslint-plugin-babel": "^5.2.1",
     "eslint-plugin-flowtype": "^2.30.0",
     "eslint-plugin-react": "^7.7.0",
     "estree-walker": "^0.5.0",

From 49ce4ef117b854eff3ab26ae30a1b336a250532d Mon Sep 17 00:00:00 2001
From: Aaron Raimist <aaron@raim.ist>
Date: Fri, 26 Oct 2018 22:50:35 -0500
Subject: [PATCH 26/34] eslint --fix src/

Signed-off-by: Aaron Raimist <aaron@raim.ist>
---
 .eslintignore.errorfiles                      | 10 ---
 src/ComposerHistoryManager.js                 |  1 -
 src/Entities.js                               |  1 -
 src/HtmlUtils.js                              |  2 +-
 src/PasswordReset.js                          |  1 -
 src/Presence.js                               |  1 -
 src/Rooms.js                                  |  3 +-
 src/ScalarAuthClient.js                       |  1 -
 src/SdkConfig.js                              |  1 -
 src/UserActivity.js                           |  1 -
 src/UserSettingsStore.js                      |  2 +-
 src/VelocityBounce.js                         |  6 +-
 src/autocomplete/Autocompleter.js             |  2 +-
 src/autocomplete/CommunityProvider.js         |  2 +-
 src/autocomplete/PlainWithPillsSerializer.js  |  1 -
 src/components/structures/BottomLeftMenu.js   | 12 +--
 .../structures/CompatibilityPage.js           |  9 +-
 src/components/structures/CreateRoom.js       |  8 +-
 src/components/structures/GroupView.js        |  2 +-
 src/components/structures/HomePage.js         | 14 ++-
 src/components/structures/LeftPanel.js        |  6 +-
 src/components/structures/LoggedInView.js     |  6 +-
 src/components/structures/LoginBox.js         |  2 +-
 src/components/structures/RoomDirectory.js    | 86 +++++++++----------
 src/components/structures/RoomStatusBar.js    |  4 +-
 src/components/structures/RoomView.js         |  6 +-
 src/components/structures/SearchBox.js        | 30 +++----
 src/components/structures/TimelinePanel.js    |  2 +-
 src/components/structures/UserSettings.js     |  2 +-
 src/components/structures/ViewSource.js       |  2 +-
 src/components/views/avatars/MemberAvatar.js  |  2 +-
 .../GroupInviteTileContextMenu.js             |  2 +-
 .../views/dialogs/ChangelogDialog.js          | 10 +--
 .../views/dialogs/ChatCreateOrReuseDialog.js  |  1 -
 .../views/directory/NetworkDropdown.js        | 10 +--
 .../views/elements/DeviceVerifyButtons.js     |  2 +-
 .../views/elements/EditableTextContainer.js   |  1 -
 src/components/views/elements/ImageView.js    | 52 ++++++-----
 .../views/elements/InlineSpinner.js           | 10 +--
 .../views/elements/PersistedElement.js        |  1 -
 src/components/views/elements/Spinner.js      | 12 +--
 .../views/elements/TintableSvgButton.js       |  1 -
 src/components/views/globals/MatrixToolbar.js |  2 +-
 src/components/views/globals/NewVersionBar.js | 10 +--
 .../views/globals/UpdateCheckBar.js           | 10 +--
 .../login/InteractiveAuthEntryComponents.js   |  4 +-
 .../views/room_settings/AliasSettings.js      |  4 +-
 src/components/views/rooms/Autocomplete.js    |  1 -
 .../views/rooms/LinkPreviewWidget.js          |  2 +-
 src/components/views/rooms/MemberInfo.js      |  2 +-
 src/components/views/rooms/MessageComposer.js |  4 +-
 .../views/rooms/MessageComposerInput.js       | 16 ++--
 src/components/views/rooms/RoomPreviewBar.js  |  2 +-
 src/components/views/rooms/RoomSettings.js    | 14 +--
 .../views/settings/Notifications.js           | 14 +--
 src/notifications/StandardActions.js          |  4 +-
 src/settings/SettingsStore.js                 |  2 +-
 src/settings/controllers/SettingController.js |  1 -
 src/stores/RoomListStore.js                   |  1 -
 src/utils/DMRoomMap.js                        |  2 +-
 src/utils/DecryptFile.js                      |  2 +-
 61 files changed, 197 insertions(+), 230 deletions(-)

diff --git a/.eslintignore.errorfiles b/.eslintignore.errorfiles
index 8d0821207a..0b4266c0b5 100644
--- a/.eslintignore.errorfiles
+++ b/.eslintignore.errorfiles
@@ -7,11 +7,8 @@ src/component-index.js
 src/components/structures/BottomLeftMenu.js
 src/components/structures/CompatibilityPage.js
 src/components/structures/CreateRoom.js
-src/components/structures/HomePage.js
-src/components/structures/LeftPanel.js
 src/components/structures/LoggedInView.js
 src/components/structures/login/ForgotPassword.js
-src/components/structures/LoginBox.js
 src/components/structures/MessagePanel.js
 src/components/structures/NotificationPanel.js
 src/components/structures/RoomDirectory.js
@@ -22,22 +19,17 @@ src/components/structures/SearchBox.js
 src/components/structures/TimelinePanel.js
 src/components/structures/UploadBar.js
 src/components/structures/UserSettings.js
-src/components/structures/ViewSource.js
 src/components/views/avatars/BaseAvatar.js
 src/components/views/avatars/MemberAvatar.js
 src/components/views/create_room/RoomAlias.js
-src/components/views/dialogs/ChangelogDialog.js
 src/components/views/dialogs/DeactivateAccountDialog.js
 src/components/views/dialogs/SetPasswordDialog.js
 src/components/views/dialogs/UnknownDeviceDialog.js
 src/components/views/directory/NetworkDropdown.js
 src/components/views/elements/AddressSelector.js
-src/components/views/elements/DeviceVerifyButtons.js
 src/components/views/elements/DirectorySearchBox.js
 src/components/views/elements/ImageView.js
-src/components/views/elements/InlineSpinner.js
 src/components/views/elements/MemberEventListSummary.js
-src/components/views/elements/Spinner.js
 src/components/views/elements/TintableSvg.js
 src/components/views/elements/UserSelector.js
 src/components/views/globals/MatrixToolbar.js
@@ -90,7 +82,6 @@ src/MatrixClientPeg.js
 src/Modal.js
 src/notifications/ContentRules.js
 src/notifications/PushRuleVectorState.js
-src/notifications/StandardActions.js
 src/notifications/VectorPushRulesDefinitions.js
 src/Notifier.js
 src/PlatformPeg.js
@@ -111,7 +102,6 @@ src/utils/MultiInviter.js
 src/utils/Receipt.js
 src/VectorConferenceHandler.js
 src/Velociraptor.js
-src/VelocityBounce.js
 src/WhoIsTyping.js
 src/wrappers/withMatrixClient.js
 test/components/structures/login/Registration-test.js
diff --git a/src/ComposerHistoryManager.js b/src/ComposerHistoryManager.js
index 0164e6c4cd..ecf773f2e7 100644
--- a/src/ComposerHistoryManager.js
+++ b/src/ComposerHistoryManager.js
@@ -22,7 +22,6 @@ import _clamp from 'lodash/clamp';
 type MessageFormat = 'rich' | 'markdown';
 
 class HistoryItem {
-
     // We store history items in their native format to ensure history is accurate
     // and then convert them if our RTE has subsequently changed format.
     value: Value;
diff --git a/src/Entities.js b/src/Entities.js
index 21abd9c473..8be1da0db8 100644
--- a/src/Entities.js
+++ b/src/Entities.js
@@ -78,7 +78,6 @@ class MemberEntity extends Entity {
 }
 
 class UserEntity extends Entity {
-
     constructor(model, showInviteButton, inviteFn) {
         super(model);
         this.showInviteButton = Boolean(showInviteButton);
diff --git a/src/HtmlUtils.js b/src/HtmlUtils.js
index b6a2bd0acb..e72c0bfe4b 100644
--- a/src/HtmlUtils.js
+++ b/src/HtmlUtils.js
@@ -64,7 +64,7 @@ export function containsEmoji(str) {
  * because we want to include emoji shortnames in title text
  */
 function unicodeToImage(str) {
-    let replaceWith, unicode, alt, short, fname;
+    let replaceWith; let unicode; let alt; let short; let fname;
     const mappedUnicode = emojione.mapUnicodeToShort();
 
     str = str.replace(emojione.regUnicode, function(unicodeChar) {
diff --git a/src/PasswordReset.js b/src/PasswordReset.js
index 71fc4f6b31..df51e4d846 100644
--- a/src/PasswordReset.js
+++ b/src/PasswordReset.js
@@ -25,7 +25,6 @@ import { _t } from './languageHandler';
  * API on the homeserver in question with the new password.
  */
 class PasswordReset {
-
     /**
      * Configure the endpoints for password resetting.
      * @param {string} homeserverUrl The URL to the HS which has the account to reset.
diff --git a/src/Presence.js b/src/Presence.js
index 9367fe35cd..b1e85e4bc7 100644
--- a/src/Presence.js
+++ b/src/Presence.js
@@ -23,7 +23,6 @@ const UNAVAILABLE_TIME_MS = 3 * 60 * 1000; // 3 mins
 const PRESENCE_STATES = ["online", "offline", "unavailable"];
 
 class Presence {
-
     /**
      * Start listening the user activity to evaluate his presence state.
      * Any state change will be sent to the Home Server.
diff --git a/src/Rooms.js b/src/Rooms.js
index e24b8316b3..6f73ea0659 100644
--- a/src/Rooms.js
+++ b/src/Rooms.js
@@ -32,7 +32,6 @@ export function getDisplayAliasForRoom(room) {
  * return the other one. Otherwise, return null.
  */
 export function getOnlyOtherMember(room, myUserId) {
-
     if (room.currentState.getJoinedMemberCount() === 2) {
         return room.getJoinedMembers().filter(function(m) {
             return m.userId !== myUserId;
@@ -103,7 +102,7 @@ export function guessAndSetDMRoom(room, isDirect) {
     let newTarget;
     if (isDirect) {
         const guessedUserId = guessDMRoomTargetId(
-            room, MatrixClientPeg.get().getUserId()
+            room, MatrixClientPeg.get().getUserId(),
         );
         newTarget = guessedUserId;
     } else {
diff --git a/src/ScalarAuthClient.js b/src/ScalarAuthClient.js
index 2038430576..0639e7ceae 100644
--- a/src/ScalarAuthClient.js
+++ b/src/ScalarAuthClient.js
@@ -22,7 +22,6 @@ const SdkConfig = require('./SdkConfig');
 const MatrixClientPeg = require('./MatrixClientPeg');
 
 class ScalarAuthClient {
-
     constructor() {
         this.scalarToken = null;
     }
diff --git a/src/SdkConfig.js b/src/SdkConfig.js
index 8df725a913..65982bd6f2 100644
--- a/src/SdkConfig.js
+++ b/src/SdkConfig.js
@@ -24,7 +24,6 @@ const DEFAULTS = {
 };
 
 class SdkConfig {
-
     static get() {
         return global.mxReactSdkConfig || {};
     }
diff --git a/src/UserActivity.js b/src/UserActivity.js
index b6fae38ed5..c628ab4186 100644
--- a/src/UserActivity.js
+++ b/src/UserActivity.js
@@ -25,7 +25,6 @@ const CURRENTLY_ACTIVE_THRESHOLD_MS = 2000;
  * with the app (but at a much lower frequency than mouse move events)
  */
 class UserActivity {
-
     /**
      * Start listening to user activity
      */
diff --git a/src/UserSettingsStore.js b/src/UserSettingsStore.js
index 5d2af3715f..b40d0529a2 100644
--- a/src/UserSettingsStore.js
+++ b/src/UserSettingsStore.js
@@ -87,7 +87,7 @@ export default {
             device_display_name: address,
             lang: navigator.language,
             data: data,
-            append: true,  // We always append for email pushers since we don't want to stop other accounts notifying to the same email address
+            append: true, // We always append for email pushers since we don't want to stop other accounts notifying to the same email address
         });
     },
 };
diff --git a/src/VelocityBounce.js b/src/VelocityBounce.js
index 2141b05325..732550cfcb 100644
--- a/src/VelocityBounce.js
+++ b/src/VelocityBounce.js
@@ -3,8 +3,10 @@ const Velocity = require('velocity-vector');
 // courtesy of https://github.com/julianshapiro/velocity/issues/283
 // We only use easeOutBounce (easeInBounce is just sort of nonsensical)
 function bounce( p ) {
-    let pow2,
-        bounce = 4;
+    let pow2;
+
+
+let bounce = 4;
 
     while ( p < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) {
         // just sets pow2
diff --git a/src/autocomplete/Autocompleter.js b/src/autocomplete/Autocompleter.js
index 7f91676cc3..e7b89fe576 100644
--- a/src/autocomplete/Autocompleter.js
+++ b/src/autocomplete/Autocompleter.js
@@ -85,7 +85,7 @@ export default class Autocompleter {
                 provider
                     .getCompletions(query, selection, force)
                     .timeout(PROVIDER_COMPLETION_TIMEOUT)
-                    .reflect()
+                    .reflect(),
             ),
         );
 
diff --git a/src/autocomplete/CommunityProvider.js b/src/autocomplete/CommunityProvider.js
index d164fab46a..b85c09b320 100644
--- a/src/autocomplete/CommunityProvider.js
+++ b/src/autocomplete/CommunityProvider.js
@@ -61,7 +61,7 @@ export default class CommunityProvider extends AutocompleteProvider {
         if (command) {
             const joinedGroups = cli.getGroups().filter(({myMembership}) => myMembership === 'join');
 
-            const groups = (await Promise.all(joinedGroups.map(async ({groupId}) => {
+            const groups = (await Promise.all(joinedGroups.map(async({groupId}) => {
                 try {
                     return FlairStore.getGroupProfileCached(cli, groupId);
                 } catch (e) { // if FlairStore failed, fall back to just groupId
diff --git a/src/autocomplete/PlainWithPillsSerializer.js b/src/autocomplete/PlainWithPillsSerializer.js
index 59cf1bde3b..09bb3772ac 100644
--- a/src/autocomplete/PlainWithPillsSerializer.js
+++ b/src/autocomplete/PlainWithPillsSerializer.js
@@ -26,7 +26,6 @@ import { Block } from 'slate';
  */
 
 class PlainWithPillsSerializer {
-
     /*
      * @param {String} options.pillFormat - either 'md', 'plain', 'id'
      */
diff --git a/src/components/structures/BottomLeftMenu.js b/src/components/structures/BottomLeftMenu.js
index d289ca5da1..ed8b8a00b7 100644
--- a/src/components/structures/BottomLeftMenu.js
+++ b/src/components/structures/BottomLeftMenu.js
@@ -33,12 +33,12 @@ module.exports = React.createClass({
     },
 
     getInitialState: function() {
-        return({
-            directoryHover : false,
-            roomsHover : false,
+        return ({
+            directoryHover: false,
+            roomsHover: false,
             homeHover: false,
-            peopleHover : false,
-            settingsHover : false,
+            peopleHover: false,
+            settingsHover: false,
         });
     },
 
@@ -145,7 +145,7 @@ module.exports = React.createClass({
     // Get the label/tooltip to show
     getLabel: function(label, show) {
         if (show) {
-            var RoomTooltip = sdk.getComponent("rooms.RoomTooltip");
+            const RoomTooltip = sdk.getComponent("rooms.RoomTooltip");
             return <RoomTooltip className="mx_BottomLeftMenu_tooltip" label={label} />;
         }
     },
diff --git a/src/components/structures/CompatibilityPage.js b/src/components/structures/CompatibilityPage.js
index 4cbaab3dfa..3c5005c053 100644
--- a/src/components/structures/CompatibilityPage.js
+++ b/src/components/structures/CompatibilityPage.js
@@ -16,18 +16,18 @@ limitations under the License.
 
 'use strict';
 
-var React = require('react');
+const React = require('react');
 import { _t } from '../../languageHandler';
 
 module.exports = React.createClass({
     displayName: 'CompatibilityPage',
     propTypes: {
-        onAccept: React.PropTypes.func
+        onAccept: React.PropTypes.func,
     },
 
     getDefaultProps: function() {
         return {
-            onAccept: function() {} // NOP
+            onAccept: function() {}, // NOP
         };
     },
 
@@ -36,7 +36,6 @@ module.exports = React.createClass({
     },
 
     render: function() {
-
         return (
         <div className="mx_CompatibilityPage">
             <div className="mx_CompatibilityPage_box">
@@ -69,5 +68,5 @@ module.exports = React.createClass({
             </div>
         </div>
         );
-    }
+    },
 });
diff --git a/src/components/structures/CreateRoom.js b/src/components/structures/CreateRoom.js
index 2bb9adb544..a8aac71479 100644
--- a/src/components/structures/CreateRoom.js
+++ b/src/components/structures/CreateRoom.js
@@ -36,10 +36,10 @@ module.exports = React.createClass({
     },
 
     phases: {
-        CONFIG: "CONFIG",  // We're waiting for user to configure and hit create.
-        CREATING: "CREATING",  // We're sending the request.
-        CREATED: "CREATED",  // We successfully created the room.
-        ERROR: "ERROR",  // There was an error while trying to create room.
+        CONFIG: "CONFIG", // We're waiting for user to configure and hit create.
+        CREATING: "CREATING", // We're sending the request.
+        CREATED: "CREATED", // We successfully created the room.
+        ERROR: "ERROR", // There was an error while trying to create room.
     },
 
     getDefaultProps: function() {
diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js
index 5d23194702..2c287c1b60 100644
--- a/src/components/structures/GroupView.js
+++ b/src/components/structures/GroupView.js
@@ -777,7 +777,7 @@ export default React.createClass({
             ),
             button: _t("Leave"),
             danger: this.state.isUserPrivileged,
-            onFinished: async (confirmed) => {
+            onFinished: async(confirmed) => {
                 if (!confirmed) return;
 
                 this.setState({membershipBusy: true});
diff --git a/src/components/structures/HomePage.js b/src/components/structures/HomePage.js
index 457796f5dc..89053b35c7 100644
--- a/src/components/structures/HomePage.js
+++ b/src/components/structures/HomePage.js
@@ -52,15 +52,14 @@ class HomePage extends React.Component {
 
         if (this.props.teamToken && this.props.teamServerUrl) {
             this.setState({
-                iframeSrc: `${this.props.teamServerUrl}/static/${this.props.teamToken}/home.html`
+                iframeSrc: `${this.props.teamServerUrl}/static/${this.props.teamToken}/home.html`,
             });
-        }
-        else {
+        } else {
             // we use request() to inline the homepage into the react component
             // so that it can inherit CSS and theming easily rather than mess around
             // with iframes and trying to synchronise document.stylesheets.
 
-            let src = this.props.homePageUrl || 'home.html';
+            const src = this.props.homePageUrl || 'home.html';
 
             request(
                 { method: "GET", url: src },
@@ -77,7 +76,7 @@ class HomePage extends React.Component {
 
                     body = body.replace(/_t\(['"]([\s\S]*?)['"]\)/mg, (match, g1)=>this.translate(g1));
                     this.setState({ page: body });
-                }
+                },
             );
         }
     }
@@ -93,8 +92,7 @@ class HomePage extends React.Component {
                     <iframe src={ this.state.iframeSrc } />
                 </div>
             );
-        }
-        else {
+        } else {
             const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
             return (
                 <GeminiScrollbarWrapper autoshow={true} className="mx_HomePage">
@@ -106,4 +104,4 @@ class HomePage extends React.Component {
     }
 }
 
-module.exports = HomePage;
\ No newline at end of file
+module.exports = HomePage;
diff --git a/src/components/structures/LeftPanel.js b/src/components/structures/LeftPanel.js
index 72d640bcac..2d6e7b66d2 100644
--- a/src/components/structures/LeftPanel.js
+++ b/src/components/structures/LeftPanel.js
@@ -28,7 +28,7 @@ import VectorConferenceHandler from '../../VectorConferenceHandler';
 import SettingsStore from '../../settings/SettingsStore';
 
 
-var LeftPanel = React.createClass({
+const LeftPanel = React.createClass({
     displayName: 'LeftPanel',
 
     // NB. If you add props, don't forget to update
@@ -214,11 +214,11 @@ var LeftPanel = React.createClass({
                         collapsed={this.props.collapsed}
                         searchFilter={this.state.searchFilter}
                         ConferenceHandler={VectorConferenceHandler} />
-                    <BottomLeftMenu collapsed={this.props.collapsed}/>
+                    <BottomLeftMenu collapsed={this.props.collapsed} />
                 </aside>
             </div>
         );
-    }
+    },
 });
 
 module.exports = LeftPanel;
diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js
index d95d5cd652..231a6ef6b6 100644
--- a/src/components/structures/LoggedInView.js
+++ b/src/components/structures/LoggedInView.js
@@ -189,13 +189,13 @@ const LoggedInView = React.createClass({
     _updateServerNoticeEvents: async function() {
         const roomLists = RoomListStore.getRoomLists();
         if (!roomLists['m.server_notice']) return [];
-        
+
         const pinnedEvents = [];
         for (const room of roomLists['m.server_notice']) {
             const pinStateEvent = room.currentState.getStateEvents("m.room.pinned_events", "");
 
             if (!pinStateEvent || !pinStateEvent.getContent().pinned) continue;
-            
+
             const pinnedEventIds = pinStateEvent.getContent().pinned.slice(0, MAX_PINNED_NOTICES_PER_ROOM);
             for (const eventId of pinnedEventIds) {
                 const timeline = await this._matrixClient.getEventTimeline(room.getUnfilteredTimelineSet(), eventId, 0);
@@ -207,7 +207,7 @@ const LoggedInView = React.createClass({
             serverNoticeEvents: pinnedEvents,
         });
     },
-    
+
 
     _onKeyDown: function(ev) {
             /*
diff --git a/src/components/structures/LoginBox.js b/src/components/structures/LoginBox.js
index a2269706ee..168014daa5 100644
--- a/src/components/structures/LoginBox.js
+++ b/src/components/structures/LoginBox.js
@@ -53,5 +53,5 @@ module.exports = React.createClass({
                 { loginButton }
             </div>
         );
-    }
+    },
 });
diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js
index f417932fd0..4c7d004015 100644
--- a/src/components/structures/RoomDirectory.js
+++ b/src/components/structures/RoomDirectory.js
@@ -16,18 +16,18 @@ limitations under the License.
 
 'use strict';
 
-var React = require('react');
+const React = require('react');
 
-var MatrixClientPeg = require('../../MatrixClientPeg');
-var ContentRepo = require("matrix-js-sdk").ContentRepo;
-var Modal = require('../../Modal');
-var sdk = require('../../index');
-var dis = require('../../dispatcher');
+const MatrixClientPeg = require('../../MatrixClientPeg');
+const ContentRepo = require("matrix-js-sdk").ContentRepo;
+const Modal = require('../../Modal');
+const sdk = require('../../index');
+const dis = require('../../dispatcher');
 
-var linkify = require('linkifyjs');
-var linkifyString = require('linkifyjs/string');
-var linkifyMatrix = require('../../linkify-matrix');
-var sanitizeHtml = require('sanitize-html');
+const linkify = require('linkifyjs');
+const linkifyString = require('linkifyjs/string');
+const linkifyMatrix = require('../../linkify-matrix');
+const sanitizeHtml = require('sanitize-html');
 import Promise from 'bluebird';
 
 import { _t } from '../../languageHandler';
@@ -46,7 +46,7 @@ module.exports = React.createClass({
     getDefaultProps: function() {
         return {
             config: {},
-        }
+        };
     },
 
     getInitialState: function() {
@@ -58,7 +58,7 @@ module.exports = React.createClass({
             includeAll: false,
             roomServer: null,
             filterString: null,
-        }
+        };
     },
 
     componentWillMount: function() {
@@ -134,13 +134,12 @@ module.exports = React.createClass({
             opts.include_all_networks = true;
         }
         if (this.nextBatch) opts.since = this.nextBatch;
-        if (my_filter_string) opts.filter = { generic_search_term: my_filter_string } ;
+        if (my_filter_string) opts.filter = { generic_search_term: my_filter_string };
         return MatrixClientPeg.get().publicRooms(opts).then((data) => {
             if (
                 my_filter_string != this.state.filterString ||
                 my_server != this.state.roomServer ||
-                my_next_batch != this.nextBatch)
-            {
+                my_next_batch != this.nextBatch) {
                 // if the filter or server has changed since this request was sent,
                 // throw away the result (don't even clear the busy flag
                 // since we must still have a request in flight)
@@ -163,8 +162,7 @@ module.exports = React.createClass({
             if (
                 my_filter_string != this.state.filterString ||
                 my_server != this.state.roomServer ||
-                my_next_batch != this.nextBatch)
-            {
+                my_next_batch != this.nextBatch) {
                 // as above: we don't care about errors for old
                 // requests either
                 return;
@@ -177,10 +175,10 @@ module.exports = React.createClass({
 
             this.setState({ loading: false });
             console.error("Failed to get publicRooms: %s", JSON.stringify(err));
-            var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+            const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
             Modal.createTrackedDialog('Failed to get public room list', '', ErrorDialog, {
                 title: _t('Failed to get public room list'),
-                description: ((err && err.message) ? err.message : _t('The server may be unavailable or overloaded'))
+                description: ((err && err.message) ? err.message : _t('The server may be unavailable or overloaded')),
             });
         });
     },
@@ -193,13 +191,13 @@ module.exports = React.createClass({
      * this needs SPEC-417.
      */
     removeFromDirectory: function(room) {
-        var alias = get_display_alias_for_room(room);
-        var name = room.name || alias || _t('Unnamed room');
+        const alias = get_display_alias_for_room(room);
+        const name = room.name || alias || _t('Unnamed room');
 
-        var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
-        var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+        const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
+        const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
 
-        var desc;
+        let desc;
         if (alias) {
             desc = _t('Delete the room alias %(alias)s and remove %(name)s from the directory?', {alias: alias, name: name});
         } else {
@@ -212,9 +210,9 @@ module.exports = React.createClass({
             onFinished: (should_delete) => {
                 if (!should_delete) return;
 
-                var Loader = sdk.getComponent("elements.Spinner");
-                var modal = Modal.createDialog(Loader);
-                var step = _t('remove %(name)s from the directory.', {name: name});
+                const Loader = sdk.getComponent("elements.Spinner");
+                const modal = Modal.createDialog(Loader);
+                let step = _t('remove %(name)s from the directory.', {name: name});
 
                 MatrixClientPeg.get().setRoomDirectoryVisibility(room.room_id, 'private').then(() => {
                     if (!alias) return;
@@ -229,10 +227,10 @@ module.exports = React.createClass({
                     console.error("Failed to " + step + ": " + err);
                     Modal.createTrackedDialog('Remove from Directory Error', '', ErrorDialog, {
                         title: _t('Error'),
-                        description: ((err && err.message) ? err.message : _t('The server may be unavailable or overloaded'))
+                        description: ((err && err.message) ? err.message : _t('The server may be unavailable or overloaded')),
                     });
                 });
-            }
+            },
         });
     },
 
@@ -347,7 +345,7 @@ module.exports = React.createClass({
     },
 
     showRoom: function(room, room_alias) {
-        var payload = {action: 'view_room'};
+        const payload = {action: 'view_room'};
         if (room) {
             // Don't let the user view a room they won't be able to either
             // peek or join: fail earlier so they don't have to click back
@@ -383,16 +381,16 @@ module.exports = React.createClass({
     },
 
     getRows: function() {
-        var BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
+        const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
 
         if (!this.state.publicRooms) return [];
 
-        var rooms = this.state.publicRooms;
-        var rows = [];
-        var self = this;
-        var guestRead, guestJoin, perms;
-        for (var i = 0; i < rooms.length; i++) {
-            var name = rooms[i].name || get_display_alias_for_room(rooms[i]) || _t('Unnamed room');
+        const rooms = this.state.publicRooms;
+        const rows = [];
+        const self = this;
+        let guestRead; let guestJoin; let perms;
+        for (let i = 0; i < rooms.length; i++) {
+            const name = rooms[i].name || get_display_alias_for_room(rooms[i]) || _t('Unnamed room');
             guestRead = null;
             guestJoin = null;
 
@@ -412,7 +410,7 @@ module.exports = React.createClass({
                 perms = <div className="mx_RoomDirectory_perms">{guestRead}{guestJoin}</div>;
             }
 
-            var topic = rooms[i].topic || '';
+            let topic = rooms[i].topic || '';
             topic = linkifyString(sanitizeHtml(topic));
 
             rows.push(
@@ -432,14 +430,14 @@ module.exports = React.createClass({
                         <div className="mx_RoomDirectory_name">{ name }</div>&nbsp;
                         { perms }
                         <div className="mx_RoomDirectory_topic"
-                             onClick={ function(e) { e.stopPropagation() } }
-                             dangerouslySetInnerHTML={{ __html: topic }}/>
+                             onClick={ function(e) { e.stopPropagation(); } }
+                             dangerouslySetInnerHTML={{ __html: topic }} />
                         <div className="mx_RoomDirectory_alias">{ get_display_alias_for_room(rooms[i]) }</div>
                     </td>
                     <td className="mx_RoomDirectory_roomMemberCount">
                         { rooms[i].num_joined_members }
                     </td>
-                </tr>
+                </tr>,
             );
         }
         return rows;
@@ -524,7 +522,7 @@ module.exports = React.createClass({
                 onFillRequest={ this.onFillRequest }
                 stickyBottom={false}
                 startAtBottom={false}
-                onResize={function(){}}
+                onResize={function() {}}
             >
                 { scrollpanel_content }
             </ScrollPanel>;
@@ -577,11 +575,11 @@ module.exports = React.createClass({
                 </div>
             </div>
         );
-    }
+    },
 });
 
 // Similar to matrix-react-sdk's MatrixTools.getDisplayAliasForRoom
 // but works with the objects we get from the public room list
 function get_display_alias_for_room(room) {
-    return  room.canonical_alias || (room.aliases ? room.aliases[0] : "");
+    return room.canonical_alias || (room.aliases ? room.aliases[0] : "");
 }
diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js
index c6a6e1e2d3..b4d70f3397 100644
--- a/src/components/structures/RoomStatusBar.js
+++ b/src/components/structures/RoomStatusBar.js
@@ -37,7 +37,7 @@ function getUnsentMessages(room) {
     return room.getPendingEvents().filter(function(ev) {
         return ev.status === Matrix.EventStatus.NOT_SENT;
     });
-};
+}
 
 module.exports = React.createClass({
     displayName: 'RoomStatusBar',
@@ -303,7 +303,7 @@ module.exports = React.createClass({
         const errorIsMauError = Boolean(
             this.state.syncStateData &&
             this.state.syncStateData.error &&
-            this.state.syncStateData.error.errcode === 'M_RESOURCE_LIMIT_EXCEEDED'
+            this.state.syncStateData.error.errcode === 'M_RESOURCE_LIMIT_EXCEEDED',
         );
         return this.state.syncState === "ERROR" && !errorIsMauError;
     },
diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js
index 53644af78f..dd57bd7636 100644
--- a/src/components/structures/RoomView.js
+++ b/src/components/structures/RoomView.js
@@ -1673,7 +1673,7 @@ module.exports = React.createClass({
             </AuxPanel>
         );
 
-        let messageComposer, searchInfo;
+        let messageComposer; let searchInfo;
         const canSpeak = (
             // joined and not showing search results
             myMembership === 'join' && !this.state.searchResults
@@ -1692,7 +1692,7 @@ module.exports = React.createClass({
 
         if (MatrixClientPeg.get().isGuest()) {
             const LoginBox = sdk.getComponent('structures.LoginBox');
-            messageComposer = <LoginBox/>;
+            messageComposer = <LoginBox />;
         }
 
         // TODO: Why aren't we storing the term/scope/count in this format
@@ -1706,7 +1706,7 @@ module.exports = React.createClass({
         }
 
         if (inCall) {
-            let zoomButton, voiceMuteButton, videoMuteButton;
+            let zoomButton; let voiceMuteButton; let videoMuteButton;
 
             if (call.type === "video") {
                 zoomButton = (
diff --git a/src/components/structures/SearchBox.js b/src/components/structures/SearchBox.js
index 459df3bd26..d1f4cd30fa 100644
--- a/src/components/structures/SearchBox.js
+++ b/src/components/structures/SearchBox.js
@@ -72,7 +72,7 @@ module.exports = React.createClass({
         function() {
             this.props.onSearch(this.refs.search.value);
         },
-        100
+        100,
     ),
 
     onToggleCollapse: function(show) {
@@ -80,8 +80,7 @@ module.exports = React.createClass({
             dis.dispatch({
                 action: 'show_left_panel',
             });
-        }
-        else {
+        } else {
             dis.dispatch({
                 action: 'hide_left_panel',
             });
@@ -103,25 +102,24 @@ module.exports = React.createClass({
     },
 
     render: function() {
-        var TintableSvg = sdk.getComponent('elements.TintableSvg');
+        const TintableSvg = sdk.getComponent('elements.TintableSvg');
 
-        var collapseTabIndex = this.refs.search && this.refs.search.value !== "" ? "-1" : "0";
+        const collapseTabIndex = this.refs.search && this.refs.search.value !== "" ? "-1" : "0";
 
-        var toggleCollapse;
+        let toggleCollapse;
         if (this.props.collapsed) {
             toggleCollapse =
                 <AccessibleButton className="mx_SearchBox_maximise" tabIndex={collapseTabIndex} onClick={ this.onToggleCollapse.bind(this, true) }>
-                    <TintableSvg src="img/maximise.svg" width="10" height="16" alt={ _t("Expand panel") }/>
-                </AccessibleButton>
-        }
-        else {
+                    <TintableSvg src="img/maximise.svg" width="10" height="16" alt={ _t("Expand panel") } />
+                </AccessibleButton>;
+        } else {
             toggleCollapse =
                 <AccessibleButton className="mx_SearchBox_minimise" tabIndex={collapseTabIndex} onClick={ this.onToggleCollapse.bind(this, false) }>
-                    <TintableSvg src="img/minimise.svg" width="10" height="16" alt={ _t("Collapse panel") }/>
-                </AccessibleButton>
+                    <TintableSvg src="img/minimise.svg" width="10" height="16" alt={ _t("Collapse panel") } />
+                </AccessibleButton>;
         }
 
-        var searchControls;
+        let searchControls;
         if (!this.props.collapsed) {
             searchControls = [
                     this.state.searchTerm.length > 0 ?
@@ -148,16 +146,16 @@ module.exports = React.createClass({
                         onChange={ this.onChange }
                         onKeyDown={ this._onKeyDown }
                         placeholder={ _t('Filter room names') }
-                    />
+                    />,
                 ];
         }
 
-        var self = this;
+        const self = this;
         return (
             <div className="mx_SearchBox">
                 { searchControls }
                 { toggleCollapse }
             </div>
         );
-    }
+    },
 });
diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js
index e06c652924..44d14cd7cf 100644
--- a/src/components/structures/TimelinePanel.js
+++ b/src/components/structures/TimelinePanel.js
@@ -829,7 +829,7 @@ var TimelinePanel = React.createClass({
         // 4. Also, if pos === null, the event might not be paginated - show the unread bar
         const pos = this.getReadMarkerPosition();
         return this.state.readMarkerEventId !== null && // 1.
-            this.state.readMarkerEventId !== this._getCurrentReadReceipt() &&  // 2.
+            this.state.readMarkerEventId !== this._getCurrentReadReceipt() && // 2.
             (pos < 0 || pos === null); // 3., 4.
     },
 
diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js
index 772eb70dde..40dccd7839 100644
--- a/src/components/structures/UserSettings.js
+++ b/src/components/structures/UserSettings.js
@@ -845,7 +845,7 @@ module.exports = React.createClass({
         SettingsStore.getLabsFeatures().forEach((featureId) => {
             // TODO: this ought to be a separate component so that we don't need
             // to rebind the onChange each time we render
-            const onChange = async (e) => {
+            const onChange = async(e) => {
                 const checked = e.target.checked;
                 if (featureId === "feature_lazyloading") {
                     const confirmed = await this._onLazyLoadChanging(checked);
diff --git a/src/components/structures/ViewSource.js b/src/components/structures/ViewSource.js
index 3a5d35a561..4844149f59 100644
--- a/src/components/structures/ViewSource.js
+++ b/src/components/structures/ViewSource.js
@@ -53,5 +53,5 @@ module.exports = React.createClass({
                 </SyntaxHighlight>
             </div>
         );
-    }
+    },
 });
diff --git a/src/components/views/avatars/MemberAvatar.js b/src/components/views/avatars/MemberAvatar.js
index d191368b17..a9db1165e8 100644
--- a/src/components/views/avatars/MemberAvatar.js
+++ b/src/components/views/avatars/MemberAvatar.js
@@ -79,7 +79,7 @@ module.exports = React.createClass({
         const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
 
         let {member, fallbackUserId, onClick, viewUserOnClick, ...otherProps} = this.props;
-        let userId = member ? member.userId : fallbackUserId;
+        const userId = member ? member.userId : fallbackUserId;
 
         if (viewUserOnClick) {
             onClick = () => {
diff --git a/src/components/views/context_menus/GroupInviteTileContextMenu.js b/src/components/views/context_menus/GroupInviteTileContextMenu.js
index e30acca16d..2dd611843a 100644
--- a/src/components/views/context_menus/GroupInviteTileContextMenu.js
+++ b/src/components/views/context_menus/GroupInviteTileContextMenu.js
@@ -48,7 +48,7 @@ export default class GroupInviteTileContextMenu extends React.Component {
         Modal.createTrackedDialog('Reject community invite', '', QuestionDialog, {
             title: _t('Reject invitation'),
             description: _t('Are you sure you want to reject the invitation?'),
-            onFinished: async (shouldLeave) => {
+            onFinished: async(shouldLeave) => {
                 if (!shouldLeave) return;
 
                 // FIXME: controller shouldn't be loading a view :(
diff --git a/src/components/views/dialogs/ChangelogDialog.js b/src/components/views/dialogs/ChangelogDialog.js
index e71fcdb624..b93678b2ab 100644
--- a/src/components/views/dialogs/ChangelogDialog.js
+++ b/src/components/views/dialogs/ChangelogDialog.js
@@ -31,13 +31,13 @@ export default class ChangelogDialog extends React.Component {
     componentDidMount() {
         const version = this.props.newVersion.split('-');
         const version2 = this.props.version.split('-');
-        if(version == null || version2 == null) return;
+        if (version == null || version2 == null) return;
         // parse versions of form: [vectorversion]-react-[react-sdk-version]-js-[js-sdk-version]
-        for(let i=0; i<REPOS.length; i++) {
+        for (let i=0; i<REPOS.length; i++) {
             const oldVersion = version2[2*i];
             const newVersion = version[2*i];
             request(`https://api.github.com/repos/${REPOS[i]}/compare/${oldVersion}...${newVersion}`, (a, b, body) => {
-                if(body == null) return;
+                if (body == null) return;
                 this.setState({[REPOS[i]]: JSON.parse(body).commits});
             });
         }
@@ -66,7 +66,7 @@ export default class ChangelogDialog extends React.Component {
                     {this.state[repo].map(this._elementsForCommit)}
                     </ul>
                 </div>
-            )
+            );
         });
 
         const content = (
@@ -83,7 +83,7 @@ export default class ChangelogDialog extends React.Component {
                 button={_t("Update")}
                 onFinished={this.props.onFinished}
                 />
-        )
+        );
     }
 }
 
diff --git a/src/components/views/dialogs/ChatCreateOrReuseDialog.js b/src/components/views/dialogs/ChatCreateOrReuseDialog.js
index 550abe5299..19b7d83717 100644
--- a/src/components/views/dialogs/ChatCreateOrReuseDialog.js
+++ b/src/components/views/dialogs/ChatCreateOrReuseDialog.js
@@ -26,7 +26,6 @@ import Unread from '../../../Unread';
 import classNames from 'classnames';
 
 export default class ChatCreateOrReuseDialog extends React.Component {
-
     constructor(props) {
         super(props);
         this.onFinished = this.onFinished.bind(this);
diff --git a/src/components/views/directory/NetworkDropdown.js b/src/components/views/directory/NetworkDropdown.js
index b45bd286f2..ad51f501fb 100644
--- a/src/components/views/directory/NetworkDropdown.js
+++ b/src/components/views/directory/NetworkDropdown.js
@@ -153,8 +153,8 @@ export default class NetworkDropdown extends React.Component {
 
                         const sortedInstances = this.props.protocols[proto].instances;
                         sortedInstances.sort(function(x, y) {
-                            const a = x.desc
-                            const b = y.desc
+                            const a = x.desc;
+                            const b = y.desc;
                             if (a < b) {
                                 return -1;
                             } else if (a > b) {
@@ -208,7 +208,7 @@ export default class NetworkDropdown extends React.Component {
         return <div key={key} className="mx_NetworkDropdown_networkoption" onClick={click_handler}>
             {icon}
             <span className="mx_NetworkDropdown_menu_network">{name}</span>
-        </div>
+        </div>;
     }
 
     render() {
@@ -223,11 +223,11 @@ export default class NetworkDropdown extends React.Component {
             current_value = <input type="text" className="mx_NetworkDropdown_networkoption"
                 ref={this.collectInputTextBox} onKeyUp={this.onInputKeyUp}
                 placeholder="matrix.org" // 'matrix.org' as an example of an HS name
-            />
+            />;
         } else {
             const instance = instanceForInstanceId(this.props.protocols, this.state.selectedInstanceId);
             current_value = this._makeMenuOption(
-                this.state.selectedServer, instance, this.state.includeAll, false
+                this.state.selectedServer, instance, this.state.includeAll, false,
             );
         }
 
diff --git a/src/components/views/elements/DeviceVerifyButtons.js b/src/components/views/elements/DeviceVerifyButtons.js
index c775cba610..b88fbdfc06 100644
--- a/src/components/views/elements/DeviceVerifyButtons.js
+++ b/src/components/views/elements/DeviceVerifyButtons.js
@@ -78,7 +78,7 @@ export default React.createClass({
     },
 
     render: function() {
-        let blacklistButton = null, verifyButton = null;
+        let blacklistButton = null; let verifyButton = null;
 
         if (this.state.device.isBlocked()) {
             blacklistButton = (
diff --git a/src/components/views/elements/EditableTextContainer.js b/src/components/views/elements/EditableTextContainer.js
index 064d2f1c39..3bf37df951 100644
--- a/src/components/views/elements/EditableTextContainer.js
+++ b/src/components/views/elements/EditableTextContainer.js
@@ -122,7 +122,6 @@ export default class EditableTextContainer extends React.Component {
             );
         }
     }
-
 }
 
 EditableTextContainer.propTypes = {
diff --git a/src/components/views/elements/ImageView.js b/src/components/views/elements/ImageView.js
index f3ad5a9696..4ac5eda170 100644
--- a/src/components/views/elements/ImageView.js
+++ b/src/components/views/elements/ImageView.js
@@ -16,13 +16,13 @@ limitations under the License.
 
 'use strict';
 
-var React = require('react');
+const React = require('react');
 
-var MatrixClientPeg = require('../../../MatrixClientPeg');
+const MatrixClientPeg = require('../../../MatrixClientPeg');
 
 import {formatDate} from '../../../DateUtils';
-var filesize = require('filesize');
-var AccessibleButton = require('../../../components/views/elements/AccessibleButton');
+const filesize = require('filesize');
+const AccessibleButton = require('../../../components/views/elements/AccessibleButton');
 const Modal = require('../../../Modal');
 const sdk = require('../../../index');
 import { _t } from '../../../languageHandler';
@@ -69,24 +69,24 @@ module.exports = React.createClass({
         Modal.createTrackedDialog('Confirm Redact Dialog', 'Image View', ConfirmRedactDialog, {
             onFinished: (proceed) => {
                 if (!proceed) return;
-                var self = this;
+                const self = this;
                 MatrixClientPeg.get().redactEvent(
-                    this.props.mxEvent.getRoomId(), this.props.mxEvent.getId()
+                    this.props.mxEvent.getRoomId(), this.props.mxEvent.getId(),
                 ).catch(function(e) {
-                    var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+                    const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
                     // display error message stating you couldn't delete this.
-                    var code = e.errcode || e.statusCode;
+                    const code = e.errcode || e.statusCode;
                     Modal.createTrackedDialog('You cannot delete this image.', '', ErrorDialog, {
                         title: _t('Error'),
-                        description: _t('You cannot delete this image. (%(code)s)', {code: code})
+                        description: _t('You cannot delete this image. (%(code)s)', {code: code}),
                     });
                 }).done();
-            }
+            },
         });
     },
 
-    getName: function () {
-        var name = this.props.name;
+    getName: function() {
+        let name = this.props.name;
         if (name && this.props.link) {
             name = <a href={ this.props.link } target="_blank" rel="noopener">{ name }</a>;
         }
@@ -94,7 +94,6 @@ module.exports = React.createClass({
     },
 
     render: function() {
-
 /*
         // In theory max-width: 80%, max-height: 80% on the CSS should work
         // but in practice, it doesn't, so do it manually:
@@ -123,7 +122,7 @@ module.exports = React.createClass({
             height: displayHeight
         };
 */
-        var style, res;
+        let style; let res;
 
         if (this.props.width && this.props.height) {
             style = {
@@ -133,23 +132,22 @@ module.exports = React.createClass({
             res = style.width + "x" + style.height + "px";
         }
 
-        var size;
+        let size;
         if (this.props.fileSize) {
             size = filesize(this.props.fileSize);
         }
 
-        var size_res;
+        let size_res;
         if (size && res) {
             size_res = size + ", " + res;
-        }
-        else {
+        } else {
             size_res = size || res;
         }
 
-        var showEventMeta = !!this.props.mxEvent;
+        const showEventMeta = !!this.props.mxEvent;
 
-        var eventMeta;
-        if(showEventMeta) {
+        let eventMeta;
+        if (showEventMeta) {
             // Figure out the sender, defaulting to mxid
             let sender = this.props.mxEvent.getSender();
             const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
@@ -163,8 +161,8 @@ module.exports = React.createClass({
             </div>);
         }
 
-        var eventRedact;
-        if(showEventMeta) {
+        let eventRedact;
+        if (showEventMeta) {
             eventRedact = (<div className="mx_ImageView_button" onClick={this.onRedactClick}>
                 { _t('Remove') }
             </div>);
@@ -175,10 +173,10 @@ module.exports = React.createClass({
                 <div className="mx_ImageView_lhs">
                 </div>
                 <div className="mx_ImageView_content">
-                    <img src={this.props.src} style={style}/>
+                    <img src={this.props.src} style={style} />
                     <div className="mx_ImageView_labelWrapper">
                         <div className="mx_ImageView_label">
-                            <AccessibleButton className="mx_ImageView_cancel" onClick={ this.props.onFinished }><img src="img/cancel-white.svg" width="18" height="18" alt={ _t('Close') }/></AccessibleButton>
+                            <AccessibleButton className="mx_ImageView_cancel" onClick={ this.props.onFinished }><img src="img/cancel-white.svg" width="18" height="18" alt={ _t('Close') } /></AccessibleButton>
                             <div className="mx_ImageView_shim">
                             </div>
                             <div className="mx_ImageView_name">
@@ -187,7 +185,7 @@ module.exports = React.createClass({
                             { eventMeta }
                             <a className="mx_ImageView_link" href={ this.props.src } download={ this.props.name } target="_blank" rel="noopener">
                                 <div className="mx_ImageView_download">
-                                        { _t('Download this file') }<br/>
+                                        { _t('Download this file') }<br />
                                          <span className="mx_ImageView_size">{ size_res }</span>
                                 </div>
                             </a>
@@ -201,5 +199,5 @@ module.exports = React.createClass({
                 </div>
             </div>
         );
-    }
+    },
 });
diff --git a/src/components/views/elements/InlineSpinner.js b/src/components/views/elements/InlineSpinner.js
index adb916fcf0..cce4705512 100644
--- a/src/components/views/elements/InlineSpinner.js
+++ b/src/components/views/elements/InlineSpinner.js
@@ -20,14 +20,14 @@ module.exports = React.createClass({
     displayName: 'InlineSpinner',
 
     render: function() {
-        var w = this.props.w || 16;
-        var h = this.props.h || 16;
-        var imgClass = this.props.imgClassName || "";
+        const w = this.props.w || 16;
+        const h = this.props.h || 16;
+        const imgClass = this.props.imgClassName || "";
 
         return (
             <div className="mx_InlineSpinner">
-                <img src="img/spinner.gif" width={w} height={h} className={imgClass}/>
+                <img src="img/spinner.gif" width={w} height={h} className={imgClass} />
             </div>
         );
-    }
+    },
 });
diff --git a/src/components/views/elements/PersistedElement.js b/src/components/views/elements/PersistedElement.js
index ff839de2a9..d23d6488b6 100644
--- a/src/components/views/elements/PersistedElement.js
+++ b/src/components/views/elements/PersistedElement.js
@@ -54,7 +54,6 @@ function getOrCreateContainer(containerId) {
  * bounding rect as the parent of PE.
  */
 export default class PersistedElement extends React.Component {
-
     static propTypes = {
         // Unique identifier for this PersistedElement instance
         // Any PersistedElements with the same persistKey will use
diff --git a/src/components/views/elements/Spinner.js b/src/components/views/elements/Spinner.js
index 2b620f12c5..67fde4bf66 100644
--- a/src/components/views/elements/Spinner.js
+++ b/src/components/views/elements/Spinner.js
@@ -16,19 +16,19 @@ limitations under the License.
 
 'use strict';
 
-var React = require('react');
+const React = require('react');
 
 module.exports = React.createClass({
     displayName: 'Spinner',
 
     render: function() {
-        var w = this.props.w || 32;
-        var h = this.props.h || 32;
-        var imgClass = this.props.imgClassName || "";
+        const w = this.props.w || 32;
+        const h = this.props.h || 32;
+        const imgClass = this.props.imgClassName || "";
         return (
             <div className="mx_Spinner">
-                <img src="img/spinner.gif" width={w} height={h} className={imgClass}/>
+                <img src="img/spinner.gif" width={w} height={h} className={imgClass} />
             </div>
         );
-    }
+    },
 });
diff --git a/src/components/views/elements/TintableSvgButton.js b/src/components/views/elements/TintableSvgButton.js
index 4bfe46d43f..a3f5b7db5d 100644
--- a/src/components/views/elements/TintableSvgButton.js
+++ b/src/components/views/elements/TintableSvgButton.js
@@ -20,7 +20,6 @@ import TintableSvg from './TintableSvg';
 import AccessibleButton from './AccessibleButton';
 
 export default class TintableSvgButton extends React.Component {
-
     constructor(props) {
         super(props);
     }
diff --git a/src/components/views/globals/MatrixToolbar.js b/src/components/views/globals/MatrixToolbar.js
index 45feede5a0..400d9e0c83 100644
--- a/src/components/views/globals/MatrixToolbar.js
+++ b/src/components/views/globals/MatrixToolbar.js
@@ -39,7 +39,7 @@ module.exports = React.createClass({
                 <div className="mx_MatrixToolbar_content">
                    { _t('You are not receiving desktop notifications') } <a className="mx_MatrixToolbar_link" onClick={ this.onClick }> { _t('Enable them now') }</a>
                 </div>
-                <AccessibleButton className="mx_MatrixToolbar_close" onClick={ this.hideToolbar } ><img src="img/cancel.svg" width="18" height="18" alt={_t('Close')}/></AccessibleButton>
+                <AccessibleButton className="mx_MatrixToolbar_close" onClick={ this.hideToolbar } ><img src="img/cancel.svg" width="18" height="18" alt={_t('Close')} /></AccessibleButton>
             </div>
         );
     },
diff --git a/src/components/views/globals/NewVersionBar.js b/src/components/views/globals/NewVersionBar.js
index 1bd0bde526..47eed4dc59 100644
--- a/src/components/views/globals/NewVersionBar.js
+++ b/src/components/views/globals/NewVersionBar.js
@@ -45,10 +45,10 @@ export default React.createClass({
             description: <div className="mx_MatrixToolbar_changelog">{releaseNotes}</div>,
             button: _t("Update"),
             onFinished: (update) => {
-                if(update && PlatformPeg.get()) {
+                if (update && PlatformPeg.get()) {
                     PlatformPeg.get().installUpdate();
                 }
-            }
+            },
         });
     },
 
@@ -58,10 +58,10 @@ export default React.createClass({
             version: this.props.version,
             newVersion: this.props.newVersion,
             onFinished: (update) => {
-                if(update && PlatformPeg.get()) {
+                if (update && PlatformPeg.get()) {
                     PlatformPeg.get().installUpdate();
                 }
-            }
+            },
         });
     },
 
@@ -103,5 +103,5 @@ export default React.createClass({
                 {action_button}
             </div>
         );
-    }
+    },
 });
diff --git a/src/components/views/globals/UpdateCheckBar.js b/src/components/views/globals/UpdateCheckBar.js
index e499ddab31..4610dbdb12 100644
--- a/src/components/views/globals/UpdateCheckBar.js
+++ b/src/components/views/globals/UpdateCheckBar.js
@@ -32,14 +32,14 @@ export default React.createClass({
     getDefaultProps: function() {
         return {
             detail: '',
-        }
+        };
     },
 
     getStatusText: function() {
         // we can't import the enum from riot-web as we don't want matrix-react-sdk
         // to depend on riot-web. so we grab it as a normal object via API instead.
         const updateCheckStatusEnum = PlatformPeg.get().getUpdateCheckStatusEnum();
-        switch(this.props.status) {
+        switch (this.props.status) {
             case updateCheckStatusEnum.ERROR:
                 return _t('Error encountered (%(errorDetail)s).', { errorDetail: this.props.detail });
             case updateCheckStatusEnum.CHECKING:
@@ -59,7 +59,7 @@ export default React.createClass({
         const message = this.getStatusText();
         const warning = _t('Warning');
 
-        if (!'getUpdateCheckStatusEnum' in PlatformPeg.get()) {
+        if (!('getUpdateCheckStatusEnum' in PlatformPeg.get())) {
             return <div></div>;
         }
 
@@ -83,9 +83,9 @@ export default React.createClass({
                     {message}
                 </div>
                 <AccessibleButton className="mx_MatrixToolbar_close" onClick={this.hideToolbar}>
-                    <img src="img/cancel.svg" width="18" height="18" alt={_t('Close')}/>
+                    <img src="img/cancel.svg" width="18" height="18" alt={_t('Close')} />
                 </AccessibleButton>
             </div>
         );
-    }
+    },
 });
diff --git a/src/components/views/login/InteractiveAuthEntryComponents.js b/src/components/views/login/InteractiveAuthEntryComponents.js
index 6e0e5d538a..481cdc60b2 100644
--- a/src/components/views/login/InteractiveAuthEntryComponents.js
+++ b/src/components/views/login/InteractiveAuthEntryComponents.js
@@ -296,7 +296,7 @@ export const TermsAuthEntry = React.createClass({
             return <Loader />;
         }
 
-        let checkboxes = [];
+        const checkboxes = [];
         let allChecked = true;
         for (const policy of this.state.policies) {
             const checked = this.state.toggledPolicies[policy.id];
@@ -306,7 +306,7 @@ export const TermsAuthEntry = React.createClass({
                 <label key={"policy_checkbox_" + policy.id}>
                     <input type="checkbox" onClick={() => this._trySubmit(policy.id)} checked={checked} />
                     <a href={policy.url} target="_blank" rel="noopener">{ policy.name }</a>
-                </label>
+                </label>,
             );
         }
 
diff --git a/src/components/views/room_settings/AliasSettings.js b/src/components/views/room_settings/AliasSettings.js
index 92a025f10c..de5d3db625 100644
--- a/src/components/views/room_settings/AliasSettings.js
+++ b/src/components/views/room_settings/AliasSettings.js
@@ -103,7 +103,7 @@ module.exports = React.createClass({
             oldCanonicalAlias = this.props.canonicalAliasEvent.getContent().alias;
         }
 
-        let newCanonicalAlias = this.state.canonicalAlias;
+        const newCanonicalAlias = this.state.canonicalAlias;
 
         if (this.props.canSetCanonicalAlias && oldCanonicalAlias !== newCanonicalAlias) {
             console.log("AliasSettings: Updating canonical alias");
@@ -167,7 +167,7 @@ module.exports = React.createClass({
 
         if (!this.props.canonicalAlias) {
             this.setState({
-                canonicalAlias: alias
+                canonicalAlias: alias,
             });
         }
     },
diff --git a/src/components/views/rooms/Autocomplete.js b/src/components/views/rooms/Autocomplete.js
index 757204f0c8..e75456ea50 100644
--- a/src/components/views/rooms/Autocomplete.js
+++ b/src/components/views/rooms/Autocomplete.js
@@ -33,7 +33,6 @@ import Autocompleter from '../../../autocomplete/Autocompleter';
 const COMPOSER_SELECTED = 0;
 
 export default class Autocomplete extends React.Component {
-
     constructor(props) {
         super(props);
 
diff --git a/src/components/views/rooms/LinkPreviewWidget.js b/src/components/views/rooms/LinkPreviewWidget.js
index 2c70f36e07..7f74176878 100644
--- a/src/components/views/rooms/LinkPreviewWidget.js
+++ b/src/components/views/rooms/LinkPreviewWidget.js
@@ -107,7 +107,7 @@ module.exports = React.createClass({
 
         // FIXME: do we want to factor out all image displaying between this and MImageBody - especially for lightboxing?
         let image = p["og:image"];
-        let imageMaxWidth = 100, imageMaxHeight = 100;
+        const imageMaxWidth = 100; const imageMaxHeight = 100;
         if (image && image.startsWith("mxc://")) {
             image = MatrixClientPeg.get().mxcUrlToHttp(image, imageMaxWidth, imageMaxHeight);
         }
diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js
index 037957c868..7ff52ecbb6 100644
--- a/src/components/views/rooms/MemberInfo.js
+++ b/src/components/views/rooms/MemberInfo.js
@@ -712,7 +712,7 @@ module.exports = withMatrixClient(React.createClass({
 
             if (!member || !member.membership || member.membership === 'leave') {
                 const roomId = member && member.roomId ? member.roomId : RoomViewStore.getRoomId();
-                const onInviteUserButton = async () => {
+                const onInviteUserButton = async() => {
                     try {
                         await cli.invite(roomId, member.userId);
                     } catch (err) {
diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js
index 66f3fdaa97..8df6a76836 100644
--- a/src/components/views/rooms/MessageComposer.js
+++ b/src/components/views/rooms/MessageComposer.js
@@ -269,7 +269,7 @@ export default class MessageComposer extends React.Component {
             );
         }
 
-        let e2eImg, e2eTitle, e2eClass;
+        let e2eImg; let e2eTitle; let e2eClass;
         const roomIsEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId);
         if (roomIsEncrypted) {
             // FIXME: show a /!\ if there are untrusted devices in the room...
@@ -429,7 +429,7 @@ export default class MessageComposer extends React.Component {
                              className="mx_MessageComposer_formatbar_cancel mx_filterFlipColor"
                              src="img/icon-text-cancel.svg" />
                     </div>
-                </div>
+                </div>;
         }
 
         return (
diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js
index 6f56d35105..14d394ab41 100644
--- a/src/components/views/rooms/MessageComposerInput.js
+++ b/src/components/views/rooms/MessageComposerInput.js
@@ -67,7 +67,7 @@ const EMOJI_UNICODE_TO_SHORTNAME = mapUnicodeToShort();
 const REGEX_EMOJI_WHITESPACE = new RegExp('(?:^|\\s)(' + asciiRegexp + ')\\s$');
 const EMOJI_REGEX = new RegExp(unicodeRegexp, 'g');
 
-const TYPING_USER_TIMEOUT = 10000, TYPING_SERVER_TIMEOUT = 30000;
+const TYPING_USER_TIMEOUT = 10000; const TYPING_SERVER_TIMEOUT = 30000;
 
 const ENTITY_TYPES = {
     AT_ROOM_PILL: 'ATROOMPILL',
@@ -175,8 +175,8 @@ export default class MessageComposerInput extends React.Component {
         // see https://github.com/ianstormtaylor/slate/issues/762#issuecomment-304855095
         this.direction = '';
 
-        this.plainWithMdPills    = new PlainWithPillsSerializer({ pillFormat: 'md' });
-        this.plainWithIdPills    = new PlainWithPillsSerializer({ pillFormat: 'id' });
+        this.plainWithMdPills = new PlainWithPillsSerializer({ pillFormat: 'md' });
+        this.plainWithIdPills = new PlainWithPillsSerializer({ pillFormat: 'id' });
         this.plainWithPlainPills = new PlainWithPillsSerializer({ pillFormat: 'plain' });
 
         this.md = new Md({
@@ -1078,7 +1078,7 @@ export default class MessageComposerInput extends React.Component {
 
         // only look for commands if the first block contains simple unformatted text
         // i.e. no pills or rich-text formatting and begins with a /.
-        let cmd, commandText;
+        let cmd; let commandText;
         const firstChild = editorState.document.nodes.get(0);
         const firstGrandChild = firstChild && firstChild.nodes.get(0);
         if (firstChild && firstGrandChild &&
@@ -1260,7 +1260,7 @@ export default class MessageComposerInput extends React.Component {
         }
     };
 
-    selectHistory = async (up) => {
+    selectHistory = async(up) => {
         const delta = up ? -1 : 1;
 
         // True if we are not currently selecting history, but composing a message
@@ -1308,7 +1308,7 @@ export default class MessageComposerInput extends React.Component {
         return true;
     };
 
-    onTab = async (e) => {
+    onTab = async(e) => {
         this.setState({
             someCompletions: null,
         });
@@ -1330,7 +1330,7 @@ export default class MessageComposerInput extends React.Component {
         up ? this.autocomplete.onUpArrow() : this.autocomplete.onDownArrow();
     };
 
-    onEscape = async (e) => {
+    onEscape = async(e) => {
         e.preventDefault();
         if (this.autocomplete) {
             this.autocomplete.onEscape(e);
@@ -1349,7 +1349,7 @@ export default class MessageComposerInput extends React.Component {
     /* If passed null, restores the original editor content from state.originalEditorState.
      * If passed a non-null displayedCompletion, modifies state.originalEditorState to compute new state.editorState.
      */
-    setDisplayedCompletion = async (displayedCompletion: ?Completion): boolean => {
+    setDisplayedCompletion = async(displayedCompletion: ?Completion): boolean => {
         const activeEditorState = this.state.originalEditorState || this.state.editorState;
 
         if (displayedCompletion == null) {
diff --git a/src/components/views/rooms/RoomPreviewBar.js b/src/components/views/rooms/RoomPreviewBar.js
index 9d3dbe5217..3c767b726a 100644
--- a/src/components/views/rooms/RoomPreviewBar.js
+++ b/src/components/views/rooms/RoomPreviewBar.js
@@ -93,7 +93,7 @@ module.exports = React.createClass({
     },
 
     render: function() {
-        let joinBlock, previewBlock;
+        let joinBlock; let previewBlock;
 
         if (this.props.spinner || this.state.busy) {
             const Spinner = sdk.getComponent("elements.Spinner");
diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js
index b69938a117..8cd559f2ea 100644
--- a/src/components/views/rooms/RoomSettings.js
+++ b/src/components/views/rooms/RoomSettings.js
@@ -657,31 +657,31 @@ module.exports = React.createClass({
         const userLevels = powerLevels.users || {};
 
         const powerLevelDescriptors = {
-            users_default: {
+            "users_default": {
                 desc: _t('The default role for new room members is'),
                 defaultValue: 0,
             },
-            events_default: {
+            "events_default": {
                 desc: _t('To send messages, you must be a'),
                 defaultValue: 0,
             },
-            invite: {
+            "invite": {
                 desc: _t('To invite users into the room, you must be a'),
                 defaultValue: 50,
             },
-            state_default: {
+            "state_default": {
                 desc: _t('To configure the room, you must be a'),
                 defaultValue: 50,
             },
-            kick: {
+            "kick": {
                 desc: _t('To kick users, you must be a'),
                 defaultValue: 50,
             },
-            ban: {
+            "ban": {
                 desc: _t('To ban users, you must be a'),
                 defaultValue: 50,
             },
-            redact: {
+            "redact": {
                 desc: _t('To remove other users\' messages, you must be a'),
                 defaultValue: 50,
             },
diff --git a/src/components/views/settings/Notifications.js b/src/components/views/settings/Notifications.js
index ea727a03b5..72ad2943aa 100644
--- a/src/components/views/settings/Notifications.js
+++ b/src/components/views/settings/Notifications.js
@@ -67,7 +67,7 @@ module.exports = React.createClass({
     phases: {
         LOADING: "LOADING", // The component is loading or sending data to the hs
         DISPLAY: "DISPLAY", // The component is ready and display data
-        ERROR: "ERROR",      // There was an error
+        ERROR: "ERROR", // There was an error
     },
 
     propTypes: {
@@ -86,14 +86,14 @@ module.exports = React.createClass({
     getInitialState: function() {
         return {
             phase: this.phases.LOADING,
-            masterPushRule: undefined,      // The master rule ('.m.rule.master')
-            vectorPushRules: [],            // HS default push rules displayed in Vector UI
-            vectorContentRules: {           // Keyword push rules displayed in Vector UI
+            masterPushRule: undefined, // The master rule ('.m.rule.master')
+            vectorPushRules: [], // HS default push rules displayed in Vector UI
+            vectorContentRules: { // Keyword push rules displayed in Vector UI
                 vectorState: PushRuleVectorState.ON,
                 rules: [],
             },
-            externalPushRules: [],          // Push rules (except content rule) that have been defined outside Vector UI
-            externalContentRules: [],        // Keyword push rules that have been defined outside Vector UI
+            externalPushRules: [], // Push rules (except content rule) that have been defined outside Vector UI
+            externalContentRules: [], // Keyword push rules that have been defined outside Vector UI
         };
     },
 
@@ -290,7 +290,7 @@ module.exports = React.createClass({
         for (const i in this.state.vectorContentRules.rules) {
             const rule = this.state.vectorContentRules.rules[i];
 
-            let enabled, actions;
+            let enabled; let actions;
             switch (newPushRuleVectorState) {
                 case PushRuleVectorState.ON:
                     if (rule.actions.length !== 1) {
diff --git a/src/notifications/StandardActions.js b/src/notifications/StandardActions.js
index 22a8f1db40..30d6ea5975 100644
--- a/src/notifications/StandardActions.js
+++ b/src/notifications/StandardActions.js
@@ -16,9 +16,9 @@ limitations under the License.
 
 'use strict';
 
-var NotificationUtils = require('./NotificationUtils');
+const NotificationUtils = require('./NotificationUtils');
 
-var encodeActions = NotificationUtils.encodeActions;
+const encodeActions = NotificationUtils.encodeActions;
 
 module.exports = {
     ACTION_NOTIFY: encodeActions({notify: true}),
diff --git a/src/settings/SettingsStore.js b/src/settings/SettingsStore.js
index cb6d83e884..bb3f412a9b 100644
--- a/src/settings/SettingsStore.js
+++ b/src/settings/SettingsStore.js
@@ -248,7 +248,7 @@ export default class SettingsStore {
         if (actualValue !== undefined && actualValue !== null) return actualValue;
         return calculatedValue;
     }
-    /* eslint-disable valid-jsdoc */    //https://github.com/eslint/eslint/issues/7307
+    /* eslint-disable valid-jsdoc */ //https://github.com/eslint/eslint/issues/7307
     /**
      * Sets the value for a setting. The room ID is optional if the setting is not being
      * set for a particular room, otherwise it should be supplied. The value may be null
diff --git a/src/settings/controllers/SettingController.js b/src/settings/controllers/SettingController.js
index 0ebe0042e6..a7d0ccf21a 100644
--- a/src/settings/controllers/SettingController.js
+++ b/src/settings/controllers/SettingController.js
@@ -23,7 +23,6 @@ limitations under the License.
  * intended to handle environmental factors for specific settings.
  */
 export default class SettingController {
-
     /**
      * Gets the overridden value for the setting, if any. This must return null if the
      * value is not to be overridden, otherwise it must return the new value.
diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js
index 67c0c13be7..c670161dbc 100644
--- a/src/stores/RoomListStore.js
+++ b/src/stores/RoomListStore.js
@@ -23,7 +23,6 @@ import Unread from '../Unread';
  * the RoomList.
  */
 class RoomListStore extends Store {
-
     static _listOrders = {
         "m.favourite": "manual",
         "im.vector.fake.invite": "recent",
diff --git a/src/utils/DMRoomMap.js b/src/utils/DMRoomMap.js
index bb5e4d706b..af65b6f001 100644
--- a/src/utils/DMRoomMap.js
+++ b/src/utils/DMRoomMap.js
@@ -93,7 +93,7 @@ export default class DMRoomMap {
                         return {userId, roomId};
                     }
                 }
-            }).filter((ids) => !!ids);  //filter out
+            }).filter((ids) => !!ids); //filter out
             // these are actually all legit self-chats
             // bail out
             if (!guessedUserIdsThatChanged.length) {
diff --git a/src/utils/DecryptFile.js b/src/utils/DecryptFile.js
index 92c2e3644d..ea0e4c3fb0 100644
--- a/src/utils/DecryptFile.js
+++ b/src/utils/DecryptFile.js
@@ -77,7 +77,7 @@ const ALLOWED_BLOB_MIMETYPES = {
     'audio/x-pn-wav': true,
     'audio/flac': true,
     'audio/x-flac': true,
-}
+};
 
 /**
  * Decrypt a file attached to a matrix event.

From 5f3b03c85aa512be8575c32b14c0d7d004ba6076 Mon Sep 17 00:00:00 2001
From: Aaron Raimist <aaron@raim.ist>
Date: Fri, 26 Oct 2018 23:00:51 -0500
Subject: [PATCH 27/34] Fix a few no-useless-escape lint errors

Signed-off-by: Aaron Raimist <aaron@raim.ist>
---
 src/components/views/dialogs/CreateGroupDialog.js          | 2 +-
 src/components/views/elements/Pill.js                      | 2 +-
 src/components/views/room_settings/RelatedGroupSettings.js | 2 +-
 src/phonenumber.js                                         | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/components/views/dialogs/CreateGroupDialog.js b/src/components/views/dialogs/CreateGroupDialog.js
index 6e14d1cf66..882d323449 100644
--- a/src/components/views/dialogs/CreateGroupDialog.js
+++ b/src/components/views/dialogs/CreateGroupDialog.js
@@ -57,7 +57,7 @@ export default React.createClass({
         let error = null;
         if (!this.state.groupId) {
             error = _t("Community IDs cannot be empty.");
-        } else if (!/^[a-z0-9=_\-\.\/]*$/.test(this.state.groupId)) {
+        } else if (!/^[a-z0-9=_\-./]*$/.test(this.state.groupId)) {
             error = _t("Community IDs may only contain characters a-z, 0-9, or '=_-./'");
         }
         this.setState({
diff --git a/src/components/views/elements/Pill.js b/src/components/views/elements/Pill.js
index af7e8c4ae3..0d0949f07c 100644
--- a/src/components/views/elements/Pill.js
+++ b/src/components/views/elements/Pill.js
@@ -29,7 +29,7 @@ const REGEX_MATRIXTO = new RegExp(MATRIXTO_URL_PATTERN);
 
 // For URLs of matrix.to links in the timeline which have been reformatted by
 // HttpUtils transformTags to relative links. This excludes event URLs (with `[^\/]*`)
-const REGEX_LOCAL_MATRIXTO = /^#\/(?:user|room|group)\/(([#!@+])[^\/]*)$/;
+const REGEX_LOCAL_MATRIXTO = /^#\/(?:user|room|group)\/(([#!@+])[^/]*)$/;
 
 const Pill = React.createClass({
     statics: {
diff --git a/src/components/views/room_settings/RelatedGroupSettings.js b/src/components/views/room_settings/RelatedGroupSettings.js
index 65132b9b74..4bad5ca806 100644
--- a/src/components/views/room_settings/RelatedGroupSettings.js
+++ b/src/components/views/room_settings/RelatedGroupSettings.js
@@ -22,7 +22,7 @@ import { _t } from '../../../languageHandler';
 import Modal from '../../../Modal';
 import isEqual from 'lodash/isEqual';
 
-const GROUP_ID_REGEX = /\+\S+\:\S+/;
+const GROUP_ID_REGEX = /\+\S+:\S+/;
 
 module.exports = React.createClass({
     displayName: 'RelatedGroupSettings',
diff --git a/src/phonenumber.js b/src/phonenumber.js
index aaf018ba26..3b7e4ba4f1 100644
--- a/src/phonenumber.js
+++ b/src/phonenumber.js
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-const PHONE_NUMBER_REGEXP = /^[0-9 -\.]+$/;
+const PHONE_NUMBER_REGEXP = /^[0-9 -.]+$/;
 
 /*
  * Do basic validation to determine if the given input could be

From e61e7f2626c8ddecb04a8d3a2bb61e5c71709e7b Mon Sep 17 00:00:00 2001
From: Aaron Raimist <aaron@raim.ist>
Date: Fri, 26 Oct 2018 23:03:57 -0500
Subject: [PATCH 28/34] Fix eslint --fix

Signed-off-by: Aaron Raimist <aaron@raim.ist>
---
 src/VelocityBounce.js | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/src/VelocityBounce.js b/src/VelocityBounce.js
index 732550cfcb..b9513249b8 100644
--- a/src/VelocityBounce.js
+++ b/src/VelocityBounce.js
@@ -4,9 +4,7 @@ const Velocity = require('velocity-vector');
 // We only use easeOutBounce (easeInBounce is just sort of nonsensical)
 function bounce( p ) {
     let pow2;
-
-
-let bounce = 4;
+    let bounce = 4;
 
     while ( p < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) {
         // just sets pow2

From d54bf13fab8d7dea00f3ae96e8d23cc87a9eaead Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Sat, 27 Oct 2018 11:06:52 +0100
Subject: [PATCH 29/34] Hide all the time travel.

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
 res/css/views/elements/_RoleButton.scss | 2 +-
 res/css/views/rooms/_RoomList.scss      | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/res/css/views/elements/_RoleButton.scss b/res/css/views/elements/_RoleButton.scss
index 094e0b9b1b..41cd11d7c1 100644
--- a/res/css/views/elements/_RoleButton.scss
+++ b/res/css/views/elements/_RoleButton.scss
@@ -1,5 +1,5 @@
 /*
-Copyright 2107 Vector Creations Ltd
+Copyright 2017 Vector Creations Ltd
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
diff --git a/res/css/views/rooms/_RoomList.scss b/res/css/views/rooms/_RoomList.scss
index 581016d5ba..a346a06a20 100644
--- a/res/css/views/rooms/_RoomList.scss
+++ b/res/css/views/rooms/_RoomList.scss
@@ -1,6 +1,6 @@
 /*
 Copyright 2015, 2016 OpenMarket Ltd
-Copyright 2107 Vector Creations Ltd
+Copyright 2017 Vector Creations Ltd
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.

From 8a3824b9ca89320fde880d5a139275126870e58b Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Sat, 27 Oct 2018 11:27:43 +0100
Subject: [PATCH 30/34] click-through svg on tag tile context menu to make it
 less weird

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
 res/css/views/context_menus/_TagTileContextMenu.scss | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/res/css/views/context_menus/_TagTileContextMenu.scss b/res/css/views/context_menus/_TagTileContextMenu.scss
index 759b92bd68..799151b198 100644
--- a/res/css/views/context_menus/_TagTileContextMenu.scss
+++ b/res/css/views/context_menus/_TagTileContextMenu.scss
@@ -25,6 +25,10 @@ limitations under the License.
     line-height: 16px;
 }
 
+.mx_TagTileContextMenu_item object {
+    pointer-events: none;
+}
+
 
 .mx_TagTileContextMenu_item_icon {
     padding-right: 8px;

From 9933a3cc63a887479176da7e9077cc0949f82bcd Mon Sep 17 00:00:00 2001
From: David Baker <dave@matrix.org>
Date: Fri, 26 Oct 2018 17:01:44 +0100
Subject: [PATCH 31/34] Fix autoreplacement of ascii emoji

More slate API updates in code paths I didn't test

Fixes https://github.com/vector-im/riot-web/issues/7509
---
 src/components/views/rooms/MessageComposerInput.js | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js
index 04f9299825..c92413f052 100644
--- a/src/components/views/rooms/MessageComposerInput.js
+++ b/src/components/views/rooms/MessageComposerInput.js
@@ -544,7 +544,7 @@ export default class MessageComposerInput extends React.Component {
 
         if (editorState.startText !== null) {
             const text = editorState.startText.text;
-            const currentStartOffset = editorState.startOffset;
+            const currentStartOffset = editorState.selection.start.offset;
 
             // Automatic replacement of plaintext emoji to Unicode emoji
             if (SettingsStore.getValue('MessageComposerInput.autoReplaceEmoji')) {
@@ -558,11 +558,11 @@ export default class MessageComposerInput extends React.Component {
 
                     const range = Range.create({
                         anchor: {
-                            key: editorState.selection.startKey,
+                            key: editorState.startText.key,
                             offset: currentStartOffset - emojiMatch[1].length - 1,
                         },
                         focus: {
-                            key: editorState.selection.startKey,
+                            key: editorState.startText.key,
                             offset: currentStartOffset - 1,
                         },
                     });

From eb22b9141ac3d6f3f6884334cef03fafa8e93f34 Mon Sep 17 00:00:00 2001
From: David Baker <dave@matrix.org>
Date: Mon, 29 Oct 2018 14:03:19 +0000
Subject: [PATCH 32/34] released js-sdk

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 63f275a609..eb8f88ef22 100644
--- a/package.json
+++ b/package.json
@@ -75,7 +75,7 @@
     "linkifyjs": "^2.1.6",
     "lodash": "^4.13.1",
     "lolex": "2.3.2",
-    "matrix-js-sdk": "0.12.1-rc.1",
+    "matrix-js-sdk": "0.12.1",
     "optimist": "^0.6.1",
     "pako": "^1.0.5",
     "prop-types": "^15.5.8",

From 2077678088576fea34b4bd8f007324435b285f65 Mon Sep 17 00:00:00 2001
From: David Baker <dave@matrix.org>
Date: Mon, 29 Oct 2018 14:06:41 +0000
Subject: [PATCH 33/34] Prepare changelog for v0.14.2

---
 CHANGELOG.md | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index b7e5ca7adf..dae09ee2f1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,10 @@
+Changes in [0.14.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.14.2) (2018-10-29)
+=====================================================================================================
+[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.14.2-rc.1...v0.14.2)
+
+ * Fix autoreplacement of ascii emoji
+   [\#2258](https://github.com/matrix-org/matrix-react-sdk/pull/2258)
+
 Changes in [0.14.2-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.14.2-rc.1) (2018-10-24)
 ===============================================================================================================
 [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.14.1...v0.14.2-rc.1)

From c5004ffbe3589756fcc575f35028397c1f4a7000 Mon Sep 17 00:00:00 2001
From: David Baker <dave@matrix.org>
Date: Mon, 29 Oct 2018 14:07:03 +0000
Subject: [PATCH 34/34] v0.14.2

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index eb8f88ef22..7d5af70dc3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "matrix-react-sdk",
-  "version": "0.14.2-rc.1",
+  "version": "0.14.2",
   "description": "SDK for matrix.org using React",
   "author": "matrix.org",
   "repository": {