diff --git a/CHANGELOG.md b/CHANGELOG.md
index a3edbce2dd..f668ca3f97 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,10 @@
+Changes in [0.13.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.13.2) (2018-08-23)
+=====================================================================================================
+[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.13.1...v0.13.2)
+
+ * Don't crash if the value of a room tag is null
+ [\#2135](https://github.com/matrix-org/matrix-react-sdk/pull/2135)
+
Changes in [0.13.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.13.1) (2018-08-20)
=====================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.13.1-rc.1...v0.13.1)
diff --git a/package.json b/package.json
index f0a6004366..1bdced5caf 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "matrix-react-sdk",
- "version": "0.13.1",
+ "version": "0.13.2",
"description": "SDK for matrix.org using React",
"author": "matrix.org",
"repository": {
diff --git a/res/css/_components.scss b/res/css/_components.scss
index 13fefd5885..0e40b40a29 100644
--- a/res/css/_components.scss
+++ b/res/css/_components.scss
@@ -39,6 +39,7 @@
@import "./views/dialogs/_EncryptedEventDialog.scss";
@import "./views/dialogs/_GroupAddressPicker.scss";
@import "./views/dialogs/_QuestionDialog.scss";
+@import "./views/dialogs/_RoomUpgradeDialog.scss";
@import "./views/dialogs/_SetEmailDialog.scss";
@import "./views/dialogs/_SetMxIdDialog.scss";
@import "./views/dialogs/_SetPasswordDialog.scss";
@@ -100,6 +101,7 @@
@import "./views/rooms/_RoomSettings.scss";
@import "./views/rooms/_RoomTile.scss";
@import "./views/rooms/_RoomTooltip.scss";
+@import "./views/rooms/_RoomUpgradeWarningBar.scss";
@import "./views/rooms/_SearchBar.scss";
@import "./views/rooms/_SearchableEntityList.scss";
@import "./views/rooms/_Stickers.scss";
diff --git a/res/css/views/dialogs/_RoomUpgradeDialog.scss b/res/css/views/dialogs/_RoomUpgradeDialog.scss
new file mode 100644
index 0000000000..2e3ac5fdea
--- /dev/null
+++ b/res/css/views/dialogs/_RoomUpgradeDialog.scss
@@ -0,0 +1,19 @@
+/*
+Copyright 2018 New Vector Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+.mx_RoomUpgradeDialog {
+ padding-right: 70px;
+}
diff --git a/res/css/views/rooms/_RoomSettings.scss b/res/css/views/rooms/_RoomSettings.scss
index 4013af4c7c..f04042ea77 100644
--- a/res/css/views/rooms/_RoomSettings.scss
+++ b/res/css/views/rooms/_RoomSettings.scss
@@ -20,6 +20,7 @@ limitations under the License.
margin-bottom: 20px;
}
+.mx_RoomSettings_upgradeButton,
.mx_RoomSettings_leaveButton,
.mx_RoomSettings_unbanButton {
@mixin mx_DialogButton;
@@ -27,11 +28,16 @@ limitations under the License.
margin-right: 8px;
}
+.mx_RoomSettings_upgradeButton,
.mx_RoomSettings_leaveButton:hover,
.mx_RoomSettings_unbanButton:hover {
@mixin mx_DialogButton_hover;
}
+.mx_RoomSettings_upgradeButton.danger {
+ @mixin mx_DialogButton_danger;
+}
+
.mx_RoomSettings_integrationsButton_error {
position: relative;
cursor: not-allowed;
diff --git a/res/css/views/rooms/_RoomUpgradeWarningBar.scss b/res/css/views/rooms/_RoomUpgradeWarningBar.scss
new file mode 100644
index 0000000000..82785b82d2
--- /dev/null
+++ b/res/css/views/rooms/_RoomUpgradeWarningBar.scss
@@ -0,0 +1,48 @@
+/*
+Copyright 2018 New Vector Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+.mx_RoomUpgradeWarningBar {
+ text-align: center;
+ height: 176px;
+ background-color: $event-selected-color;
+ align-items: center;
+ flex-direction: column;
+ justify-content: center;
+ display: flex;
+ background-color: $preview-bar-bg-color;
+ -webkit-align-items: center;
+ padding-left: 20px;
+ padding-right: 20px;
+}
+
+.mx_RoomUpgradeWarningBar_header {
+ color: $warning-color;
+ font-weight: bold;
+}
+
+.mx_RoomUpgradeWarningBar_body {
+ color: $warning-color;
+}
+
+.mx_RoomUpgradeWarningBar_upgradelink {
+ color: $warning-color;
+ text-decoration: underline;
+}
+
+.mx_RoomUpgradeWarningBar_small {
+ color: $greyed-fg-color;
+ font-size: 70%;
+}
diff --git a/res/themes/light/css/_base.scss b/res/themes/light/css/_base.scss
index 7d004bd831..c7fd38259c 100644
--- a/res/themes/light/css/_base.scss
+++ b/res/themes/light/css/_base.scss
@@ -171,6 +171,10 @@ $progressbar-color: #000;
outline: none;
}
+@define-mixin mx_DialogButton_danger {
+ background-color: $warning-color;
+}
+
@define-mixin mx_DialogButton_hover {
}
diff --git a/src/ScalarMessaging.js b/src/ScalarMessaging.js
index 3325044b84..fa7b8c5b76 100644
--- a/src/ScalarMessaging.js
+++ b/src/ScalarMessaging.js
@@ -480,7 +480,7 @@ function getMembershipCount(event, roomId) {
sendError(event, _t('This room is not recognised.'));
return;
}
- const count = room.getJoinedMembers().length;
+ const count = room.getJoinedMemberCount();
sendResponse(event, count);
}
@@ -497,12 +497,11 @@ function canSendEvent(event, roomId) {
sendError(event, _t('This room is not recognised.'));
return;
}
- const me = client.credentials.userId;
- const member = room.getMember(me);
- if (!member || member.membership !== "join") {
+ if (room.getMyMembership() !== "join") {
sendError(event, _t('You are not in this room.'));
return;
}
+ const me = client.credentials.userId;
let canSend = false;
if (isState) {
diff --git a/src/VectorConferenceHandler.js b/src/VectorConferenceHandler.js
index 9ba46b2ab6..c53a01d464 100644
--- a/src/VectorConferenceHandler.js
+++ b/src/VectorConferenceHandler.js
@@ -72,7 +72,7 @@ ConferenceCall.prototype._getConferenceUserRoom = function() {
for (var i = 0; i < rooms.length; i++) {
var confUser = rooms[i].getMember(this.confUserId);
if (confUser && confUser.membership === "join" &&
- rooms[i].getJoinedMembers().length === 2) {
+ rooms[i].getJoinedMemberCount() === 2) {
confRoom = rooms[i];
break;
}
diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js
index 2bc6522232..0c4688a411 100644
--- a/src/components/structures/LoggedInView.js
+++ b/src/components/structures/LoggedInView.js
@@ -434,7 +434,10 @@ const LoggedInView = React.createClass({
}
const usageLimitEvent = this.state.serverNoticeEvents.find((e) => {
- return e && e.getType() === 'm.server_notice.usage_limit_reached';
+ return (
+ e && e.getType() === 'm.room.message' &&
+ e.getContent()['server_notice_type'] === 'm.server_notice.usage_limit_reached'
+ );
});
let topBar;
diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js
index 18523ceb59..bd4ed722cb 100644
--- a/src/components/structures/RightPanel.js
+++ b/src/components/structures/RightPanel.js
@@ -280,7 +280,7 @@ module.exports = React.createClass({
const room = cli.getRoom(this.props.roomId);
let isUserInRoom;
if (room) {
- const numMembers = room.getJoinedMembers().length;
+ const numMembers = room.getJoinedMemberCount();
membersTitle = _t('%(count)s Members', { count: numMembers });
membersBadge =
{ formatCount(numMembers) }
;
isUserInRoom = room.hasMembershipState(this.context.matrixClient.credentials.userId, 'join');
diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js
index 855090873f..ca06243ed1 100644
--- a/src/components/structures/RoomView.js
+++ b/src/components/structures/RoomView.js
@@ -310,7 +310,7 @@ module.exports = React.createClass({
});
} else if (room) {
//viewing a previously joined room, try to lazy load members
-
+
// Stop peeking because we have joined this room previously
MatrixClientPeg.get().stopPeeking();
this.setState({isPeeking: false});
@@ -363,7 +363,7 @@ module.exports = React.createClass({
// XXX: EVIL HACK to autofocus inviting on empty rooms.
// We use the setTimeout to avoid racing with focus_composer.
if (this.state.room &&
- this.state.room.getJoinedMembers().length == 1 &&
+ this.state.room.getJoinedMemberCount() == 1 &&
this.state.room.getLiveTimeline() &&
this.state.room.getLiveTimeline().getEvents() &&
this.state.room.getLiveTimeline().getEvents().length <= 6) {
@@ -701,7 +701,6 @@ module.exports = React.createClass({
}
this._updateRoomMembers();
- this._checkIfAlone(this.state.room);
},
onRoomMemberMembership: function(ev, member, oldMembership) {
@@ -717,6 +716,7 @@ module.exports = React.createClass({
// refresh the conf call notification state
this._updateConfCallNotification();
this._updateDMState();
+ this._checkIfAlone(this.state.room);
}, 500),
_checkIfAlone: function(room) {
@@ -729,8 +729,8 @@ module.exports = React.createClass({
return;
}
- const joinedMembers = room.currentState.getMembers().filter((m) => m.membership === "join" || m.membership === "invite");
- this.setState({isAlone: joinedMembers.length === 1});
+ const joinedOrInvitedMemberCount = room.getJoinedMemberCount() + room.getInvitedMemberCount();
+ this.setState({isAlone: joinedOrInvitedMemberCount === 1});
},
_updateConfCallNotification: function() {
@@ -1461,6 +1461,7 @@ module.exports = React.createClass({
const RoomPreviewBar = sdk.getComponent("rooms.RoomPreviewBar");
const Loader = sdk.getComponent("elements.Spinner");
const TimelinePanel = sdk.getComponent("structures.TimelinePanel");
+ const RoomUpgradeWarningBar = sdk.getComponent("rooms.RoomUpgradeWarningBar");
if (!this.state.room) {
if (this.state.roomLoading || this.state.peekLoading) {
@@ -1507,9 +1508,8 @@ module.exports = React.createClass({
}
}
- const myUserId = MatrixClientPeg.get().credentials.userId;
- const myMember = this.state.room.getMember(myUserId);
- if (myMember && myMember.membership == 'invite') {
+ const myMembership = this.state.room.getMyMembership();
+ if (myMembership == 'invite') {
if (this.state.joining || this.state.rejecting) {
return (
@@ -1517,6 +1517,8 @@ module.exports = React.createClass({
);
} else {
+ const myUserId = MatrixClientPeg.get().credentials.userId;
+ const myMember = this.state.room.getMember(myUserId);
const inviteEvent = myMember.events.member;
var inviterName = inviteEvent.sender ? inviteEvent.sender.name : inviteEvent.getSender();
@@ -1586,6 +1588,11 @@ module.exports = React.createClass({
/>;
}
+ const showRoomUpgradeBar = (
+ this.state.room.shouldUpgradeToVersion() &&
+ this.state.room.userMayUpgradeRoom(MatrixClientPeg.get().credentials.userId)
+ );
+
let aux = null;
let hideCancel = false;
if (this.state.editingRoomSettings) {
@@ -1597,10 +1604,13 @@ module.exports = React.createClass({
} else if (this.state.searching) {
hideCancel = true; // has own cancel
aux = ;
+ } else if (showRoomUpgradeBar) {
+ aux = ;
+ hideCancel = true;
} else if (this.state.showingPinned) {
hideCancel = true; // has own cancel
aux = ;
- } else if (!myMember || myMember.membership !== "join") {
+ } else if (myMembership !== "join") {
// We do have a room object for this room, but we're not currently in it.
// We may have a 3rd party invite to it.
var inviterName = undefined;
@@ -1642,7 +1652,7 @@ module.exports = React.createClass({
let messageComposer, searchInfo;
const canSpeak = (
// joined and not showing search results
- myMember && (myMember.membership == 'join') && !this.state.searchResults
+ myMembership == 'join' && !this.state.searchResults
);
if (canSpeak) {
messageComposer =
@@ -1777,15 +1787,15 @@ module.exports = React.createClass({
oobData={this.props.oobData}
editing={this.state.editingRoomSettings}
saving={this.state.uploadingRoomSettings}
- inRoom={myMember && myMember.membership === 'join'}
+ inRoom={myMembership === 'join'}
collapsedRhs={this.props.collapsedRhs}
onSearchClick={this.onSearchClick}
onSettingsClick={this.onSettingsClick}
onPinnedClick={this.onPinnedClick}
onSaveClick={this.onSettingsSaveClick}
onCancelClick={(aux && !hideCancel) ? this.onCancelClick : null}
- onForgetClick={(myMember && myMember.membership === "leave") ? this.onForgetClick : null}
- onLeaveClick={(myMember && myMember.membership === "join") ? this.onLeaveClick : null}
+ onForgetClick={(myMembership === "leave") ? this.onForgetClick : null}
+ onLeaveClick={(myMembership === "join") ? this.onLeaveClick : null}
/>
{ auxPanel }
diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js
index f4dc92aca4..53e1ddea71 100644
--- a/src/components/structures/UserSettings.js
+++ b/src/components/structures/UserSettings.js
@@ -921,6 +921,25 @@ module.exports = React.createClass({
;
},
+ _renderTermsAndConditionsLinks: function() {
+ if (SdkConfig.get().terms_and_conditions_links) {
+ const tncLinks = [];
+ for (const tncEntry of SdkConfig.get().terms_and_conditions_links) {
+ tncLinks.push();
+ }
+ return
+
{ _t("Legal") }
+
+ {tncLinks}
+
+
;
+ } else {
+ return null;
+ }
+ },
+
_renderClearCache: function() {
return
{ _t("Clear Cache") }
@@ -1407,6 +1426,8 @@ module.exports = React.createClass({
{ this._renderDeactivateAccount() }
+ { this._renderTermsAndConditionsLinks() }
+
);
diff --git a/src/components/views/avatars/RoomAvatar.js b/src/components/views/avatars/RoomAvatar.js
index 821448207f..e0105159fb 100644
--- a/src/components/views/avatars/RoomAvatar.js
+++ b/src/components/views/avatars/RoomAvatar.js
@@ -19,6 +19,7 @@ import {ContentRepo} from "matrix-js-sdk";
import MatrixClientPeg from "../../../MatrixClientPeg";
import Modal from '../../../Modal';
import sdk from "../../../index";
+import DMRoomMap from '../../../utils/DMRoomMap';
module.exports = React.createClass({
displayName: 'RoomAvatar',
@@ -107,58 +108,37 @@ module.exports = React.createClass({
},
getOneToOneAvatar: function(props) {
- if (!props.room) return null;
-
- const mlist = props.room.currentState.members;
- const userIds = [];
- const leftUserIds = [];
- // for .. in optimisation to return early if there are >2 keys
- for (const uid in mlist) {
- if (mlist.hasOwnProperty(uid)) {
- if (["join", "invite"].includes(mlist[uid].membership)) {
- userIds.push(uid);
- } else {
- leftUserIds.push(uid);
- }
- }
- if (userIds.length > 2) {
- return null;
- }
+ const room = props.room;
+ if (!room) {
+ return null;
}
-
- if (userIds.length == 2) {
- let theOtherGuy = null;
- if (mlist[userIds[0]].userId == MatrixClientPeg.get().credentials.userId) {
- theOtherGuy = mlist[userIds[1]];
- } else {
- theOtherGuy = mlist[userIds[0]];
- }
- return theOtherGuy.getAvatarUrl(
- MatrixClientPeg.get().getHomeserverUrl(),
- Math.floor(props.width * window.devicePixelRatio),
- Math.floor(props.height * window.devicePixelRatio),
- props.resizeMethod,
- false,
- );
- } else if (userIds.length == 1) {
- // The other 1-1 user left, leaving just the current user, so show the left user's avatar
- if (leftUserIds.length === 1) {
- return mlist[leftUserIds[0]].getAvatarUrl(
- MatrixClientPeg.get().getHomeserverUrl(),
- props.width, props.height, props.resizeMethod,
- false,
- );
- }
- return mlist[userIds[0]].getAvatarUrl(
- MatrixClientPeg.get().getHomeserverUrl(),
- Math.floor(props.width * window.devicePixelRatio),
- Math.floor(props.height * window.devicePixelRatio),
- props.resizeMethod,
- false,
- );
+ let otherMember = null;
+ const otherUserId = DMRoomMap.shared().getUserIdForRoomId(room.roomId);
+ if (otherUserId) {
+ otherMember = room.getMember(otherUserId);
} else {
- return null;
+ // if the room is not marked as a 1:1, but only has max 2 members
+ // then still try to show any avatar (pref. other member)
+ const totalMemberCount = room.getJoinedMemberCount() +
+ room.getInvitedMemberCount();
+ const members = room.currentState.getMembers();
+ if (totalMemberCount == 2) {
+ const myUserId = MatrixClientPeg.get().getUserId();
+ otherMember = members.find(m => m.userId !== myUserId);
+ } else if (totalMemberCount == 1) {
+ otherMember = members[0];
+ }
}
+ if (otherMember) {
+ return otherMember.getAvatarUrl(
+ MatrixClientPeg.get().getHomeserverUrl(),
+ Math.floor(props.width * window.devicePixelRatio),
+ Math.floor(props.height * window.devicePixelRatio),
+ props.resizeMethod,
+ false,
+ );
+ }
+ return null;
},
onRoomAvatarClick: function() {
diff --git a/src/components/views/context_menus/RoomTileContextMenu.js b/src/components/views/context_menus/RoomTileContextMenu.js
index 77f71fa8fa..ce9895447e 100644
--- a/src/components/views/context_menus/RoomTileContextMenu.js
+++ b/src/components/views/context_menus/RoomTileContextMenu.js
@@ -346,20 +346,18 @@ module.exports = React.createClass({
},
render: function() {
- const myMember = this.props.room.getMember(
- MatrixClientPeg.get().credentials.userId,
- );
+ const myMembership = this.props.room.getMyMembership();
// Can't set notif level or tags on non-join rooms
- if (myMember.membership !== 'join') {
- return this._renderLeaveMenu(myMember.membership);
+ if (myMembership !== 'join') {
+ return this._renderLeaveMenu(myMembership);
}
return (
{ this._renderNotifMenu() }
- { this._renderLeaveMenu(myMember.membership) }
+ { this._renderLeaveMenu(myMembership) }
{ this._renderRoomTagMenu() }
diff --git a/src/components/views/dialogs/RoomUpgradeDialog.js b/src/components/views/dialogs/RoomUpgradeDialog.js
new file mode 100644
index 0000000000..936ff745d1
--- /dev/null
+++ b/src/components/views/dialogs/RoomUpgradeDialog.js
@@ -0,0 +1,106 @@
+/*
+Copyright 2018 New Vector Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from 'react';
+import PropTypes from 'prop-types';
+import sdk from '../../../index';
+import MatrixClientPeg from '../../../MatrixClientPeg';
+import Modal from '../../../Modal';
+import { _t } from '../../../languageHandler';
+
+export default React.createClass({
+ displayName: 'RoomUpgradeDialog',
+
+ propTypes: {
+ room: PropTypes.object.isRequired,
+ onFinished: PropTypes.func.isRequired,
+ },
+
+ componentWillMount: function() {
+ this._targetVersion = this.props.room.shouldUpgradeToVersion();
+ },
+
+ getInitialState: function() {
+ return {
+ busy: false,
+ };
+ },
+
+ _onCancelClick: function() {
+ this.props.onFinished(false);
+ },
+
+ _onUpgradeClick: function() {
+ this.setState({busy: true});
+ MatrixClientPeg.get().upgradeRoom(this.props.room.roomId, this._targetVersion).catch((err) => {
+ const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+ Modal.createTrackedDialog('Failed to upgrade room', '', ErrorDialog, {
+ title: _t("Failed to upgrade room"),
+ description: ((err && err.message) ? err.message : _t("The room upgrade could not be completed")),
+ });
+ }).finally(() => {
+ this.setState({busy: false});
+ });
+ },
+
+ render: function() {
+ const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
+ const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
+ const Spinner = sdk.getComponent('views.elements.Spinner');
+
+ let buttons;
+ if (this.state.busy) {
+ buttons = ;
+ } else {
+ buttons = ;
+ }
+
+ return (
+
+
+ {_t(
+ "Upgrading this room requires closing down the current " +
+ "instance of the room and creating a new room it its place. " +
+ "To give room members the best possible experience, we will:",
+ )}
+
+
+ - {_t("Create a new room with the same name, description and avatar")}
+ - {_t("Update any local room aliases to point to the new room")}
+ - {_t("Stop users from speaking in the old version of the room, and post a message advising users to move to the new room")}
+ - {_t("Put a link back to the old room at the start of the new room so people can see old messages")}
+
+ {buttons}
+
+ );
+ },
+});
diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js
index a7e02d16ae..eccdfe17af 100644
--- a/src/components/views/rooms/MessageComposer.js
+++ b/src/components/views/rooms/MessageComposer.js
@@ -71,6 +71,23 @@ export default class MessageComposer extends React.Component {
// XXX: fragile as all hell - fixme somehow, perhaps with a dedicated Room.encryption event or something.
MatrixClientPeg.get().on("event", this.onEvent);
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
+ this._waitForOwnMember();
+ }
+
+ _waitForOwnMember() {
+ // if we have the member already, do that
+ const me = this.props.room.getMember(MatrixClientPeg.get().getUserId());
+ if (me) {
+ this.setState({me});
+ return;
+ }
+ // Otherwise, wait for member loading to finish and then update the member for the avatar.
+ // The members should already be loading, and loadMembersIfNeeded
+ // will return the promise for the existing operation
+ this.props.room.loadMembersIfNeeded().then(() => {
+ const me = this.props.room.getMember(MatrixClientPeg.get().getUserId());
+ this.setState({me});
+ });
}
componentWillUnmount() {
@@ -208,7 +225,6 @@ export default class MessageComposer extends React.Component {
}
render() {
- const me = this.props.room.getMember(MatrixClientPeg.get().credentials.userId);
const uploadInputStyle = {display: 'none'};
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
const TintableSvg = sdk.getComponent("elements.TintableSvg");
@@ -216,11 +232,13 @@ export default class MessageComposer extends React.Component {
const controls = [];
- controls.push(
-
-
-
,
- );
+ if (this.state.me) {
+ controls.push(
+
+
+
,
+ );
+ }
let e2eImg, e2eTitle, e2eClass;
const roomIsEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId);
diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js
index 732048f712..3e632ba8ce 100644
--- a/src/components/views/rooms/RoomList.js
+++ b/src/components/views/rooms/RoomList.js
@@ -97,7 +97,7 @@ module.exports = React.createClass({
};
// All rooms that should be kept in the room list when filtering.
// By default, show all rooms.
- this._visibleRooms = MatrixClientPeg.get().getRooms();
+ this._visibleRooms = MatrixClientPeg.get().getVisibleRooms();
// Listen to updates to group data. RoomList cares about members and rooms in order
// to filter the room list when group tags are selected.
@@ -302,7 +302,7 @@ module.exports = React.createClass({
this._visibleRooms = Array.from(roomSet);
} else {
// Show all rooms
- this._visibleRooms = MatrixClientPeg.get().getRooms();
+ this._visibleRooms = MatrixClientPeg.get().getVisibleRooms();
}
this._delayedRefreshRoomList();
},
diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js
index 7a78d205b9..46869c1773 100644
--- a/src/components/views/rooms/RoomSettings.js
+++ b/src/components/views/rooms/RoomSettings.js
@@ -572,6 +572,11 @@ module.exports = React.createClass({
});
},
+ _onRoomUpgradeClick: function() {
+ const RoomUpgradeDialog = sdk.getComponent('dialogs.RoomUpgradeDialog');
+ Modal.createTrackedDialog('Upgrade Room Version', '', RoomUpgradeDialog, {room: this.props.room});
+ },
+
_onRoomMemberMembership: function() {
// Update, since our banned user list may have changed
this.forceUpdate();
@@ -794,15 +799,15 @@ module.exports = React.createClass({
}
let leaveButton = null;
- const myMember = this.props.room.getMember(myUserId);
- if (myMember) {
- if (myMember.membership === "join") {
+ const myMemberShip = this.props.room.getMyMembership();
+ if (myMemberShip) {
+ if (myMemberShip === "join") {
leaveButton = (
{ _t('Leave room') }
);
- } else if (myMember.membership === "leave") {
+ } else if (myMemberShip === "leave") {
leaveButton = (
{ _t('Forget room') }
@@ -930,6 +935,13 @@ module.exports = React.createClass({
);
});
+ let roomUpgradeButton = null;
+ if (this.props.room.shouldUpgradeToVersion() && this.props.room.userMayUpgradeRoom(myUserId)) {
+ roomUpgradeButton =
+ { _t("Upgrade room to version %(ver)s", {ver: this.props.room.shouldUpgradeToVersion()}) }
+ ;
+ }
+
return (
@@ -1041,7 +1053,8 @@ module.exports = React.createClass({
{ _t('Advanced') }
{ _t('Internal room ID: ') } { this.props.room.roomId }
- { _t('Room version number: ') } { this.props.room.getVersion() }
+ { _t('Room version number: ') } { this.props.room.getVersion() }
+ { roomUpgradeButton }
);
diff --git a/src/components/views/rooms/RoomUpgradeWarningBar.js b/src/components/views/rooms/RoomUpgradeWarningBar.js
new file mode 100644
index 0000000000..75a5901fc9
--- /dev/null
+++ b/src/components/views/rooms/RoomUpgradeWarningBar.js
@@ -0,0 +1,57 @@
+/*
+Copyright 2018 New Vector Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from 'react';
+import PropTypes from 'prop-types';
+import sdk from '../../../index';
+import Modal from '../../../Modal';
+
+import { _t } from '../../../languageHandler';
+
+module.exports = React.createClass({
+ displayName: 'RoomUpgradeWarningBar',
+
+ propTypes: {
+ room: PropTypes.object.isRequired,
+ },
+
+ onUpgradeClick: function() {
+ const RoomUpgradeDialog = sdk.getComponent('dialogs.RoomUpgradeDialog');
+ Modal.createTrackedDialog('Upgrade Room Version', '', RoomUpgradeDialog, {room: this.props.room});
+ },
+
+ render: function() {
+ const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
+ return (
+
+
+ {_t("There is a known vulnerability affecting this room.")}
+
+
+ {_t("This room version is vulnerable to malicious modification of room state.")}
+
+
+
+ {_t("Click here to upgrade to the latest room version and ensure room integrity is protected.")}
+
+
+
+ {_t("Only room administrators will see this warning")}
+
+
+ );
+ },
+});
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index db2537307d..078090100e 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -203,6 +203,8 @@
"Your browser does not support the required cryptography extensions": "Your browser does not support the required cryptography extensions",
"Not a valid Riot keyfile": "Not a valid Riot keyfile",
"Authentication check failed: incorrect password?": "Authentication check failed: incorrect password?",
+ "Sorry, your homeserver is too old to participate in this room.": "Sorry, your homeserver is too old to participate in this room.",
+ "Please contact your homeserver administrator.": "Please contact your homeserver administrator.",
"Failed to join room": "Failed to join room",
"Message Pinning": "Message Pinning",
"Increase performance by only loading room members on first view": "Increase performance by only loading room members on first view",
@@ -529,6 +531,7 @@
"Guests cannot join this room even if explicitly invited.": "Guests cannot join this room even if explicitly invited.",
"Click here to fix": "Click here to fix",
"To send events of type , you must be a": "To send events of type , you must be a",
+ "Upgrade room to version %(ver)s": "Upgrade room to version %(ver)s",
"Who can access this room?": "Who can access this room?",
"Only people who have been invited": "Only people who have been invited",
"Anyone who knows the room's link, apart from guests": "Anyone who knows the room's link, apart from guests",
@@ -544,6 +547,10 @@
"Internal room ID: ": "Internal room ID: ",
"Room version number: ": "Room version number: ",
"Add a topic": "Add a topic",
+ "There is a known vulnerability affecting this room.": "There is a known vulnerability affecting this room.",
+ "This room version is vulnerable to malicious modification of room state.": "This room version is vulnerable to malicious modification of room state.",
+ "Click here to upgrade to the latest room version and ensure room integrity is protected.": "Click here to upgrade to the latest room version and ensure room integrity is protected.",
+ "Only room administrators will see this warning": "Only room administrators will see this warning",
"Search…": "Search…",
"This Room": "This Room",
"All Rooms": "All Rooms",
@@ -864,6 +871,15 @@
"Ignore request": "Ignore request",
"Loading device info...": "Loading device info...",
"Encryption key request": "Encryption key request",
+ "Failed to upgrade room": "Failed to upgrade room",
+ "The room upgrade could not be completed": "The room upgrade could not be completed",
+ "Upgrade this room to version %(version)s": "Upgrade this room to version %(version)s",
+ "Upgrade Room Version": "Upgrade Room Version",
+ "Upgrading this room requires closing down the current instance of the room and creating a new room it its place. To give room members the best possible experience, we will:": "Upgrading this room requires closing down the current instance of the room and creating a new room it its place. To give room members the best possible experience, we will:",
+ "Create a new room with the same name, description and avatar": "Create a new room with the same name, description and avatar",
+ "Update any local room aliases to point to the new room": "Update any local room aliases to point to the new room",
+ "Stop users from speaking in the old version of the room, and post a message advising users to move to the new room": "Stop users from speaking in the old version of the room, and post a message advising users to move to the new room",
+ "Put a link back to the old room at the start of the new room so people can see old messages": "Put a link back to the old room at the start of the new room so people can see old messages",
"Sign out": "Sign out",
"Log out and remove encryption keys?": "Log out and remove encryption keys?",
"Clear Storage and Sign Out": "Clear Storage and Sign Out",
@@ -1117,6 +1133,7 @@
"Lazy loading members not supported": "Lazy loading members not supported",
"Lazy loading is not supported by your current homeserver.": "Lazy loading is not supported by your current homeserver.",
"Deactivate my account": "Deactivate my account",
+ "Legal": "Legal",
"Clear Cache": "Clear Cache",
"Clear Cache and Reload": "Clear Cache and Reload",
"Updates": "Updates",
diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js
index 0a0a39a450..6571e1590f 100644
--- a/src/stores/RoomListStore.js
+++ b/src/stores/RoomListStore.js
@@ -284,8 +284,8 @@ class RoomListStore extends Store {
if (optimisticRequest && roomB === optimisticRequest.room) metaB = optimisticRequest.metaData;
// Make sure the room tag has an order element, if not set it to be the bottom
- const a = metaA.order;
- const b = metaB.order;
+ const a = metaA ? metaA.order : undefined;
+ const b = metaB ? metaB.order : undefined;
// Order undefined room tag orders to the bottom
if (a === undefined && b !== undefined) {
diff --git a/src/stores/RoomViewStore.js b/src/stores/RoomViewStore.js
index fed0d7b4a1..f15925f480 100644
--- a/src/stores/RoomViewStore.js
+++ b/src/stores/RoomViewStore.js
@@ -223,7 +223,13 @@ class RoomViewStore extends Store {
action: 'join_room_error',
err: err,
});
- const msg = err.message ? err.message : JSON.stringify(err);
+ let msg = err.message ? err.message : JSON.stringify(err);
+ if (err.errcode === 'M_INCOMPATIBLE_ROOM_VERSION') {
+ msg =
+ {_t("Sorry, your homeserver is too old to participate in this room.")}
+ {_t("Please contact your homeserver administrator.")}
+
;
+ }
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to join room', '', ErrorDialog, {
title: _t("Failed to join room"),
diff --git a/test/components/views/rooms/RoomList-test.js b/test/components/views/rooms/RoomList-test.js
index 1e9f80f161..fbb2dd4af9 100644
--- a/test/components/views/rooms/RoomList-test.js
+++ b/test/components/views/rooms/RoomList-test.js
@@ -94,6 +94,7 @@ describe('RoomList', () => {
createRoom({tags: {'m.lowpriority': {}}, name: 'Some unimportant room'}),
createRoom({tags: {'custom.tag': {}}, name: 'Some room customly tagged'}),
];
+ client.getVisibleRooms = client.getRooms;
const roomMap = {};
client.getRooms().forEach((r) => {
diff --git a/test/test-utils.js b/test/test-utils.js
index 975a4df0ee..bc4d29210e 100644
--- a/test/test-utils.js
+++ b/test/test-utils.js
@@ -74,6 +74,7 @@ export function createTestClient() {
getPushActionsForEvent: sinon.stub(),
getRoom: sinon.stub().returns(mkStubRoom()),
getRooms: sinon.stub().returns([]),
+ getVisibleRooms: sinon.stub().returns([]),
getGroups: sinon.stub().returns([]),
loginFlows: sinon.stub(),
on: sinon.stub(),
@@ -256,6 +257,8 @@ export function mkStubRoom(roomId = null) {
getAccountData: () => null,
hasMembershipState: () => null,
getVersion: () => '1',
+ shouldUpgradeToVersion: () => null,
+ getMyMembership: () => "join",
currentState: {
getStateEvents: sinon.stub(),
mayClientSendStateEvent: sinon.stub().returns(true),