From 738313384609a8ed05072c0a97723c0a9350d648 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 19 Oct 2018 13:30:38 -0600 Subject: [PATCH 01/33] 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 Date: Fri, 19 Oct 2018 16:22:20 -0600 Subject: [PATCH 02/33] 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 Date: Wed, 24 Oct 2018 11:20:39 +0100 Subject: [PATCH 03/33] 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 Date: Wed, 24 Oct 2018 11:23:12 +0100 Subject: [PATCH 04/33] 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 Date: Wed, 24 Oct 2018 11:23:57 +0100 Subject: [PATCH 05/33] 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 Date: Wed, 24 Oct 2018 16:57:16 -0600 Subject: [PATCH 06/33] 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 Date: Wed, 24 Oct 2018 18:01:08 -0600 Subject: [PATCH 07/33] 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 54ff5d8f256cfb1c34f518430ab62ab6df9c6ba0 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 25 Oct 2018 15:21:18 -0600 Subject: [PATCH 08/33] 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 Date: Thu, 25 Oct 2018 15:22:28 -0600 Subject: [PATCH 09/33] 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 Date: Thu, 25 Oct 2018 15:22:52 -0600 Subject: [PATCH 10/33] 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 Date: Thu, 25 Oct 2018 15:23:15 -0600 Subject: [PATCH 11/33] 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 Date: Thu, 25 Oct 2018 15:59:42 -0600 Subject: [PATCH 12/33] 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" Date: Fri, 26 Oct 2018 14:15:16 +0200 Subject: [PATCH 13/33] 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 --- 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 =
+ body =
{ this.state.mode.getLabel() }
Room ID: { this.props.roomId }
@@ -634,7 +634,7 @@ export default class DevtoolsDialog extends React.Component { } else { const classes = "mx_DevTools_RoomStateExplorer_button"; body =
-
+
{ _t('Toolbox') }
Room ID: { this.props.roomId }
From 156ffd1334fd465ded3c99dbd643264e47a9557d Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 26 Oct 2018 17:01:44 +0100 Subject: [PATCH 14/33] 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 Date: Fri, 26 Oct 2018 10:07:21 -0600 Subject: [PATCH 15/33] 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 = ""; - if (olmVersion !== undefined) { + if (olmVersion) { olmVersionString = `${olmVersion[0]}.${olmVersion[1]}.${olmVersion[2]}`; } From c389540522067978298eaf487596ee0fd30948a2 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 26 Oct 2018 10:22:18 -0600 Subject: [PATCH 16/33] 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 Date: Fri, 26 Oct 2018 10:25:23 -0600 Subject: [PATCH 17/33] 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 Date: Fri, 26 Oct 2018 12:21:02 -0600 Subject: [PATCH 18/33] 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 Date: Fri, 26 Oct 2018 12:21:27 -0600 Subject: [PATCH 19/33] 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 Date: Fri, 26 Oct 2018 19:47:53 -0600 Subject: [PATCH 20/33] 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 Date: Fri, 26 Oct 2018 20:34:06 -0600 Subject: [PATCH 21/33] 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 Date: Fri, 26 Oct 2018 20:49:01 -0600 Subject: [PATCH 22/33] 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 Date: Fri, 26 Oct 2018 22:07:10 -0500 Subject: [PATCH 23/33] Update babel-eslint to 8.1.1 Signed-off-by: Aaron Raimist --- 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 Date: Fri, 26 Oct 2018 22:48:11 -0500 Subject: [PATCH 24/33] Update more eslint related packages Signed-off-by: Aaron Raimist --- 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 Date: Fri, 26 Oct 2018 22:50:35 -0500 Subject: [PATCH 25/33] eslint --fix src/ Signed-off-by: Aaron Raimist --- .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 ; } }, 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 (
@@ -69,5 +68,5 @@ module.exports = React.createClass({
); - } + }, }); 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 {