From d8dedae0841b4df5c398c447015d1c9fce2ef85f Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 20 Jun 2016 16:30:51 +0100 Subject: [PATCH 01/28] Fix /join to be consistent with the other code Plus a number of other tidyups: * Fix /join to dispatch a view_room for the room alias with the additional auto_join parameter * Make RoomView automatically join the room if the auto_join parameter is true and the user isn't already in it * Tidy up RoomView's peeking code, also fixing https://github.com/vector-im/vector-web/issues/1220 in react-sdk (although it still requires a synapse change to actually fix, but react-sdk does 'the right thing'). * Remove duplication of usage text from /join command * Amalgamate MatrixChat::_viewRoom's many, many parameters into an object and sort out case consistency a little. --- src/SlashCommands.js | 37 ++++------------ src/components/structures/MatrixChat.js | 58 +++++++++++++------------ src/components/structures/RoomView.js | 57 ++++++++++++++---------- 3 files changed, 73 insertions(+), 79 deletions(-) diff --git a/src/SlashCommands.js b/src/SlashCommands.js index e4c0d5973a..d4e63018d3 100644 --- a/src/SlashCommands.js +++ b/src/SlashCommands.js @@ -132,46 +132,25 @@ var commands = { }), // Join a room - join: new Command("join", "", function(room_id, args) { + join: new Command("join", "#alias:domain", function(room_id, args) { if (args) { var matches = args.match(/^(\S+)$/); if (matches) { var room_alias = matches[1]; if (room_alias[0] !== '#') { - return reject("Usage: /join #alias:domain"); + return reject(this.getUsage()); } if (!room_alias.match(/:/)) { room_alias += ':' + MatrixClientPeg.get().getDomain(); } - // Try to find a room with this alias - // XXX: do we need to do this? Doesn't the JS SDK suppress duplicate attempts to join the same room? - var foundRoom = MatrixTools.getRoomForAlias( - MatrixClientPeg.get().getRooms(), - room_alias - ); + dis.dispatch({ + action: 'view_room', + room_alias: room_alias, + auto_join: true, + }); - if (foundRoom) { // we've already joined this room, view it if it's not archived. - var me = foundRoom.getMember(MatrixClientPeg.get().credentials.userId); - if (me && me.membership !== "leave") { - dis.dispatch({ - action: 'view_room', - room_id: foundRoom.roomId - }); - return success(); - } - } - - // otherwise attempt to join this alias. - return success( - MatrixClientPeg.get().joinRoom(room_alias).then( - function(room) { - dis.dispatch({ - action: 'view_room', - room_id: room.roomId - }); - }) - ); + return success(); } } return reject(this.getUsage()); diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 1973fedbcd..65cb6a3ef4 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -403,10 +403,7 @@ module.exports = React.createClass({ // known to be in (eg. user clicks on a room in the recents panel), supply the ID // If the user is clicking on a room in the context of the alias being presented // to them, supply the room alias. If both are supplied, the room ID will be ignored. - this._viewRoom( - payload.room_id, payload.room_alias, payload.show_settings, payload.event_id, - payload.third_party_invite, payload.oob_data - ); + this._viewRoom(payload); break; case 'view_prev_room': roomIndexDelta = -1; @@ -423,7 +420,7 @@ module.exports = React.createClass({ } roomIndex = (roomIndex + roomIndexDelta) % allRooms.length; if (roomIndex < 0) roomIndex = allRooms.length - 1; - this._viewRoom(allRooms[roomIndex].roomId); + this._viewRoom({ room_id: allRooms[roomIndex].room_id }); break; case 'view_indexed_room': var allRooms = RoomListSorter.mostRecentActivityFirst( @@ -431,7 +428,7 @@ module.exports = React.createClass({ ); var roomIndex = payload.roomIndex; if (allRooms[roomIndex]) { - this._viewRoom(allRooms[roomIndex].roomId); + this._viewRoom({ room_id: allRooms[roomIndex].roomId }); } break; case 'view_user_settings': @@ -491,39 +488,45 @@ module.exports = React.createClass({ // switch view to the given room // - // eventId is optional and will cause a switch to the context of that - // particular event. - // @param {Object} thirdPartyInvite Object containing data about the third party + // @param {Object} room_info Object containing data about the room to be joined + // @param {string} room_info.room_id ID of the room to join. One of room_id or room_alias must be given. + // @param {string} room_info.room_alias Alias of the room to join. One of room_id or room_alias must be given. + // @param {boolean} room_info.auto_join If true, automatically attempt to join the room if not already a member. + // @param {string} room_info.show_settings ?? + // @param {string} room_info.event_id ID of the event in this room to show: this will cause a switch to the + // context of that particular event. Optional. + // @param {Object} room_info.third_party_invite Object containing data about the third party // we received to join the room, if any. - // @param {string} thirdPartyInvite.inviteSignUrl 3pid invite sign URL - // @param {string} thirdPartyInvite.invitedwithEmail The email address the invite was sent to - // @param {Object} oob_data Object of additional data about the room + // @param {string} room_info.third_party_invite.inviteSignUrl 3pid invite sign URL + // @param {string} room_info.third_party_invite.invitedwithEmail The email address the invite was sent to + // @param {Object} room_info.oob_data Object of additional data about the room // that has been passed out-of-band (eg. // room name and avatar from an invite email) - _viewRoom: function(roomId, roomAlias, showSettings, eventId, thirdPartyInvite, oob_data) { + _viewRoom: function(room_info) { // before we switch room, record the scroll state of the current room this._updateScrollMap(); this.focusComposer = true; var newState = { - initialEventId: eventId, - highlightedEventId: eventId, + initialEventId: room_info.event_id, + highlightedEventId: room_info.event_id, initialEventPixelOffset: undefined, page_type: this.PageTypes.RoomView, - thirdPartyInvite: thirdPartyInvite, - roomOobData: oob_data, - currentRoomAlias: roomAlias, + thirdPartyInvite: room_info.third_party_invite, + roomOobData: room_info.oob_data, + currentRoomAlias: room_info.room_alias, + autoJoin: room_info.auto_join, }; - if (!roomAlias) { - newState.currentRoomId = roomId; + if (!room_info.room_alias) { + newState.currentRoomId = room_info.room_id; } // if we aren't given an explicit event id, look for one in the // scrollStateMap. - if (!eventId) { - var scrollState = this.scrollStateMap[roomId]; + if (!room_info.event_id) { + var scrollState = this.scrollStateMap[room_info.room_id]; if (scrollState) { newState.initialEventId = scrollState.focussedEvent; newState.initialEventPixelOffset = scrollState.pixelOffset; @@ -536,8 +539,8 @@ module.exports = React.createClass({ // the new screen yet (we won't be showing it yet) // The normal case where this happens is navigating // to the room in the URL bar on page load. - var presentedId = roomAlias || roomId; - var room = MatrixClientPeg.get().getRoom(roomId); + var presentedId = room_info.room_alias || room_info.room_id; + var room = MatrixClientPeg.get().getRoom(room_info.room_id); if (room) { var theAlias = MatrixTools.getDisplayAliasForRoom(room); if (theAlias) presentedId = theAlias; @@ -553,15 +556,15 @@ module.exports = React.createClass({ // Tinter.tint(color_scheme.primary_color, color_scheme.secondary_color); } - if (eventId) { - presentedId += "/"+eventId; + if (room_info.event_id) { + presentedId += "/"+event_id; } this.notifyNewScreen('room/'+presentedId); newState.ready = true; } this.setState(newState); - if (this.refs.roomView && showSettings) { + if (this.refs.roomView && room_info.showSettings) { this.refs.roomView.showSettings(true); } }, @@ -1030,6 +1033,7 @@ module.exports = React.createClass({ { this.setState({ roomLoading: false, @@ -172,11 +172,11 @@ module.exports = React.createClass({ room: room, roomLoading: !room, hasUnsentMessages: this._hasUnsentMessages(room), - }, this._updatePeeking); + }, this._onHaveRoom); } }, - _updatePeeking: function() { + _onHaveRoom: function() { // if this is an unknown room then we're in one of three states: // - This is a room we can peek into (search engine) (we can /peek) // - This is a room we can publicly join or were invited to. (we can /join) @@ -187,29 +187,40 @@ module.exports = React.createClass({ // Note that peeking works by room ID and room ID only, as opposed to joining // which must be by alias or invite wherever possible (peeking currently does // not work over federation). - if (!this.state.room && this.state.roomId) { - console.log("Attempting to peek into room %s", this.state.roomId); - MatrixClientPeg.get().peekInRoom(this.state.roomId).then((room) => { - this.setState({ - room: room, - roomLoading: false, - }); - this._onRoomLoaded(room); - }, (err) => { - // This won't necessarily be a MatrixError, but we duck-type - // here and say if it's got an 'errcode' key with the right value, - // it means we can't peek. - if (err.errcode == "M_GUEST_ACCESS_FORBIDDEN") { - // This is fine: the room just isn't peekable (we assume). + // NB. We peek if we are not in the room, although if we try to peek into + // a room in which we have a member event (ie. we've left) synapse will just + // send us the same data as we get in the sync (ie. the last events we saw). + var my_member = this.state.room ? this.state.room.getMember(MatrixClientPeg.get().credentials.userId) : null; + var user_is_in_room = my_member ? my_member.membership == 'join' : false; + + if (!user_is_in_room && this.state.roomId) { + if (this.props.autoJoin) { + this.onJoinButtonClicked(); + } else if (this.state.roomId) { + console.log("Attempting to peek into room %s", this.state.roomId); + + MatrixClientPeg.get().peekInRoom(this.state.roomId).then((room) => { this.setState({ + room: room, roomLoading: false, }); - } else { - throw err; - } - }).done(); - } else if (this.state.room) { + this._onRoomLoaded(room); + }, (err) => { + // This won't necessarily be a MatrixError, but we duck-type + // here and say if it's got an 'errcode' key with the right value, + // it means we can't peek. + if (err.errcode == "M_GUEST_ACCESS_FORBIDDEN") { + // This is fine: the room just isn't peekable (we assume). + this.setState({ + roomLoading: false, + }); + } else { + throw err; + } + }).done(); + } + } else if (user_is_in_room) { MatrixClientPeg.get().stopPeeking(); this._onRoomLoaded(this.state.room); } @@ -992,7 +1003,7 @@ module.exports = React.createClass({ this.setState({ rejecting: true }); - MatrixClientPeg.get().leave(this.props.roomAddress).done(function() { + MatrixClientPeg.get().leave(this.props.roomId).done(function() { dis.dispatch({ action: 'view_next_room' }); self.setState({ rejecting: false From 4d5fbfc5b1383a5c0045a5815b245f02cea6beae Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 20 Jun 2016 17:11:46 +0100 Subject: [PATCH 02/28] Remove now unused MatrixTools.GetRoomForAlias --- src/MatrixTools.js | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/src/MatrixTools.js b/src/MatrixTools.js index 372f17f69c..b003d8d2d7 100644 --- a/src/MatrixTools.js +++ b/src/MatrixTools.js @@ -24,30 +24,5 @@ module.exports = { getDisplayAliasForRoom: function(room) { return room.getCanonicalAlias() || room.getAliases()[0]; }, - - /** - * Given a list of room objects, return the room which has the given alias, - * else null. - */ - getRoomForAlias: function(rooms, room_alias) { - var room; - for (var i = 0; i < rooms.length; i++) { - var aliasEvents = rooms[i].currentState.getStateEvents( - "m.room.aliases" - ); - for (var j = 0; j < aliasEvents.length; j++) { - var aliases = aliasEvents[j].getContent().aliases || []; - for (var k = 0; k < aliases.length; k++) { - if (aliases[k] === room_alias) { - room = rooms[i]; - break; - } - } - if (room) { break; } - } - if (room) { break; } - } - return room || null; - } } From 4c214119b2b65e83988e0ef801351cf6cdd875be Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 20 Jun 2016 18:05:58 +0100 Subject: [PATCH 03/28] Fix PR feedback --- src/components/structures/MatrixChat.js | 22 +++++++++++----------- src/components/structures/RoomView.js | 10 +++++++--- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 65cb6a3ef4..8dc70ca2aa 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -489,17 +489,17 @@ module.exports = React.createClass({ // switch view to the given room // // @param {Object} room_info Object containing data about the room to be joined - // @param {string} room_info.room_id ID of the room to join. One of room_id or room_alias must be given. - // @param {string} room_info.room_alias Alias of the room to join. One of room_id or room_alias must be given. - // @param {boolean} room_info.auto_join If true, automatically attempt to join the room if not already a member. - // @param {string} room_info.show_settings ?? - // @param {string} room_info.event_id ID of the event in this room to show: this will cause a switch to the - // context of that particular event. Optional. - // @param {Object} room_info.third_party_invite Object containing data about the third party + // @param {string=} room_info.room_id ID of the room to join. One of room_id or room_alias must be given. + // @param {string=} room_info.room_alias Alias of the room to join. One of room_id or room_alias must be given. + // @param {boolean=} room_info.auto_join If true, automatically attempt to join the room if not already a member. + // @param {boolean=} room_info.show_settings Makes RoomView show the room settings dialog. + // @param {string=} room_info.event_id ID of the event in this room to show: this will cause a switch to the + // context of that particular event. + // @param {Object=} room_info.third_party_invite Object containing data about the third party // we received to join the room, if any. - // @param {string} room_info.third_party_invite.inviteSignUrl 3pid invite sign URL - // @param {string} room_info.third_party_invite.invitedwithEmail The email address the invite was sent to - // @param {Object} room_info.oob_data Object of additional data about the room + // @param {string=} room_info.third_party_invite.inviteSignUrl 3pid invite sign URL + // @param {string=} room_info.third_party_invite.invitedEmail The email address the invite was sent to + // @param {Object=} room_info.oob_data Object of additional data about the room // that has been passed out-of-band (eg. // room name and avatar from an invite email) _viewRoom: function(room_info) { @@ -557,7 +557,7 @@ module.exports = React.createClass({ } if (room_info.event_id) { - presentedId += "/"+event_id; + presentedId += "/"+room_info.event_id; } this.notifyNewScreen('room/'+presentedId); newState.ready = true; diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 8440a56f2d..4219210734 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -191,8 +191,12 @@ module.exports = React.createClass({ // NB. We peek if we are not in the room, although if we try to peek into // a room in which we have a member event (ie. we've left) synapse will just // send us the same data as we get in the sync (ie. the last events we saw). - var my_member = this.state.room ? this.state.room.getMember(MatrixClientPeg.get().credentials.userId) : null; - var user_is_in_room = my_member ? my_member.membership == 'join' : false; + var user_is_in_room = null; + if (this.state.room) { + user_is_in_room = this.state.room.hasMembershipState( + MatrixClientPeg.get().credentials.userId, 'join' + ); + } if (!user_is_in_room && this.state.roomId) { if (this.props.autoJoin) { @@ -1003,7 +1007,7 @@ module.exports = React.createClass({ this.setState({ rejecting: true }); - MatrixClientPeg.get().leave(this.props.roomId).done(function() { + MatrixClientPeg.get().leave(this.state.roomId).done(function() { dis.dispatch({ action: 'view_next_room' }); self.setState({ rejecting: false From fc06ebcc63fa4653269e161b10a34d214c0999a9 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 20 Jun 2016 18:43:56 +0100 Subject: [PATCH 04/28] Fix view_next_room --- src/components/structures/MatrixChat.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 8dc70ca2aa..37bcbe7cb0 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -420,7 +420,7 @@ module.exports = React.createClass({ } roomIndex = (roomIndex + roomIndexDelta) % allRooms.length; if (roomIndex < 0) roomIndex = allRooms.length - 1; - this._viewRoom({ room_id: allRooms[roomIndex].room_id }); + this._viewRoom({ room_id: allRooms[roomIndex].roomId }); break; case 'view_indexed_room': var allRooms = RoomListSorter.mostRecentActivityFirst( From 213e284edfeba1c3e7b5da64f309360fd6506e41 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 21 Jun 2016 11:05:37 +0100 Subject: [PATCH 05/28] Fix https://github.com/vector-im/vector-web/issues/1679 --- src/components/structures/MatrixChat.js | 30 ++++++++++++++++--------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 1973fedbcd..4c59d16474 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -108,10 +108,14 @@ module.exports = React.createClass({ return window.localStorage.getItem("mx_hs_url"); } else { - return this.props.config.default_hs_url || "https://matrix.org"; + return this.getDefaultHsUrl(); } }, + getDefaultHsUrl() { + return this.props.config.default_hs_url || "https://matrix.org"; + }, + getFallbackHsUrl: function() { return this.props.config.fallback_hs_url; }, @@ -126,10 +130,14 @@ module.exports = React.createClass({ return window.localStorage.getItem("mx_is_url"); } else { - return this.props.config.default_is_url || "https://vector.im" + return this.getDefaultIsUrl(); } }, + getDefaultIsUrl() { + return this.props.config.default_is_url || "https://vector.im"; + }, + componentWillMount: function() { this.favicon = new Favico({animation: 'none'}); }, @@ -151,8 +159,8 @@ module.exports = React.createClass({ this.onLoggedIn({ userId: this.props.startingQueryParams.guest_user_id, accessToken: this.props.startingQueryParams.guest_access_token, - homeserverUrl: this.props.config.default_hs_url, - identityServerUrl: this.props.config.default_is_url, + homeserverUrl: this.getDefaultHsUrl(), + identityServerUrl: this.getDefaultIsUrl(), guest: true }); } @@ -1109,8 +1117,8 @@ module.exports = React.createClass({ email={this.props.startingQueryParams.email} username={this.state.upgradeUsername} guestAccessToken={this.state.guestAccessToken} - defaultHsUrl={this.props.config.default_hs_url} - defaultIsUrl={this.props.config.default_is_url} + defaultHsUrl={this.getDefaultHsUrl()} + defaultIsUrl={this.getDefaultIsUrl()} brand={this.props.config.brand} customHsUrl={this.getCurrentHsUrl()} customIsUrl={this.getCurrentIsUrl()} @@ -1124,8 +1132,8 @@ module.exports = React.createClass({ } else if (this.state.screen == 'forgot_password') { return ( ); From d3265ab9707db33a0d8c92d017c100d3cde6a261 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 21 Jun 2016 17:46:55 +0100 Subject: [PATCH 06/28] Redundant getDeafultHs() This now can never be falsey so no point checking it --- src/components/structures/MatrixChat.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 4c59d16474..24ebd95f60 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1150,7 +1150,7 @@ module.exports = React.createClass({ customIsUrl={this.getCurrentIsUrl()} fallbackHsUrl={this.getFallbackHsUrl()} onForgotPasswordClick={this.onForgotPasswordClick} - onLoginAsGuestClick={this.props.enableGuest && this.props.config && this.getDefaultHsUrl() ? this._registerAsGuest.bind(this, true) : undefined} + onLoginAsGuestClick={this.props.enableGuest && this.props.config && this._registerAsGuest.bind(this, true)} onCancelClick={ this.state.guestCreds ? this.onReturnToGuestClick : null } /> ); From 5195140ff8e6950fccc95b9bd3239a39aefc822a Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 21 Jun 2016 19:50:03 +0100 Subject: [PATCH 07/28] reposition Login spinner --- src/SdkConfig.js | 47 ++++++++++++++++++++++++ src/components/structures/login/Login.js | 5 ++- 2 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 src/SdkConfig.js diff --git a/src/SdkConfig.js b/src/SdkConfig.js new file mode 100644 index 0000000000..46c2b818b8 --- /dev/null +++ b/src/SdkConfig.js @@ -0,0 +1,47 @@ +/* +Copyright 2016 OpenMarket Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +var DEFAULTS = { + // URL to a page we show in an iframe to configure integrations + //integrations_ui_url: "https://scalar.vector.im/", + integrations_ui_url: "http://127.0.0.1:5051/", + // Base URL to the REST interface of the integrations server + //integrations_rest_url: "https://scalar.vector.im/api", + integrations_rest_url: "http://127.0.0.1:5050", +}; + +class SdkConfig { + + static get() { + return global.mxReactSdkConfig; + } + + static put(cfg) { + var defaultKeys = Object.keys(DEFAULTS); + for (var i = 0; i < defaultKeys.length; ++i) { + if (cfg[defaultKeys[i]] === undefined) { + cfg[defaultKeys[i]] = DEFAULTS[defaultKeys[i]]; + } + } + global.mxReactSdkConfig = cfg; + } + + static unset() { + global.mxReactSdkConfig = undefined; + } +} + +module.exports = SdkConfig; diff --git a/src/components/structures/login/Login.js b/src/components/structures/login/Login.js index aa0c42dc98..a73ad30f87 100644 --- a/src/components/structures/login/Login.js +++ b/src/components/structures/login/Login.js @@ -232,7 +232,9 @@ module.exports = React.createClass({displayName: 'Login',
-

Sign in

+

Sign in + { loader } +

{ this.componentForStep(this._getCurrentFlowStep()) }
- { loader } { this.state.errorText }
From a04f03669c0c24a38d58dc6f631f45a4d53e3dd9 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Thu, 23 Jun 2016 10:36:16 +0100 Subject: [PATCH 08/28] RoomSettings: refactor permissions calculations The logic for calculating who had permission for what was impenetrable (and wrong, in parts), so rewrite it to be a bit clearer. --- src/components/views/rooms/RoomSettings.js | 72 ++++++++-------------- 1 file changed, 25 insertions(+), 47 deletions(-) diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js index 8764700c5a..d6b8b67f25 100644 --- a/src/components/views/rooms/RoomSettings.js +++ b/src/components/views/rooms/RoomSettings.js @@ -22,6 +22,13 @@ var Modal = require('../../../Modal'); var ObjectUtils = require("../../../ObjectUtils"); var dis = require("../../../dispatcher"); +// parse a string as an integer; if the input is undefined, or cannot be parsed +// as an integer, return a default. +function parseIntWithDefault(val, def) { + var res = parseInt(val); + return isNaN(res) ? def : res; +} + module.exports = React.createClass({ displayName: 'RoomSettings', @@ -251,7 +258,7 @@ module.exports = React.createClass({ power_levels_changed: true }); }, - + _yankValueFromEvent: function(stateEventType, keyName, defaultValue) { // E.g.("m.room.name","name") would yank the "name" content key from "m.room.name" var event = this.props.room.currentState.getStateEvents(stateEventType, ''); @@ -286,7 +293,7 @@ module.exports = React.createClass({ }, }); }, - + _onRoomAccessRadioToggle: function(ev) { // join_rule @@ -368,58 +375,29 @@ module.exports = React.createClass({ var EditableText = sdk.getComponent('elements.EditableText'); var PowerSelector = sdk.getComponent('elements.PowerSelector'); - var power_levels = this.props.room.currentState.getStateEvents('m.room.power_levels', ''); - var events_levels = (power_levels ? power_levels.getContent().events : {}) || {}; var cli = MatrixClientPeg.get(); var roomState = this.props.room.currentState; var user_id = cli.credentials.userId; - if (power_levels) { - power_levels = power_levels.getContent(); + var power_level_event = roomState.getStateEvents('m.room.power_levels', ''); + var power_levels = power_level_event ? power_level_event.getContent() : {}; + var events_levels = power_levels.events || {}; + var user_levels = power_levels.users || {}; - var ban_level = parseInt(power_levels.ban); - var kick_level = parseInt(power_levels.kick); - var redact_level = parseInt(power_levels.redact); - var invite_level = parseInt(power_levels.invite || 0); - var send_level = parseInt(power_levels.events_default || 0); - var state_level = parseInt(power_levels.state_default || 50); - var default_user_level = parseInt(power_levels.users_default || 0); + var ban_level = parseIntWithDefault(power_levels.ban, 50); + var kick_level = parseIntWithDefault(power_levels.kick, 50); + var redact_level = parseIntWithDefault(power_levels.redact, 50); + var invite_level = parseIntWithDefault(power_levels.invite, 50); + var send_level = parseIntWithDefault(power_levels.events_default, 0); + var state_level = power_level_event ? parseIntWithDefault(power_levels.state_default, 50) : 0; + var default_user_level = parseIntWithDefault(power_levels.users_default, 0); - if (power_levels.ban == undefined) ban_level = 50; - if (power_levels.kick == undefined) kick_level = 50; - if (power_levels.redact == undefined) redact_level = 50; - - var user_levels = power_levels.users || {}; - - var current_user_level = user_levels[user_id]; - if (current_user_level == undefined) current_user_level = default_user_level; - - var power_level_level = events_levels["m.room.power_levels"]; - if (power_level_level == undefined) { - power_level_level = state_level; - } - - var can_change_levels = current_user_level >= power_level_level; - } else { - var ban_level = 50; - var kick_level = 50; - var redact_level = 50; - var invite_level = 0; - var send_level = 0; - var state_level = 0; - var default_user_level = 0; - - var user_levels = []; - var events_levels = []; - - var current_user_level = 0; - - var power_level_level = 0; - - var can_change_levels = false; + var current_user_level = user_levels[user_id]; + if (current_user_level === undefined) { + current_user_level = default_user_level; } - var state_default = (parseInt(power_levels ? power_levels.state_default : 0) || 0); + var can_change_levels = roomState.mayClientSendStateEvent("m.room.power_levels", cli); var canSetTag = !cli.isGuest(); @@ -488,7 +466,7 @@ module.exports = React.createClass({ var tagsSection = null; if (canSetTag || self.state.tags) { - var tagsSection = + var tagsSection =
Tagged as: { canSetTag ? (tags.map(function(tag, i) { From 7a7d7c0e023b62e9afd774232ea107cfeaa0d535 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Thu, 23 Jun 2016 11:15:55 +0100 Subject: [PATCH 09/28] Fix a pair of warnings from RoomSettings - initialise the 'publish' checkbox correctly so react doesn't grumble about it turning from uncontrolled into controlled - PowerSelector's 'controlled' property isn't really required, so mark it as such. --- src/components/views/elements/PowerSelector.js | 7 ++++++- src/components/views/rooms/RoomSettings.js | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/views/elements/PowerSelector.js b/src/components/views/elements/PowerSelector.js index 3c65ca707c..993f2b965a 100644 --- a/src/components/views/elements/PowerSelector.js +++ b/src/components/views/elements/PowerSelector.js @@ -34,10 +34,15 @@ module.exports = React.createClass({ propTypes: { value: React.PropTypes.number.isRequired, + // if true, the + Enable encryption (warning: cannot be disabled again!) + + ); + } + + return ( +
+

Encryption

+ + {button} +
+ ); + }, + + render: function() { // TODO: go through greying out things you don't have permission to change // (or turning them into informative stuff) @@ -587,10 +637,6 @@ module.exports = React.createClass({ Members only (since they joined)
-
@@ -655,6 +701,8 @@ module.exports = React.createClass({ { bannedUsersSection } + { this._renderEncryptionSection() } +

Advanced

This room's internal ID is { this.props.room.roomId } From 6283b200fb629c04990f09d58092482fa81f2808 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Thu, 23 Jun 2016 13:21:55 +0100 Subject: [PATCH 11/28] Remove /encrypt command Now that we have the room setting to enable encryption, the /encrypt command is not only redundant, but confusing, since it's in conflict with the room setting. --- src/SlashCommands.js | 22 +--------------------- src/encryption.js | 38 -------------------------------------- 2 files changed, 1 insertion(+), 59 deletions(-) delete mode 100644 src/encryption.js diff --git a/src/SlashCommands.js b/src/SlashCommands.js index d4e63018d3..759a95c8ff 100644 --- a/src/SlashCommands.js +++ b/src/SlashCommands.js @@ -17,7 +17,6 @@ limitations under the License. var MatrixClientPeg = require("./MatrixClientPeg"); var MatrixTools = require("./MatrixTools"); var dis = require("./dispatcher"); -var encryption = require("./encryption"); var Tinter = require("./Tinter"); @@ -82,32 +81,13 @@ var commands = { return success( MatrixClientPeg.get().setRoomAccountData( room_id, "org.matrix.room.color_scheme", colorScheme - ) + ) ); } } return reject(this.getUsage()); }), - encrypt: new Command("encrypt", "", function(room_id, args) { - if (args == "on") { - var client = MatrixClientPeg.get(); - var members = client.getRoom(room_id).currentState.members; - var user_ids = Object.keys(members); - return success( - encryption.enableEncryption(client, room_id, user_ids) - ); - } - if (args == "off") { - var client = MatrixClientPeg.get(); - return success( - encryption.disableEncryption(client, room_id) - ); - - } - return reject(this.getUsage()); - }), - // Change the room topic topic: new Command("topic", "", function(room_id, args) { if (args) { diff --git a/src/encryption.js b/src/encryption.js deleted file mode 100644 index cbe92d36de..0000000000 --- a/src/encryption.js +++ /dev/null @@ -1,38 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -function enableEncyption(client, roomId, members) { - members = members.slice(0); - members.push(client.credentials.userId); - // TODO: Check the keys actually match what keys the user has. - // TODO: Don't redownload keys each time. - return client.downloadKeys(members, "forceDownload").then(function(res) { - return client.setRoomEncryption(roomId, { - algorithm: "m.olm.v1.curve25519-aes-sha2", - members: members, - }); - }) -} - -function disableEncryption(client, roomId) { - return client.disableRoomEncryption(roomId); -} - - -module.exports = { - enableEncryption: enableEncyption, - disableEncryption: disableEncryption, -} From e046f5359fa39a914aaa8c8fd15af19d6e5cbabd Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Thu, 23 Jun 2016 14:08:45 +0100 Subject: [PATCH 12/28] CreateRoom: remove reference to encryption module The CreateRoom structure isn't currently used, but contained a reference to the (now defunct) encryption module; remove the reference for now. --- src/components/structures/CreateRoom.js | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/components/structures/CreateRoom.js b/src/components/structures/CreateRoom.js index f6e2baeaf2..e7585e3640 100644 --- a/src/components/structures/CreateRoom.js +++ b/src/components/structures/CreateRoom.js @@ -24,7 +24,6 @@ var PresetValues = { Custom: "custom", }; var q = require('q'); -var encryption = require("../../encryption"); var sdk = require('../../index'); module.exports = React.createClass({ @@ -108,17 +107,8 @@ module.exports = React.createClass({ var deferred = cli.createRoom(options); - var response; - if (this.state.encrypt) { - deferred = deferred.then(function(res) { - response = res; - return encryption.enableEncryption( - cli, response.room_id, options.invite - ); - }).then(function() { - return q(response) } - ); + // TODO } this.setState({ From 5cda2a6802271d48f9d1b1fef3a46e2d48b00569 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 23 Jun 2016 14:38:08 +0100 Subject: [PATCH 13/28] Disable colour output in jenkins script As it really confuses jenkins --- jenkins.sh | 1 + package.json | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/jenkins.sh b/jenkins.sh index eeb7d7d56e..26d434a504 100755 --- a/jenkins.sh +++ b/jenkins.sh @@ -2,6 +2,7 @@ set -e +export KARMAFLAGS="--no-colors" export NVM_DIR="/home/jenkins/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" nvm use 4 diff --git a/package.json b/package.json index d3021fb1ac..e8c7dd4f97 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,8 @@ "start": "babel src -w -d lib --source-maps", "clean": "rimraf lib", "prepublish": "npm run build && git rev-parse HEAD > git-revision.txt", - "test": "karma start --browsers PhantomJS", - "test-multi": "karma start --single-run=false" + "test": "karma start $KARMAFLAGS --browsers PhantomJS", + "test-multi": "karma start $KARMAFLAGS --single-run=false" }, "dependencies": { "classnames": "^2.1.2", From dc50a0f24a37633c0c8af5452c247ed62ade639f Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 23 Jun 2016 16:20:40 +0100 Subject: [PATCH 14/28] Add logging to TimelinePanel test to see where it fails on jenkins --- test/components/structures/TimelinePanel-test.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/components/structures/TimelinePanel-test.js b/test/components/structures/TimelinePanel-test.js index c201c647c6..045ccd70b7 100644 --- a/test/components/structures/TimelinePanel-test.js +++ b/test/components/structures/TimelinePanel-test.js @@ -220,12 +220,14 @@ describe('TimelinePanel', function() { for (var i = 0; i < N_EVENTS; i++) { timeline.addEvent(mkMessage()); } + console.log("added events to timeline"); var scrollDefer; var panel = ReactDOM.render( {scrollDefer.resolve()}} />, parentDiv ); + console.log("TimelinePanel rendered"); var messagePanel = ReactTestUtils.findRenderedComponentWithType( panel, sdk.getComponent('structures.MessagePanel')); @@ -246,6 +248,7 @@ describe('TimelinePanel', function() { // need to go further return backPaginate(); } + console.log("paginated to end."); // hopefully, we got to the start of the timeline expect(messagePanel.props.backPaginating).toBe(false); @@ -259,6 +262,7 @@ describe('TimelinePanel', function() { expect(messagePanel.props.suppressFirstDateSeparator).toBe(true); // back-paginate until we hit the start + console.log("back paginating..."); return backPaginate(); }).then(() => { expect(messagePanel.props.suppressFirstDateSeparator).toBe(false); From a1dd4274201db740a945ae728fd12cb6dfe390ad Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Thu, 23 Jun 2016 17:27:23 +0100 Subject: [PATCH 15/28] Implement device blocking This is the react-sdk part of https://github.com/matrix-org/matrix-js-sdk/pull/146. It adds 'Block'/'Unblock' buttons to the device list, and updates the deviceVerified listeners to listen for deviceVerificationChanged instead. Also adds an extra
to the deviceinfo section to help me with the CSS. --- src/components/views/rooms/EventTile.js | 8 ++- .../views/rooms/MemberDeviceInfo.js | 57 ++++++++++++++++--- src/components/views/rooms/MemberInfo.js | 10 ++-- 3 files changed, 60 insertions(+), 15 deletions(-) diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index ff02139d87..70dfe8ac33 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -139,7 +139,8 @@ module.exports = React.createClass({ componentDidMount: function() { this._suppressReadReceiptAnimation = false; - MatrixClientPeg.get().on("deviceVerified", this.onDeviceVerified); + MatrixClientPeg.get().on("deviceVerificationChanged", + this.onDeviceVerificationChanged); }, componentWillReceiveProps: function (nextProps) { @@ -163,11 +164,12 @@ module.exports = React.createClass({ componentWillUnmount: function() { var client = MatrixClientPeg.get(); if (client) { - client.removeListener("deviceVerified", this.onDeviceVerified); + client.removeListener("deviceVerificationChanged", + this.onDeviceVerificationChanged); } }, - onDeviceVerified: function(userId, device) { + onDeviceVerificationChanged: function(userId, device) { if (userId == this.props.mxEvent.getSender()) { this._verifyEvent(this.props.mxEvent); } diff --git a/src/components/views/rooms/MemberDeviceInfo.js b/src/components/views/rooms/MemberDeviceInfo.js index ebc2ab1c32..b7ddf9b2ce 100644 --- a/src/components/views/rooms/MemberDeviceInfo.js +++ b/src/components/views/rooms/MemberDeviceInfo.js @@ -36,32 +36,73 @@ module.exports = React.createClass({ ); }, + onBlockClick: function() { + MatrixClientPeg.get().setDeviceBlocked( + this.props.userId, this.props.device.id, true + ); + }, + + onUnblockClick: function() { + MatrixClientPeg.get().setDeviceBlocked( + this.props.userId, this.props.device.id, false + ); + }, + render: function() { - var indicator = null, button = null; - if (this.props.device.verified) { - indicator = ( -
+ var indicator = null, blockButton = null, verifyButton = null; + if (this.props.device.blocked) { + blockButton = ( +
+ Unblock +
); - button = ( + } else { + blockButton = ( +
+ Block +
+ ); + } + + if (this.props.device.verified) { + verifyButton = (
Unverify
); } else { - button = ( + verifyButton = (
Verify
); } + + if (this.props.device.blocked) { + indicator = ( +
+ ); + } else if (this.props.device.verified) { + indicator = ( +
+ ); + + } else { + indicator = ( +
?
+ ); + } + return (
{this.props.device.id}
-
{this.props.device.key}
{indicator} - {button} + {verifyButton} + {blockButton}
); }, diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index 97cfecc9e1..66501abfa5 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -70,7 +70,7 @@ module.exports = React.createClass({ componentDidMount: function() { this._updateStateForNewMember(this.props.member); - MatrixClientPeg.get().on("deviceVerified", this.onDeviceVerified); + MatrixClientPeg.get().on("deviceVerificationChanged", this.onDeviceVerificationChanged); }, componentWillReceiveProps: function(newProps) { @@ -82,14 +82,14 @@ module.exports = React.createClass({ componentWillUnmount: function() { var client = MatrixClientPeg.get(); if (client) { - client.removeListener("deviceVerified", this.onDeviceVerified); + client.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged); } if (this._cancelDeviceList) { this._cancelDeviceList(); } }, - onDeviceVerified: function(userId, device) { + onDeviceVerificationChanged: function(userId, device) { if (userId == this.props.member.userId) { // no need to re-download the whole thing; just update our copy of // the list. @@ -535,7 +535,9 @@ module.exports = React.createClass({ return (

Devices

- {devComponents} +
+ {devComponents} +
); }, From 1c280baddebc21286224302c745eef77f885a4b4 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 23 Jun 2016 18:36:44 +0100 Subject: [PATCH 16/28] Increase timeout on TimelinePanel test Since it looks like this timeout sometimes isn't sufficient to scroll all the way up when jenkins is very busy --- test/components/structures/TimelinePanel-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/components/structures/TimelinePanel-test.js b/test/components/structures/TimelinePanel-test.js index 045ccd70b7..de547b1779 100644 --- a/test/components/structures/TimelinePanel-test.js +++ b/test/components/structures/TimelinePanel-test.js @@ -210,7 +210,7 @@ describe('TimelinePanel', function() { var N_EVENTS = 600; // sadly, loading all those events takes a while - this.timeout(N_EVENTS * 20); + this.timeout(N_EVENTS * 40); // client.getRoom is called a /lot/ in this test, so replace // sinon's spy with a fast noop. From 98c03869a7af19932d4d4d7ecc9704ede4471bf8 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 24 Jun 2016 15:34:07 +0100 Subject: [PATCH 17/28] Display an error message if room not found Fixes https://github.com/vector-im/vector-web/issues/1012 --- src/components/structures/RoomView.js | 10 +++++--- src/components/views/rooms/RoomPreviewBar.js | 27 +++++++++++++++++--- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 4219210734..78d9351171 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -116,6 +116,7 @@ module.exports = React.createClass({ callState: null, guestsCanJoin: false, canPeek: false, + roomLoadError: null, // this is true if we are fully scrolled-down, and are looking at // the end of the live timeline. It has the effect of hiding the @@ -163,6 +164,7 @@ module.exports = React.createClass({ }, (err) => { this.setState({ roomLoading: false, + roomLoadError: err, }); }); } else { @@ -1282,6 +1284,7 @@ module.exports = React.createClass({ // We have no room object for this room, only the ID. // We've got to this room by following a link, possibly a third party invite. + var room_alias = this.props.roomAddress[0] == '#' ? this.props.roomAddress : null; return (
@@ -1400,7 +1404,7 @@ module.exports = React.createClass({ invitedEmail = this.props.thirdPartyInvite.invitedEmail; } aux = ( - +
+ { error } +
+
+ ); + } + else { + var name = this.props.room ? this.props.room.name : (this.props.room_alias || ""); name = name ? { name } : "a room"; joinBlock = (
From c016eb78c8cb46c317b083275ddb9096c91eca03 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 28 Jun 2016 14:28:05 +0100 Subject: [PATCH 18/28] Fix user links 'Start chat' was broken on the sidebar if the panel was displayed by clicking on a link to a user. This adds null checking for the hack that we use to display users in the member panel. --- src/components/views/rooms/MemberInfo.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index 66501abfa5..ddd0d1f6c6 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -358,10 +358,15 @@ module.exports = React.createClass({ ]; var existingRoomId; - var currentRoom = MatrixClientPeg.get().getRoom(this.props.member.roomId); - var currentMembers = currentRoom.getJoinedMembers(); + // roomId can be null here because of a hack in MatrixChat.onUserClick where we + // abuse this to view users rather than room members. + var currentMembers; + if (this.props.member.roomId) { + var currentRoom = MatrixClientPeg.get().getRoom(this.props.member.roomId); + currentMembers = currentRoom.getJoinedMembers(); + } // if we're currently in a 1:1 with this user, start a new chat - if (currentMembers.length === 2 && + if (currentMembers && currentMembers.length === 2 && userIds.indexOf(currentMembers[0].userId) !== -1 && userIds.indexOf(currentMembers[1].userId) !== -1) { From 548c392236078b29121256e5f9c86819bebb0300 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 28 Jun 2016 14:59:45 +0100 Subject: [PATCH 19/28] PR feedback --- src/components/structures/RoomView.js | 3 +++ src/components/views/rooms/RoomPreviewBar.js | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 78d9351171..776461e0e5 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -116,6 +116,9 @@ module.exports = React.createClass({ callState: null, guestsCanJoin: false, canPeek: false, + + // If we failed to load information about the room, + // store the error here. roomLoadError: null, // this is true if we are fully scrolled-down, and are looking at diff --git a/src/components/views/rooms/RoomPreviewBar.js b/src/components/views/rooms/RoomPreviewBar.js index f1b15490d7..2d83b14931 100644 --- a/src/components/views/rooms/RoomPreviewBar.js +++ b/src/components/views/rooms/RoomPreviewBar.js @@ -41,6 +41,9 @@ module.exports = React.createClass({ canPreview: React.PropTypes.bool, spinner: React.PropTypes.bool, room: React.PropTypes.object, + + // The alias that was used to access this room, if appropriate + roomAlias: React.PropTypes.object, }, getDefaultProps: function() { From e8337b21198ed4d4a1c72ffd571736273a6fce55 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 28 Jun 2016 17:11:47 +0100 Subject: [PATCH 20/28] More PR feedback --- src/components/structures/RoomView.js | 1 + src/components/views/rooms/RoomPreviewBar.js | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 776461e0e5..7946f723e8 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -117,6 +117,7 @@ module.exports = React.createClass({ guestsCanJoin: false, canPeek: false, + // error object, as from the matrix client/server API // If we failed to load information about the room, // store the error here. roomLoadError: null, diff --git a/src/components/views/rooms/RoomPreviewBar.js b/src/components/views/rooms/RoomPreviewBar.js index 2d83b14931..fee3dd8b4f 100644 --- a/src/components/views/rooms/RoomPreviewBar.js +++ b/src/components/views/rooms/RoomPreviewBar.js @@ -43,6 +43,8 @@ module.exports = React.createClass({ room: React.PropTypes.object, // The alias that was used to access this room, if appropriate + // If given, this will be how the room is referred to (eg. + // in error messages). roomAlias: React.PropTypes.object, }, From 174caceabf1010a1c89ec393af0bafa0f9039709 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Wed, 29 Jun 2016 16:57:59 +0900 Subject: [PATCH 21/28] Use lastActiveAgo to reorder member list --- src/components/views/rooms/MemberList.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index 328f9774c7..da1ef04364 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -445,6 +445,8 @@ module.exports = React.createClass({ // console.log("comparing ts: " + lastActiveTsA + " and " + lastActiveTsB); + var lastActiveTsA = userA && userA.lastActiveAgo ? userA.lastActiveAgo : 0; + var lastActiveTsB = userB && userB.lastActiveAgo ? userB.lastActiveAgo : 0; return lastActiveTsB - lastActiveTsA; }, From 72a1d5a0deea3a1be0a6305f7b244438ddc28afa Mon Sep 17 00:00:00 2001 From: theworldbright Date: Wed, 29 Jun 2016 16:58:08 +0900 Subject: [PATCH 22/28] Remove unused comments --- src/components/views/rooms/MemberList.js | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index da1ef04364..f0b979edd9 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -423,28 +423,6 @@ module.exports = React.createClass({ if (userA.currentlyActive && !userB.currentlyActive) return -1; if (!userA.currentlyActive && userB.currentlyActive) return 1; - // For now, let's just order things by timestamp. It's really annoying - // that a user disappears from sight just because they temporarily go offline - /* - var presenceMap = { - online: 3, - unavailable: 2, - offline: 1 - }; - - var presenceOrdA = userA ? presenceMap[userA.presence] : 0; - var presenceOrdB = userB ? presenceMap[userB.presence] : 0; - - if (presenceOrdA != presenceOrdB) { - return presenceOrdB - presenceOrdA; - } - */ - - var lastActiveTsA = userA && userA.lastActiveTs ? userA.lastActiveTs : 0; - var lastActiveTsB = userB && userB.lastActiveTs ? userB.lastActiveTs : 0; - - // console.log("comparing ts: " + lastActiveTsA + " and " + lastActiveTsB); - var lastActiveTsA = userA && userA.lastActiveAgo ? userA.lastActiveAgo : 0; var lastActiveTsB = userB && userB.lastActiveAgo ? userB.lastActiveAgo : 0; return lastActiveTsB - lastActiveTsA; From 69cb0a8f1c726d62d5ba2dc09b879af4cc8b814e Mon Sep 17 00:00:00 2001 From: theworldbright Date: Wed, 29 Jun 2016 17:08:17 +0900 Subject: [PATCH 23/28] Switch ordering of Idle users --- src/components/views/rooms/MemberList.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index f0b979edd9..9c0fe401db 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -423,9 +423,9 @@ module.exports = React.createClass({ if (userA.currentlyActive && !userB.currentlyActive) return -1; if (!userA.currentlyActive && userB.currentlyActive) return 1; - var lastActiveTsA = userA && userA.lastActiveAgo ? userA.lastActiveAgo : 0; - var lastActiveTsB = userB && userB.lastActiveAgo ? userB.lastActiveAgo : 0; - return lastActiveTsB - lastActiveTsA; + var lastActiveTsA = userA && userA.lastActiveAgo ? userA.lastActiveAgo : 9999999999; + var lastActiveTsB = userB && userB.lastActiveAgo ? userB.lastActiveAgo : 9999999999; + return lastActiveTsA - lastActiveTsB; }, onSearchQueryChanged: function(input) { From b3d871aa6a1bfc1595fb7b4bba65e052fac6a5ad Mon Sep 17 00:00:00 2001 From: theworldbright Date: Wed, 29 Jun 2016 17:33:41 +0900 Subject: [PATCH 24/28] Add back comment --- src/components/views/rooms/MemberList.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index 9c0fe401db..28e6b97942 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -423,6 +423,8 @@ module.exports = React.createClass({ if (userA.currentlyActive && !userB.currentlyActive) return -1; if (!userA.currentlyActive && userB.currentlyActive) return 1; + // For now, let's just order things by timestamp. It's really annoying + // that a user disappears from sight just because they temporarily go offline var lastActiveTsA = userA && userA.lastActiveAgo ? userA.lastActiveAgo : 9999999999; var lastActiveTsB = userB && userB.lastActiveAgo ? userB.lastActiveAgo : 9999999999; return lastActiveTsA - lastActiveTsB; From 7609b9eba8b6711ce806d7eea3693de3d8db86ae Mon Sep 17 00:00:00 2001 From: theworldbright Date: Wed, 29 Jun 2016 17:45:24 +0900 Subject: [PATCH 25/28] Simplify logic for timestamp ordering of memberlist --- src/components/views/rooms/MemberList.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index 28e6b97942..20f60c80a8 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -425,9 +425,7 @@ module.exports = React.createClass({ // For now, let's just order things by timestamp. It's really annoying // that a user disappears from sight just because they temporarily go offline - var lastActiveTsA = userA && userA.lastActiveAgo ? userA.lastActiveAgo : 9999999999; - var lastActiveTsB = userB && userB.lastActiveAgo ? userB.lastActiveAgo : 9999999999; - return lastActiveTsA - lastActiveTsB; + return userB.getLastActiveTs() - userA.getLastActiveTs(); }, onSearchQueryChanged: function(input) { From 1c002866e8fbb0cb225fe0899ca08736c1101951 Mon Sep 17 00:00:00 2001 From: Aviral Dasgupta Date: Fri, 1 Jul 2016 23:08:51 +0530 Subject: [PATCH 26/28] feat: add and configure eslint --- .eslintrc | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++ .gitignore | 2 + package.json | 6 +++ 3 files changed, 112 insertions(+) create mode 100644 .eslintrc diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000000..761fd2af2b --- /dev/null +++ b/.eslintrc @@ -0,0 +1,104 @@ +{ + "parser": "babel-eslint", + "plugins": [ + "react", + "flowtype" + ], + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module", + "ecmaFeatures": { + "jsx": true, + "impliedStrict": true + } + }, + "env": { + "browser": true, + "amd": true, + "es6": true, + "node": true, + "mocha": true + }, + "extends": ["eslint:recommended", "plugin:react/recommended"], + "rules": { + "no-undef": ["warn"], + "global-strict": ["off"], + "no-extra-semi": ["warn"], + "no-underscore-dangle": ["off"], + "no-console": ["off"], + "no-unused-vars": ["off"], + "no-trailing-spaces": ["warn", { + "skipBlankLines": true + }], + "no-unreachable": ["warn"], + "no-spaced-func": ["warn"], + "no-new-func": ["error"], + "no-new-wrappers": ["error"], + "no-invalid-regexp": ["error"], + "no-extra-bind": ["error"], + "no-magic-numbers": ["error"], + "consistent-return": ["error"], + "valid-jsdoc": ["error"], + "no-use-before-define": ["error"], + "camelcase": ["warn"], + "array-callback-return": ["error"], + "dot-location": ["warn", "property"], + "guard-for-in": ["error"], + "no-useless-call": ["warn"], + "no-useless-escape": ["warn"], + "no-useless-concat": ["warn"], + "brace-style": ["warn", "1tbs"], + "comma-style": ["warn", "last"], + "space-before-function-paren": ["warn", "never"], + "space-before-blocks": ["warn", "always"], + "keyword-spacing": ["warn", { + "before": true, + "after": true + }], + + // dangling commas required, but only for multiline objects/arrays + "comma-dangle": ["warn", "always-multiline"], + // always === instead of ==, unless dealing with null/undefined + "eqeqeq": ["error", "smart"], + // always use curly braces, even with single statements + "curly": ["error", "all"], + // phasing out var in favour of let/const is a good idea + "no-var": ["warn"], + // always require semicolons + "semi": ["error", "always"], + // prefer rest and spread over the Old Ways + "prefer-spread": ["warn"], + "prefer-rest-params": ["warn"], + + /** react **/ + + // bind or arrow function in props causes performance issues + "react/jsx-no-bind": ["error"], + "react/jsx-key": ["error"], + "react/prefer-stateless-function": ["warn"], + "react/sort-comp": ["warn"], + + /** flowtype **/ + "flowtype/require-parameter-type": 1, + "flowtype/require-return-type": [ + 1, + "always", + { + "annotateUndefined": "never" + } + ], + "flowtype/space-after-type-colon": [ + 1, + "always" + ], + "flowtype/space-before-type-colon": [ + 1, + "never" + ] + }, + "settings": { + "flowtype": { + "onlyFilesWithFlowAnnotation": true + } + } +} diff --git a/.gitignore b/.gitignore index 8fdaf5903f..5139d614ad 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +npm-debug.log + /node_modules /lib diff --git a/package.json b/package.json index e8c7dd4f97..be5556013b 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,8 @@ "reskindex": "reskindex -h header", "build": "babel src -d lib --source-maps", "start": "babel src -w -d lib --source-maps", + "lint": "eslint", + "lintall": "eslint src/ test/", "clean": "rimraf lib", "prepublish": "npm run build && git rev-parse HEAD > git-revision.txt", "test": "karma start $KARMAFLAGS --browsers PhantomJS", @@ -51,8 +53,12 @@ "devDependencies": { "babel": "^5.8.23", "babel-core": "^5.8.38", + "babel-eslint": "^6.1.0", "babel-loader": "^5.4.0", "babel-polyfill": "^6.5.0", + "eslint": "^2.13.1", + "eslint-plugin-flowtype": "^2.3.0", + "eslint-plugin-react": "^5.2.2", "expect": "^1.16.0", "json-loader": "^0.5.3", "karma": "^0.13.22", From 21cc4cba9a8796f25954c3d85aaec6e12857cfe7 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 1 Jul 2016 19:30:53 +0100 Subject: [PATCH 27/28] Correct npm run lint command line --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index be5556013b..6c78c92b93 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "reskindex": "reskindex -h header", "build": "babel src -d lib --source-maps", "start": "babel src -w -d lib --source-maps", - "lint": "eslint", + "lint": "eslint src/", "lintall": "eslint src/ test/", "clean": "rimraf lib", "prepublish": "npm run build && git rev-parse HEAD > git-revision.txt", From 28e620f19b9ba68a55c0c4c90b70c2563da1c802 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 1 Jul 2016 19:38:14 +0100 Subject: [PATCH 28/28] Run eslint in jenkins script --- jenkins.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jenkins.sh b/jenkins.sh index 26d434a504..b318b586e2 100755 --- a/jenkins.sh +++ b/jenkins.sh @@ -15,6 +15,9 @@ npm install # run the mocha tests npm run test +# run eslint +npm run lint -- -f checkstyle -o eslint.xml || true + # delete the old tarball, if it exists rm -f matrix-react-sdk-*.tgz