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/jenkins.sh b/jenkins.sh index eeb7d7d56e..b318b586e2 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 @@ -14,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 diff --git a/package.json b/package.json index 82ac307710..fc3b1c8f24 100644 --- a/package.json +++ b/package.json @@ -16,10 +16,12 @@ "reskindex": "reskindex -h header", "build": "babel src -d lib --source-maps", "start": "babel src -w -d lib --source-maps", + "lint": "eslint src/", + "lintall": "eslint src/ test/", "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", @@ -55,8 +57,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", 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; - } } 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/SlashCommands.js b/src/SlashCommands.js index e4c0d5973a..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) { @@ -132,46 +112,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/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({ diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 1973fedbcd..dcaa82fc75 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 }); } @@ -403,10 +411,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 +428,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].roomId }); break; case 'view_indexed_room': var allRooms = RoomListSorter.mostRecentActivityFirst( @@ -431,7 +436,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 +496,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 {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} 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.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(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 +547,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 +564,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 += "/"+room_info.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 +1041,7 @@ module.exports = React.createClass({ ); diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 9d952e611e..bee038a9e2 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -119,6 +119,11 @@ 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, + // 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 // 'scroll to bottom' knob, among a couple of other things. @@ -161,10 +166,11 @@ module.exports = React.createClass({ roomId: result.room_id, roomLoading: !room, hasUnsentMessages: this._hasUnsentMessages(room), - }, this._updatePeeking); + }, this._onHaveRoom); }, (err) => { this.setState({ roomLoading: false, + roomLoadError: err, }); }); } else { @@ -174,11 +180,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) @@ -189,29 +195,44 @@ 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 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) { + 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); } @@ -999,7 +1020,7 @@ module.exports = React.createClass({ this.setState({ rejecting: true }); - MatrixClientPeg.get().leave(this.props.roomAddress).done(function() { + MatrixClientPeg.get().leave(this.state.roomId).done(function() { dis.dispatch({ action: 'view_next_room' }); self.setState({ rejecting: false @@ -1274,6 +1295,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 (
@@ -1392,7 +1415,7 @@ module.exports = React.createClass({ invitedEmail = this.props.thirdPartyInvite.invitedEmail; } aux = ( -
-

Sign in

+

Sign in + { loader } +

{ this.componentForStep(this._getCurrentFlowStep()) }
- { loader } { this.state.errorText }
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) @@ -368,58 +425,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 +516,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) { @@ -609,10 +637,6 @@ module.exports = React.createClass({ Members only (since they joined)
-
@@ -677,6 +701,8 @@ module.exports = React.createClass({ { bannedUsersSection } + { this._renderEncryptionSection() } +

Advanced

This room's internal ID is { this.props.room.roomId } 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, -} diff --git a/test/components/structures/TimelinePanel-test.js b/test/components/structures/TimelinePanel-test.js index c201c647c6..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. @@ -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);