Merge remote-tracking branch 'origin/develop' into dbkr/roomcreate

pull/21833/head
David Baker 2018-08-29 18:18:08 +01:00
commit c40ac49b2a
25 changed files with 410 additions and 92 deletions

View File

@ -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)

View File

@ -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": {

View File

@ -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";

View File

@ -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;
}

View File

@ -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;

View File

@ -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%;
}

View File

@ -171,6 +171,10 @@ $progressbar-color: #000;
outline: none;
}
@define-mixin mx_DialogButton_danger {
background-color: $warning-color;
}
@define-mixin mx_DialogButton_hover {
}

View File

@ -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) {

View File

@ -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;
}

View File

@ -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;

View File

@ -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 = <div title={membersTitle}>{ formatCount(numMembers) }</div>;
isUserInRoom = room.hasMembershipState(this.context.matrixClient.credentials.userId, 'join');

View File

@ -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 (
<div className="mx_RoomView">
@ -1517,6 +1517,8 @@ module.exports = React.createClass({
</div>
);
} 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 = <SearchBar ref="search_bar" searchInProgress={this.state.searchInProgress} onCancelClick={this.onCancelSearchClick} onSearch={this.onSearch} />;
} else if (showRoomUpgradeBar) {
aux = <RoomUpgradeWarningBar room={this.state.room} />;
hideCancel = true;
} else if (this.state.showingPinned) {
hideCancel = true; // has own cancel
aux = <PinnedEventsPanel room={this.state.room} onCancelClick={this.onPinnedClick} />;
} 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 }
<div className={fadableSectionClasses}>

View File

@ -921,6 +921,25 @@ module.exports = React.createClass({
</div>;
},
_renderTermsAndConditionsLinks: function() {
if (SdkConfig.get().terms_and_conditions_links) {
const tncLinks = [];
for (const tncEntry of SdkConfig.get().terms_and_conditions_links) {
tncLinks.push(<div key={tncEntry.url}>
<a href={tncEntry.url} rel="noopener" target="_blank">{tncEntry.text}</a>
</div>);
}
return <div>
<h3>{ _t("Legal") }</h3>
<div className="mx_UserSettings_section">
{tncLinks}
</div>
</div>;
} else {
return null;
}
},
_renderClearCache: function() {
return <div>
<h3>{ _t("Clear Cache") }</h3>
@ -1407,6 +1426,8 @@ module.exports = React.createClass({
{ this._renderDeactivateAccount() }
{ this._renderTermsAndConditionsLinks() }
</GeminiScrollbarWrapper>
</div>
);

View File

@ -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() {

View File

@ -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 (
<div>
{ this._renderNotifMenu() }
<hr className="mx_RoomTileContextMenu_separator" />
{ this._renderLeaveMenu(myMember.membership) }
{ this._renderLeaveMenu(myMembership) }
<hr className="mx_RoomTileContextMenu_separator" />
{ this._renderRoomTagMenu() }
</div>

View File

@ -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 = <Spinner />;
} else {
buttons = <DialogButtons
primaryButton={_t(
'Upgrade this room to version %(version)s',
{version: this._targetVersion},
)}
primaryButtonClass="danger"
hasCancel={true}
onPrimaryButtonClick={this._onUpgradeClick}
focus={this.props.focus}
onCancel={this._onCancelClick}
/>;
}
return (
<BaseDialog className="mx_RoomUpgradeDialog"
onFinished={this.onCancelled}
title={_t("Upgrade Room Version")}
contentId='mx_Dialog_content'
onFinished={this.props.onFinished}
hasCancel={true}
>
<p>
{_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:",
)}
</p>
<ol>
<li>{_t("Create a new room with the same name, description and avatar")}</li>
<li>{_t("Update any local room aliases to point to the new room")}</li>
<li>{_t("Stop users from speaking in the old version of the room, and post a message advising users to move to the new room")}</li>
<li>{_t("Put a link back to the old room at the start of the new room so people can see old messages")}</li>
</ol>
{buttons}
</BaseDialog>
);
},
});

View File

@ -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(
<div key="controls_avatar" className="mx_MessageComposer_avatar">
<MemberAvatar member={me} width={24} height={24} />
</div>,
);
if (this.state.me) {
controls.push(
<div key="controls_avatar" className="mx_MessageComposer_avatar">
<MemberAvatar member={this.state.me} width={24} height={24} />
</div>,
);
}
let e2eImg, e2eTitle, e2eClass;
const roomIsEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId);

View File

@ -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();
},

View File

@ -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 = (
<AccessibleButton className="mx_RoomSettings_leaveButton" onClick={this.onLeaveClick}>
{ _t('Leave room') }
</AccessibleButton>
);
} else if (myMember.membership === "leave") {
} else if (myMemberShip === "leave") {
leaveButton = (
<AccessibleButton className="mx_RoomSettings_leaveButton" onClick={this.onForgetClick}>
{ _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 = <AccessibleButton className="mx_RoomSettings_upgradeButton danger" onClick={this._onRoomUpgradeClick}>
{ _t("Upgrade room to version %(ver)s", {ver: this.props.room.shouldUpgradeToVersion()}) }
</AccessibleButton>;
}
return (
<div className="mx_RoomSettings">
@ -1041,7 +1053,8 @@ module.exports = React.createClass({
<h3>{ _t('Advanced') }</h3>
<div className="mx_RoomSettings_settings">
{ _t('Internal room ID: ') } <code>{ this.props.room.roomId }</code><br />
{ _t('Room version number: ') } <code>{ this.props.room.getVersion() }</code>
{ _t('Room version number: ') } <code>{ this.props.room.getVersion() }</code><br />
{ roomUpgradeButton }
</div>
</div>
);

View File

@ -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 (
<div className="mx_RoomUpgradeWarningBar">
<div className="mx_RoomUpgradeWarningBar_header">
{_t("There is a known vulnerability affecting this room.")}
</div>
<div className="mx_RoomUpgradeWarningBar_body">
{_t("This room version is vulnerable to malicious modification of room state.")}
</div>
<p className="mx_RoomUpgradeWarningBar_upgradelink">
<AccessibleButton onClick={this.onUpgradeClick}>
{_t("Click here to upgrade to the latest room version and ensure room integrity is protected.")}
</AccessibleButton>
</p>
<div className="mx_RoomUpgradeWarningBar_small">
{_t("Only room administrators will see this warning")}
</div>
</div>
);
},
});

View File

@ -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 <eventType/>, you must be a": "To send events of type <eventType/>, 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",

View File

@ -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) {

View File

@ -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 = <div>
{_t("Sorry, your homeserver is too old to participate in this room.")}<br />
{_t("Please contact your homeserver administrator.")}
</div>;
}
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to join room', '', ErrorDialog, {
title: _t("Failed to join room"),

View File

@ -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) => {

View File

@ -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),