diff --git a/.eslintrc.js b/.eslintrc.js
index 429aa24993..fd4d1da631 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -33,6 +33,8 @@ module.exports = {
// This just uses the react plugin to help eslint known when
// variables have been used in JSX
"react/jsx-uses-vars": "error",
+ // Don't mark React as unused if we're using JSX
+ "react/jsx-uses-react": "error",
// bind or arrow function in props causes performance issues
"react/jsx-no-bind": ["error", {
diff --git a/package.json b/package.json
index 883fdae8d5..e0c8e714c2 100644
--- a/package.json
+++ b/package.json
@@ -71,9 +71,10 @@
"isomorphic-fetch": "^2.2.1",
"linkifyjs": "^2.1.3",
"lodash": "^4.13.1",
- "matrix-js-sdk": "0.8.5",
+ "matrix-js-sdk": "0.9.0-rc.1",
"optimist": "^0.6.1",
"prop-types": "^15.5.8",
+ "querystring": "^0.2.0",
"react": "^15.4.0",
"react-addons-css-transition-group": "15.3.2",
"react-dom": "^15.4.0",
diff --git a/src/UnknownDeviceErrorHandler.js b/src/UnknownDeviceErrorHandler.js
index 664fe14eb5..e7d77b3b66 100644
--- a/src/UnknownDeviceErrorHandler.js
+++ b/src/UnknownDeviceErrorHandler.js
@@ -25,6 +25,7 @@ const onAction = function(payload) {
const UnknownDeviceDialog = sdk.getComponent('dialogs.UnknownDeviceDialog');
isDialogOpen = true;
Modal.createTrackedDialog('Unknown Device Error', '', UnknownDeviceDialog, {
+ devices: payload.err.devices,
room: payload.room,
onFinished: (r) => {
isDialogOpen = false;
diff --git a/src/UserSettingsStore.js b/src/UserSettingsStore.js
index c1d77b120b..8d60790812 100644
--- a/src/UserSettingsStore.js
+++ b/src/UserSettingsStore.js
@@ -26,10 +26,6 @@ import SdkConfig from './SdkConfig';
*/
const FEATURES = [
- {
- id: 'feature_groups',
- name: _td("Communities"),
- },
{
id: 'feature_pinning',
name: _td("Message Pinning"),
diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js
index caa5e7cb01..1b5ebb6b36 100644
--- a/src/components/structures/GroupView.js
+++ b/src/components/structures/GroupView.js
@@ -435,14 +435,15 @@ export default React.createClass({
},
componentWillMount: function() {
+ this._matrixClient = MatrixClientPeg.get();
+ this._matrixClient.on("Group.myMembership", this._onGroupMyMembership);
+
this._changeAvatarComponent = null;
this._initGroupStore(this.props.groupId, true);
-
- MatrixClientPeg.get().on("Group.myMembership", this._onGroupMyMembership);
},
componentWillUnmount: function() {
- MatrixClientPeg.get().removeListener("Group.myMembership", this._onGroupMyMembership);
+ this._matrixClient.removeListener("Group.myMembership", this._onGroupMyMembership);
this._groupStore.removeAllListeners();
},
@@ -464,11 +465,11 @@ export default React.createClass({
},
_initGroupStore: function(groupId, firstInit) {
- const group = MatrixClientPeg.get().getGroup(groupId);
+ const group = this._matrixClient.getGroup(groupId);
if (group && group.inviter && group.inviter.userId) {
this._fetchInviterProfile(group.inviter.userId);
}
- this._groupStore = GroupStoreCache.getGroupStore(MatrixClientPeg.get(), groupId);
+ this._groupStore = GroupStoreCache.getGroupStore(this._matrixClient, groupId);
this._groupStore.registerListener(() => {
const summary = this._groupStore.getSummary();
if (summary.profile) {
@@ -486,7 +487,7 @@ export default React.createClass({
groupRooms: this._groupStore.getGroupRooms(),
groupRoomsLoading: !this._groupStore.isStateReady(GroupStore.STATE_KEY.GroupRooms),
isUserMember: this._groupStore.getGroupMembers().some(
- (m) => m.userId === MatrixClientPeg.get().credentials.userId,
+ (m) => m.userId === this._matrixClient.credentials.userId,
),
error: null,
});
@@ -506,7 +507,7 @@ export default React.createClass({
this.setState({
inviterProfileBusy: true,
});
- MatrixClientPeg.get().getProfileInfo(userId).then((resp) => {
+ this._matrixClient.getProfileInfo(userId).then((resp) => {
this.setState({
inviterProfile: {
avatarUrl: resp.avatar_url,
@@ -571,7 +572,7 @@ export default React.createClass({
if (!file) return;
this.setState({uploadingAvatar: true});
- MatrixClientPeg.get().uploadContent(file).then((url) => {
+ this._matrixClient.uploadContent(file).then((url) => {
const newProfileForm = Object.assign(this.state.profileForm, { avatar_url: url });
this.setState({
uploadingAvatar: false,
@@ -591,7 +592,7 @@ export default React.createClass({
_onSaveClick: function() {
this.setState({saving: true});
const savePromise = this.state.isUserPrivileged ?
- MatrixClientPeg.get().setGroupProfile(this.props.groupId, this.state.profileForm) :
+ this._matrixClient.setGroupProfile(this.props.groupId, this.state.profileForm) :
Promise.resolve();
savePromise.then((result) => {
this.setState({
@@ -630,7 +631,7 @@ export default React.createClass({
_onRejectInviteClick: function() {
this.setState({membershipBusy: true});
- MatrixClientPeg.get().leaveGroup(this.props.groupId).then(() => {
+ this._matrixClient.leaveGroup(this.props.groupId).then(() => {
// don't reset membershipBusy here: wait for the membership change to come down the sync
}).catch((e) => {
this.setState({membershipBusy: false});
@@ -653,7 +654,7 @@ export default React.createClass({
if (!confirmed) return;
this.setState({membershipBusy: true});
- MatrixClientPeg.get().leaveGroup(this.props.groupId).then(() => {
+ this._matrixClient.leaveGroup(this.props.groupId).then(() => {
// don't reset membershipBusy here: wait for the membership change to come down the sync
}).catch((e) => {
this.setState({membershipBusy: false});
@@ -829,7 +830,7 @@ export default React.createClass({
const Spinner = sdk.getComponent("elements.Spinner");
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
- const group = MatrixClientPeg.get().getGroup(this.props.groupId);
+ const group = this._matrixClient.getGroup(this.props.groupId);
if (!group) return null;
if (group.myMembership === 'invite') {
@@ -839,7 +840,7 @@ export default React.createClass({
;
}
const httpInviterAvatar = this.state.inviterProfile ?
- MatrixClientPeg.get().mxcUrlToHttp(
+ this._matrixClient.mxcUrlToHttp(
this.state.inviterProfile.avatarUrl, 36, 36,
) : null;
diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js
index 5972611af8..268654103d 100644
--- a/src/components/structures/MatrixChat.js
+++ b/src/components/structures/MatrixChat.js
@@ -74,6 +74,17 @@ const VIEWS = {
LOGGED_IN: 6,
};
+// Actions that are redirected through the onboarding process prior to being
+// re-dispatched. NOTE: some actions are non-trivial and would require
+// re-factoring to be included in this list in future.
+const ONBOARDING_FLOW_STARTERS = [
+ 'view_user_settings',
+ 'view_create_chat',
+ 'view_create_room',
+ 'view_my_groups',
+ 'view_group',
+];
+
module.exports = React.createClass({
// we export this so that the integration tests can use it :-S
statics: {
@@ -377,6 +388,22 @@ module.exports = React.createClass({
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
+ // Start the onboarding process for certain actions
+ if (MatrixClientPeg.get() && MatrixClientPeg.get().isGuest() &&
+ ONBOARDING_FLOW_STARTERS.includes(payload.action)
+ ) {
+ // This will cause `payload` to be dispatched later, once a
+ // sync has reached the "prepared" state. Setting a matrix ID
+ // will cause a full login and sync and finally the deferred
+ // action will be dispatched.
+ dis.dispatch({
+ action: 'do_after_sync_prepared',
+ deferred_action: payload,
+ });
+ dis.dispatch({action: 'view_set_mxid'});
+ return;
+ }
+
switch (payload.action) {
case 'logout':
Lifecycle.logout();
@@ -466,16 +493,6 @@ module.exports = React.createClass({
this._viewIndexedRoom(payload.roomIndex);
break;
case 'view_user_settings':
- if (MatrixClientPeg.get().isGuest()) {
- dis.dispatch({
- action: 'do_after_sync_prepared',
- deferred_action: {
- action: 'view_user_settings',
- },
- });
- dis.dispatch({action: 'view_set_mxid'});
- break;
- }
this._setPage(PageTypes.UserSettings);
this.notifyNewScreen('settings');
break;
@@ -512,7 +529,7 @@ module.exports = React.createClass({
this._chatCreateOrReuse(payload.user_id, payload.go_home_on_cancel);
break;
case 'view_create_chat':
- this._createChat();
+ showStartChatInviteDialog();
break;
case 'view_invite':
showRoomInviteDialog(payload.roomId);
@@ -753,31 +770,7 @@ module.exports = React.createClass({
}).close;
},
- _createChat: function() {
- if (MatrixClientPeg.get().isGuest()) {
- dis.dispatch({
- action: 'do_after_sync_prepared',
- deferred_action: {
- action: 'view_create_chat',
- },
- });
- dis.dispatch({action: 'view_set_mxid'});
- return;
- }
- showStartChatInviteDialog();
- },
-
_createRoom: function() {
- if (MatrixClientPeg.get().isGuest()) {
- dis.dispatch({
- action: 'do_after_sync_prepared',
- deferred_action: {
- action: 'view_create_room',
- },
- });
- dis.dispatch({action: 'view_set_mxid'});
- return;
- }
const CreateRoomDialog = sdk.getComponent('dialogs.CreateRoomDialog');
Modal.createTrackedDialog('Create Room', '', CreateRoomDialog, {
onFinished: (shouldCreate, name, noFederate) => {
diff --git a/src/components/views/dialogs/ConfirmUserActionDialog.js b/src/components/views/dialogs/ConfirmUserActionDialog.js
index 64e25df5f1..78d084b709 100644
--- a/src/components/views/dialogs/ConfirmUserActionDialog.js
+++ b/src/components/views/dialogs/ConfirmUserActionDialog.js
@@ -15,6 +15,7 @@ limitations under the License.
*/
import React from 'react';
+import { MatrixClient } from 'matrix-js-sdk';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
import classnames from 'classnames';
@@ -35,6 +36,8 @@ export default React.createClass({
member: React.PropTypes.object,
// group member object. Supply either this or 'member'
groupMember: GroupMemberType,
+ // needed if a group member is specified
+ matrixClient: React.PropTypes.instanceOf(MatrixClient),
action: React.PropTypes.string.isRequired, // eg. 'Ban'
title: React.PropTypes.string.isRequired, // eg. 'Ban this user?'
@@ -104,10 +107,11 @@ export default React.createClass({
name = this.props.member.name;
userId = this.props.member.userId;
} else {
- // we don't get this info from the API yet
- avatar = ;
- name = this.props.groupMember.userId;
+ const httpAvatarUrl = this.props.groupMember.avatarUrl ?
+ this.props.matrixClient.mxcUrlToHttp(this.props.groupMember.avatarUrl, 48, 48) : null;
+ name = this.props.groupMember.displayname || this.props.groupMember.userId;
userId = this.props.groupMember.userId;
+ avatar = ;
}
return (
diff --git a/src/components/views/dialogs/UnknownDeviceDialog.js b/src/components/views/dialogs/UnknownDeviceDialog.js
index ff2accf0b0..ee8f307f76 100644
--- a/src/components/views/dialogs/UnknownDeviceDialog.js
+++ b/src/components/views/dialogs/UnknownDeviceDialog.js
@@ -48,9 +48,8 @@ function UserUnknownDeviceList(props) {
const {userId, userDevices} = props;
const deviceListEntries = Object.keys(userDevices).map((deviceId) =>
- ,
+ ,
);
return (
@@ -93,60 +92,26 @@ export default React.createClass({
propTypes: {
room: React.PropTypes.object.isRequired,
+ // map from userid -> deviceid -> deviceinfo
+ devices: React.PropTypes.object.isRequired,
onFinished: React.PropTypes.func.isRequired,
},
- componentWillMount: function() {
- this._unmounted = false;
-
- const roomMembers = this.props.room.getJoinedMembers().map((m) => {
- return m.userId;
- });
-
- this.setState({
- // map from userid -> deviceid -> deviceinfo
- devices: null,
- });
- MatrixClientPeg.get().downloadKeys(roomMembers, false).then((devices) => {
- if (this._unmounted) return;
-
- const unknownDevices = {};
- // This is all devices in this room, so find the unknown ones.
- Object.keys(devices).forEach((userId) => {
- Object.keys(devices[userId]).map((deviceId) => {
- const device = devices[userId][deviceId];
-
- if (device.isUnverified() && !device.isKnown()) {
- if (unknownDevices[userId] === undefined) {
- unknownDevices[userId] = {};
- }
- unknownDevices[userId][deviceId] = device;
- }
-
- // Given we've now shown the user the unknown device, it is no longer
- // unknown to them. Therefore mark it as 'known'.
- if (!device.isKnown()) {
- MatrixClientPeg.get().setDeviceKnown(userId, deviceId, true);
- }
- });
- });
-
- this.setState({
- devices: unknownDevices,
+ componentDidMount: function() {
+ // Given we've now shown the user the unknown device, it is no longer
+ // unknown to them. Therefore mark it as 'known'.
+ Object.keys(this.props.devices).forEach((userId) => {
+ Object.keys(this.props.devices[userId]).map((deviceId) => {
+ MatrixClientPeg.get().setDeviceKnown(userId, deviceId, true);
});
});
- },
- componentWillUnmount: function() {
- this._unmounted = true;
+ // XXX: temporary logging to try to diagnose
+ // https://github.com/vector-im/riot-web/issues/3148
+ console.log('Opening UnknownDeviceDialog');
},
render: function() {
- if (this.state.devices === null) {
- const Spinner = sdk.getComponent("elements.Spinner");
- return ;
- }
-
const client = MatrixClientPeg.get();
const blacklistUnverified = client.getGlobalBlacklistUnverifiedDevices() ||
this.props.room.getBlacklistUnverifiedDevices();
@@ -189,7 +154,7 @@ export default React.createClass({
{ warning }
{ _t("Unknown devices") }:
-
+