Merge branch 'develop' of https://github.com/matrix-org/matrix-react-sdk into rxl881/titleBar

This commit is contained in:
Richard Lewis 2017-12-11 13:08:49 +00:00
commit c93266b6af
16 changed files with 417 additions and 221 deletions

View file

@ -40,7 +40,6 @@ require('../../stores/LifecycleStore');
import PageTypes from '../../PageTypes';
import createRoom from "../../createRoom";
import * as UDEHandler from '../../UnknownDeviceErrorHandler';
import KeyRequestHandler from '../../KeyRequestHandler';
import { _t, getCurrentLanguage } from '../../languageHandler';
import SettingsStore, {SettingLevel} from "../../settings/SettingsStore";
@ -295,7 +294,6 @@ module.exports = React.createClass({
componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction);
UDEHandler.startListening();
this.focusComposer = false;
@ -361,7 +359,6 @@ module.exports = React.createClass({
componentWillUnmount: function() {
Lifecycle.stopMatrixClient();
dis.unregister(this.dispatcherRef);
UDEHandler.stopListening();
window.removeEventListener("focus", this.onFocus);
window.removeEventListener('resize', this.handleResize);
},
@ -1142,6 +1139,37 @@ module.exports = React.createClass({
room.setBlacklistUnverifiedDevices(blacklistEnabled);
}
});
cli.on("crypto.warning", (type) => {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
switch (type) {
case 'CRYPTO_WARNING_ACCOUNT_MIGRATED':
Modal.createTrackedDialog('Crypto migrated', '', ErrorDialog, {
title: _t('Cryptography data migrated'),
description: _t(
"A one-off migration of cryptography data has been performed. "+
"End-to-end encryption will not work if you go back to an older "+
"version of Riot. If you need to use end-to-end cryptography on "+
"an older version, log out of Riot first. To retain message history, "+
"export and re-import your keys.",
),
});
break;
case 'CRYPTO_WARNING_OLD_VERSION_DETECTED':
Modal.createTrackedDialog('Crypto migrated', '', ErrorDialog, {
title: _t('Old cryptography data detected'),
description: _t(
"Data from an older version of Riot has been detected. "+
"This will have caused end-to-end cryptography to malfunction "+
"in the older version. End-to-end encrypted messages exchanged "+
"recently whilst using the older version may not be decryptable "+
"in this version. This may also cause messages exchanged with this "+
"version to fail. If you experience problems, log out and back in "+
"again. To retain message history, export and re-import your keys.",
),
});
break;
}
});
},
/**
@ -1398,13 +1426,6 @@ module.exports = React.createClass({
cli.sendEvent(roomId, event.getType(), event.getContent()).done(() => {
dis.dispatch({action: 'message_sent'});
}, (err) => {
if (err.name === 'UnknownDeviceError') {
dis.dispatch({
action: 'unknown_device_error',
err: err,
room: cli.getRoom(roomId),
});
}
dis.dispatch({action: 'message_send_failed'});
});
},

View file

@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 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.
@ -15,16 +16,26 @@ limitations under the License.
*/
import React from 'react';
import Matrix from 'matrix-js-sdk';
import { _t } from '../../languageHandler';
import sdk from '../../index';
import WhoIsTyping from '../../WhoIsTyping';
import MatrixClientPeg from '../../MatrixClientPeg';
import MemberAvatar from '../views/avatars/MemberAvatar';
import Resend from '../../Resend';
import { showUnknownDeviceDialogForMessages } from '../../cryptodevices';
const STATUS_BAR_HIDDEN = 0;
const STATUS_BAR_EXPANDED = 1;
const STATUS_BAR_EXPANDED_LARGE = 2;
function getUnsentMessages(room) {
if (!room) { return []; }
return room.getPendingEvents().filter(function(ev) {
return ev.status === Matrix.EventStatus.NOT_SENT;
});
};
module.exports = React.createClass({
displayName: 'RoomStatusBar',
@ -35,9 +46,6 @@ module.exports = React.createClass({
// the number of messages which have arrived since we've been scrolled up
numUnreadMessages: React.PropTypes.number,
// string to display when there are messages in the room which had errors on send
unsentMessageError: React.PropTypes.string,
// this is true if we are fully scrolled-down, and are looking at
// the end of the live timeline.
atEndOfLiveTimeline: React.PropTypes.bool,
@ -98,12 +106,14 @@ module.exports = React.createClass({
return {
syncState: MatrixClientPeg.get().getSyncState(),
usersTyping: WhoIsTyping.usersTypingApartFromMe(this.props.room),
unsentMessages: getUnsentMessages(this.props.room),
};
},
componentWillMount: function() {
MatrixClientPeg.get().on("sync", this.onSyncStateChange);
MatrixClientPeg.get().on("RoomMember.typing", this.onRoomMemberTyping);
MatrixClientPeg.get().on("Room.localEchoUpdated", this._onRoomLocalEchoUpdated);
this._checkSize();
},
@ -118,6 +128,7 @@ module.exports = React.createClass({
if (client) {
client.removeListener("sync", this.onSyncStateChange);
client.removeListener("RoomMember.typing", this.onRoomMemberTyping);
client.removeListener("Room.localEchoUpdated", this._onRoomLocalEchoUpdated);
}
},
@ -136,6 +147,26 @@ module.exports = React.createClass({
});
},
_onResendAllClick: function() {
Resend.resendUnsentEvents(this.props.room);
},
_onCancelAllClick: function() {
Resend.cancelUnsentEvents(this.props.room);
},
_onShowDevicesClick: function() {
showUnknownDeviceDialogForMessages(MatrixClientPeg.get(), this.props.room);
},
_onRoomLocalEchoUpdated: function(event, room, oldEventId, oldStatus) {
if (room.roomId !== this.props.room.roomId) return;
this.setState({
unsentMessages: getUnsentMessages(this.props.room),
});
},
// Check whether current size is greater than 0, if yes call props.onVisible
_checkSize: function() {
if (this.props.onVisible && this._getSize()) {
@ -155,7 +186,7 @@ module.exports = React.createClass({
this.props.sentMessageAndIsAlone
) {
return STATUS_BAR_EXPANDED;
} else if (this.props.unsentMessageError) {
} else if (this.state.unsentMessages.length > 0) {
return STATUS_BAR_EXPANDED_LARGE;
}
return STATUS_BAR_HIDDEN;
@ -241,6 +272,61 @@ module.exports = React.createClass({
return avatars;
},
_getUnsentMessageContent: function() {
const unsentMessages = this.state.unsentMessages;
if (!unsentMessages.length) return null;
let title;
let content;
const hasUDE = unsentMessages.some((m) => {
return m.error && m.error.name === "UnknownDeviceError";
});
if (hasUDE) {
title = _t("Message not sent due to unknown devices being present");
content = _t(
"<showDevicesText>Show devices</showDevicesText> or <cancelText>cancel all</cancelText>.",
{},
{
'showDevicesText': (sub) => <a className="mx_RoomStatusBar_resend_link" key="resend" onClick={this._onShowDevicesClick}>{ sub }</a>,
'cancelText': (sub) => <a className="mx_RoomStatusBar_resend_link" key="cancel" onClick={this._onCancelAllClick}>{ sub }</a>,
},
);
} else {
if (
unsentMessages.length === 1 &&
unsentMessages[0].error &&
unsentMessages[0].error.data &&
unsentMessages[0].error.data.error
) {
title = unsentMessages[0].error.data.error;
} else {
title = _t("Some of your messages have not been sent.");
}
content = _t("<resendText>Resend all</resendText> or <cancelText>cancel all</cancelText> now. " +
"You can also select individual messages to resend or cancel.",
{},
{
'resendText': (sub) =>
<a className="mx_RoomStatusBar_resend_link" key="resend" onClick={this._onResendAllClick}>{ sub }</a>,
'cancelText': (sub) =>
<a className="mx_RoomStatusBar_resend_link" key="cancel" onClick={this._onCancelAllClick}>{ sub }</a>,
},
);
}
return <div className="mx_RoomStatusBar_connectionLostBar">
<img src="img/warning.svg" width="24" height="23" title={_t("Warning")} alt={_t("Warning")} />
<div className="mx_RoomStatusBar_connectionLostBar_title">
{ title }
</div>
<div className="mx_RoomStatusBar_connectionLostBar_desc">
{ content }
</div>
</div>;
},
// return suitable content for the main (text) part of the status bar.
_getContent: function() {
const EmojiText = sdk.getComponent('elements.EmojiText');
@ -263,28 +349,8 @@ module.exports = React.createClass({
);
}
if (this.props.unsentMessageError) {
return (
<div className="mx_RoomStatusBar_connectionLostBar">
<img src="img/warning.svg" width="24" height="23" title="/!\ " alt="/!\ " />
<div className="mx_RoomStatusBar_connectionLostBar_title">
{ this.props.unsentMessageError }
</div>
<div className="mx_RoomStatusBar_connectionLostBar_desc">
{
_t("<resendText>Resend all</resendText> or <cancelText>cancel all</cancelText> now. " +
"You can also select individual messages to resend or cancel.",
{},
{
'resendText': (sub) =>
<a className="mx_RoomStatusBar_resend_link" key="resend" onClick={this.props.onResendAllClick}>{ sub }</a>,
'cancelText': (sub) =>
<a className="mx_RoomStatusBar_resend_link" key="cancel" onClick={this.props.onCancelAllClick}>{ sub }</a>,
},
) }
</div>
</div>
);
if (this.state.unsentMessages.length > 0) {
return this._getUnsentMessageContent();
}
// unread count trumps who is typing since the unread count is only
@ -342,7 +408,6 @@ module.exports = React.createClass({
return null;
},
render: function() {
const content = this._getContent();
const indicator = this._getIndicator(this.state.usersTyping.length > 0);

View file

@ -26,7 +26,6 @@ const React = require("react");
const ReactDOM = require("react-dom");
import Promise from 'bluebird';
const classNames = require("classnames");
const Matrix = require("matrix-js-sdk");
import { _t } from '../../languageHandler';
const MatrixClientPeg = require("../../MatrixClientPeg");
@ -34,7 +33,6 @@ const ContentMessages = require("../../ContentMessages");
const Modal = require("../../Modal");
const sdk = require('../../index');
const CallHandler = require('../../CallHandler');
const Resend = require("../../Resend");
const dis = require("../../dispatcher");
const Tinter = require("../../Tinter");
const rate_limited_func = require('../../ratelimitedfunc');
@ -110,7 +108,6 @@ module.exports = React.createClass({
draggingFile: false,
searching: false,
searchResults: null,
unsentMessageError: '',
callState: null,
guestsCanJoin: false,
canPeek: false,
@ -202,7 +199,6 @@ module.exports = React.createClass({
if (initial) {
newState.room = MatrixClientPeg.get().getRoom(newState.roomId);
if (newState.room) {
newState.unsentMessageError = this._getUnsentMessageError(newState.room);
newState.showApps = this._shouldShowApps(newState.room);
this._onRoomLoaded(newState.room);
}
@ -462,11 +458,6 @@ module.exports = React.createClass({
case 'message_send_failed':
case 'message_sent':
this._checkIfAlone(this.state.room);
// no break; to intentionally fall through
case 'message_send_cancelled':
this.setState({
unsentMessageError: this._getUnsentMessageError(this.state.room),
});
break;
case 'notifier_enabled':
case 'upload_failed':
@ -711,35 +702,6 @@ module.exports = React.createClass({
this.setState({isAlone: joinedMembers.length === 1});
},
_getUnsentMessageError: function(room) {
const unsentMessages = this._getUnsentMessages(room);
if (!unsentMessages.length) return "";
if (
unsentMessages.length === 1 &&
unsentMessages[0].error &&
unsentMessages[0].error.data &&
unsentMessages[0].error.data.error &&
unsentMessages[0].error.name !== "UnknownDeviceError"
) {
return unsentMessages[0].error.data.error;
}
for (const event of unsentMessages) {
if (!event.error || event.error.name !== "UnknownDeviceError") {
return _t("Some of your messages have not been sent.");
}
}
return _t("Message not sent due to unknown devices being present");
},
_getUnsentMessages: function(room) {
if (!room) { return []; }
return room.getPendingEvents().filter(function(ev) {
return ev.status === Matrix.EventStatus.NOT_SENT;
});
},
_updateConfCallNotification: function() {
const room = this.state.room;
if (!room || !this.props.ConferenceHandler) {
@ -784,14 +746,6 @@ module.exports = React.createClass({
}
},
onResendAllClick: function() {
Resend.resendUnsentEvents(this.state.room);
},
onCancelAllClick: function() {
Resend.cancelUnsentEvents(this.state.room);
},
onInviteButtonClick: function() {
// call AddressPickerDialog
dis.dispatch({
@ -935,11 +889,7 @@ module.exports = React.createClass({
file, this.state.room.roomId, MatrixClientPeg.get(),
).done(undefined, (error) => {
if (error.name === "UnknownDeviceError") {
dis.dispatch({
action: 'unknown_device_error',
err: error,
room: this.state.room,
});
// Let the staus bar handle this
return;
}
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
@ -1571,12 +1521,9 @@ module.exports = React.createClass({
statusBar = <RoomStatusBar
room={this.state.room}
numUnreadMessages={this.state.numUnreadMessages}
unsentMessageError={this.state.unsentMessageError}
atEndOfLiveTimeline={this.state.atEndOfLiveTimeline}
sentMessageAndIsAlone={this.state.isAlone}
hasActiveCall={inCall}
onResendAllClick={this.onResendAllClick}
onCancelAllClick={this.onCancelAllClick}
onInviteClick={this.onInviteButtonClick}
onStopWarningClick={this.onStopAloneWarningClick}
onScrollToBottomClick={this.jumpToLiveTimeline}

View file

@ -1,5 +1,6 @@
/*
Copyright 2017 Vector Creations Ltd
Copyright 2017 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.
@ -15,6 +16,7 @@ limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import sdk from '../../../index';
import MatrixClientPeg from '../../../MatrixClientPeg';
import GeminiScrollbar from 'react-gemini-scrollbar';
@ -22,6 +24,14 @@ import Resend from '../../../Resend';
import { _t } from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore";
function markAllDevicesKnown(devices) {
Object.keys(devices).forEach((userId) => {
Object.keys(devices[userId]).map((deviceId) => {
MatrixClientPeg.get().setDeviceKnown(userId, deviceId, true);
});
});
}
function DeviceListEntry(props) {
const {userId, device} = props;
@ -38,10 +48,10 @@ function DeviceListEntry(props) {
}
DeviceListEntry.propTypes = {
userId: React.PropTypes.string.isRequired,
userId: PropTypes.string.isRequired,
// deviceinfo
device: React.PropTypes.object.isRequired,
device: PropTypes.object.isRequired,
};
@ -61,10 +71,10 @@ function UserUnknownDeviceList(props) {
}
UserUnknownDeviceList.propTypes = {
userId: React.PropTypes.string.isRequired,
userId: PropTypes.string.isRequired,
// map from deviceid -> deviceinfo
userDevices: React.PropTypes.object.isRequired,
userDevices: PropTypes.object.isRequired,
};
@ -83,7 +93,7 @@ function UnknownDeviceList(props) {
UnknownDeviceList.propTypes = {
// map from userid -> deviceid -> deviceinfo
devices: React.PropTypes.object.isRequired,
devices: PropTypes.object.isRequired,
};
@ -91,28 +101,63 @@ export default React.createClass({
displayName: 'UnknownDeviceDialog',
propTypes: {
room: React.PropTypes.object.isRequired,
room: PropTypes.object.isRequired,
// map from userid -> deviceid -> deviceinfo
devices: React.PropTypes.object.isRequired,
onFinished: React.PropTypes.func.isRequired,
// map from userid -> deviceid -> deviceinfo or null if devices are not yet loaded
devices: PropTypes.object,
onFinished: PropTypes.func.isRequired,
// Label for the button that marks all devices known and tries the send again
sendAnywayLabel: PropTypes.string.isRequired,
// Label for the button that to send the event if you've verified all devices
sendLabel: PropTypes.string.isRequired,
// function to retry the request once all devices are verified / known
onSend: PropTypes.func.isRequired,
},
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);
});
});
componentWillMount: function() {
MatrixClientPeg.get().on("deviceVerificationChanged", this._onDeviceVerificationChanged);
},
// XXX: temporary logging to try to diagnose
// https://github.com/vector-im/riot-web/issues/3148
console.log('Opening UnknownDeviceDialog');
componentWillUnmount: function() {
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener("deviceVerificationChanged", this._onDeviceVerificationChanged);
}
},
_onDeviceVerificationChanged: function(userId, deviceId, deviceInfo) {
if (this.props.devices[userId] && this.props.devices[userId][deviceId]) {
// XXX: Mutating props :/
this.props.devices[userId][deviceId] = deviceInfo;
this.forceUpdate();
}
},
_onDismissClicked: function() {
this.props.onFinished();
},
_onSendAnywayClicked: function() {
markAllDevicesKnown(this.props.devices);
this.props.onFinished();
this.props.onSend();
},
_onSendClicked: function() {
this.props.onFinished();
this.props.onSend();
},
render: function() {
if (this.props.devices === null) {
const Spinner = sdk.getComponent("elements.Spinner");
return <Spinner />;
}
let warning;
if (SettingsStore.getValue("blacklistUnverifiedDevices", this.props.room.roomId)) {
warning = (
@ -133,15 +178,30 @@ export default React.createClass({
);
}
let haveUnknownDevices = false;
Object.keys(this.props.devices).forEach((userId) => {
Object.keys(this.props.devices[userId]).map((deviceId) => {
const device = this.props.devices[userId][deviceId];
if (device.isUnverified() && !device.isKnown()) {
haveUnknownDevices = true;
}
});
});
let sendButton;
if (haveUnknownDevices) {
sendButton = <button onClick={this._onSendAnywayClicked}>
{ this.props.sendAnywayLabel }
</button>;
} else {
sendButton = <button onClick={this._onSendClicked}>
{ this.props.sendLabel }
</button>;
}
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
return (
<BaseDialog className='mx_UnknownDeviceDialog'
onFinished={() => {
// XXX: temporary logging to try to diagnose
// https://github.com/vector-im/riot-web/issues/3148
console.log("UnknownDeviceDialog closed by escape");
this.props.onFinished();
}}
onFinished={this.props.onFinished}
title={_t('Room contains unknown devices')}
>
<GeminiScrollbar autoshow={false} className="mx_Dialog_content">
@ -154,21 +214,11 @@ export default React.createClass({
<UnknownDeviceList devices={this.props.devices} />
</GeminiScrollbar>
<div className="mx_Dialog_buttons">
{sendButton}
<button className="mx_Dialog_primary" autoFocus={true}
onClick={() => {
this.props.onFinished();
Resend.resendUnsentEvents(this.props.room);
}}>
{ _t("Send anyway") }
</button>
<button className="mx_Dialog_primary" autoFocus={true}
onClick={() => {
// XXX: temporary logging to try to diagnose
// https://github.com/vector-im/riot-web/issues/3148
console.log("UnknownDeviceDialog closed by OK");
this.props.onFinished();
}}>
OK
onClick={this._onDismissClicked}
>
{_t("Dismiss")}
</button>
</div>
</BaseDialog>

View file

@ -52,11 +52,13 @@ export default React.createClass({
userId: React.PropTypes.string.isRequired,
// UserId of the entity that added / modified the widget
creatorUserId: React.PropTypes.string,
waitForIframeLoad: React.PropTypes.bool,
},
getDefaultProps() {
return {
url: "",
waitForIframeLoad: true,
};
},
@ -71,7 +73,7 @@ export default React.createClass({
const hasPermissionToLoad = localStorage.getItem(widgetPermissionId);
return {
initialising: true, // True while we are mangling the widget URL
loading: true, // True while the iframe content is loading
loading: this.props.waitForIframeLoad, // True while the iframe content is loading
widgetUrl: this._addWurlParams(newProps.url),
widgetPermissionId: widgetPermissionId,
// Assume that widget has permission to load if we are the user who
@ -220,7 +222,7 @@ export default React.createClass({
if (nextProps.url !== this.props.url) {
this._getNewState(nextProps);
this.setScalarToken();
} else if (nextProps.show && !this.props.show) {
} else if (nextProps.show && !this.props.show && this.props.waitForIframeLoad) {
this.setState({
loading: true,
});

View file

@ -478,7 +478,7 @@ module.exports = React.createClass({
}
const toggleButton = (
<div className={"mx_MemberEventListSummary_toggle"} onClick={this._toggleSummary}>
{ expanded ? 'collapse' : 'expand' }
{ expanded ? _t('collapse') : _t('expand') }
</div>
);

View file

@ -253,7 +253,7 @@ module.exports = React.createClass({
return (
<div>
<h3>Addresses</h3>
<h3>{ _t('Addresses') }</h3>
<div className="mx_RoomSettings_aliasLabel">
{ _t('The main address for this room is') }: { canonical_alias_section }
</div>

View file

@ -133,14 +133,17 @@ module.exports = React.createClass({
'$matrix_avatar_url': user ? MatrixClientPeg.get().mxcUrlToHttp(user.avatarUrl) : '',
};
app.id = appId;
app.name = app.name || app.type;
if (app.data) {
Object.keys(app.data).forEach((key) => {
params['$' + key] = app.data[key];
});
app.waitForIframeLoad = (app.data.waitForIframeLoad === 'false' ? false : true);
}
app.id = appId;
app.name = app.name || app.type;
app.url = this.encodeUri(app.url, params);
app.creatorUserId = (sender && sender.userId) ? sender.userId : null;
@ -225,6 +228,7 @@ module.exports = React.createClass({
show={this.props.showApps}
creatorUserId={app.creatorUserId}
widgetPageTitle={(app.data && app.data.title) ? app.data.title : ''}
waitForIframeLoad={app.waitForIframeLoad}
/>);
});

View file

@ -74,13 +74,6 @@ function onSendMessageFailed(err, room) {
// XXX: temporary logging to try to diagnose
// https://github.com/vector-im/riot-web/issues/3148
console.log('MessageComposer got send failure: ' + err.name + '('+err+')');
if (err.name === "UnknownDeviceError") {
dis.dispatch({
action: 'unknown_device_error',
err: err,
room: room,
});
}
dis.dispatch({
action: 'message_send_failed',
});