Merge up from develop
This commit is contained in:
commit
524eeaa315
27 changed files with 780 additions and 171 deletions
|
@ -188,7 +188,10 @@ module.exports = React.createClass({
|
|||
for (let i = 0; i < dmRooms.length; i++) {
|
||||
let room = MatrixClientPeg.get().getRoom(dmRooms[i]);
|
||||
if (room) {
|
||||
return room;
|
||||
const me = room.getMember(MatrixClientPeg.get().credentials.userId);
|
||||
if (me.membership == 'join') {
|
||||
return room;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -221,6 +224,17 @@ module.exports = React.createClass({
|
|||
})
|
||||
.done();
|
||||
}
|
||||
// // Start the chat
|
||||
// createRoom({dmUserId: addr})
|
||||
// .catch(function(err) {
|
||||
// var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
// Modal.createDialog(ErrorDialog, {
|
||||
// title: "Failure to invite user",
|
||||
// description: err.toString()
|
||||
// });
|
||||
// return null;
|
||||
// })
|
||||
// .done();
|
||||
|
||||
// Close - this will happen before the above, as that is async
|
||||
this.props.onFinished(true, addr);
|
||||
|
|
123
src/components/views/dialogs/EncryptedEventDialog.js
Normal file
123
src/components/views/dialogs/EncryptedEventDialog.js
Normal file
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
var React = require("react");
|
||||
var sdk = require('../../../index');
|
||||
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'EncryptedEventDialog',
|
||||
|
||||
propTypes: {
|
||||
onFinished: React.PropTypes.func,
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
var client = MatrixClientPeg.get();
|
||||
client.on("deviceVerificationChanged", this.onDeviceVerificationChanged);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
var client = MatrixClientPeg.get();
|
||||
if (client) {
|
||||
client.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged);
|
||||
}
|
||||
},
|
||||
|
||||
refreshDevice: function() {
|
||||
// XXX: gutwrench - is there any reason not to expose this on MatrixClient itself?
|
||||
return MatrixClientPeg.get()._crypto.getDeviceByIdentityKey(
|
||||
this.props.event.getSender(),
|
||||
this.props.event.getWireContent().algorithm,
|
||||
this.props.event.getWireContent().sender_key
|
||||
);
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return { device: this.refreshDevice() };
|
||||
},
|
||||
|
||||
onDeviceVerificationChanged: function(userId, device) {
|
||||
if (userId == this.props.event.getSender()) {
|
||||
this.setState({ device: this.refreshDevice() });
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var event = this.props.event;
|
||||
var device = this.state.device;
|
||||
|
||||
var MemberDeviceInfo = sdk.getComponent('rooms.MemberDeviceInfo');
|
||||
|
||||
return (
|
||||
<div className="mx_EncryptedEventDialog">
|
||||
<div className="mx_Dialog_title">
|
||||
End-to-end encryption information
|
||||
</div>
|
||||
<div className="mx_Dialog_content">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Sent by</td>
|
||||
<td>{ event.getSender() }</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Sender device name</td>
|
||||
<td>{ device.getDisplayName() }</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Sender device ID</td>
|
||||
<td>{ device.deviceId }</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Sender device verification:</td>
|
||||
<td>{ MatrixClientPeg.get().isEventSenderVerified(event) ? "verified" : <b>NOT verified</b> }</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Sender device ed25519 fingerprint</td>
|
||||
<td>{ device.getFingerprint() }</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Sender device curve25519 identity key</td>
|
||||
<td>{ event.getWireContent().sender_key }</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Algorithm</td>
|
||||
<td>{ event.getWireContent().algorithm }</td>
|
||||
</tr>
|
||||
{
|
||||
event.getContent().msgtype === 'm.bad.encrypted' ? (
|
||||
<tr>
|
||||
<td>Decryption error</td>
|
||||
<td>{ event.getContent().body }</td>
|
||||
</tr>
|
||||
) : ''
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className="mx_Dialog_buttons">
|
||||
<button className="mx_Dialog_primary" onClick={ this.props.onFinished } autoFocus={ true }>
|
||||
OK
|
||||
</button>
|
||||
<MemberDeviceInfo ref="memberDeviceInfo" hideInfo={true} device={ this.state.device } userId={ this.props.event.getSender() }/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -18,6 +18,11 @@ var dis = require("../../../dispatcher");
|
|||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'LogoutPrompt',
|
||||
|
||||
propTypes: {
|
||||
onFinished: React.PropTypes.func,
|
||||
},
|
||||
|
||||
logOut: function() {
|
||||
dis.dispatch({action: 'logout'});
|
||||
if (this.props.onFinished) {
|
||||
|
|
|
@ -57,18 +57,34 @@ module.exports = React.createClass({
|
|||
var TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||
|
||||
if (httpUrl) {
|
||||
return (
|
||||
<span className="mx_MFileBody">
|
||||
<div className="mx_MImageBody_download">
|
||||
<a href={cli.mxcUrlToHttp(content.url)} target="_blank" rel="noopener">
|
||||
<TintableSvg src="img/download.svg" width="12" height="14"/>
|
||||
Download {text}
|
||||
</a>
|
||||
</div>
|
||||
</span>
|
||||
);
|
||||
if (this.props.tileShape === "file_grid") {
|
||||
return (
|
||||
<span className="mx_MFileBody">
|
||||
<div className="mx_MImageBody_download">
|
||||
<a className="mx_ImageBody_downloadLink" href={cli.mxcUrlToHttp(content.url)} target="_blank" rel="noopener">
|
||||
{ content.body && content.body.length > 0 ? content.body : "Attachment" }
|
||||
</a>
|
||||
<div className="mx_MImageBody_size">
|
||||
{ content.info && content.info.size ? filesize(content.info.size) : "" }
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
else {
|
||||
return (
|
||||
<span className="mx_MFileBody">
|
||||
<div className="mx_MImageBody_download">
|
||||
<a href={cli.mxcUrlToHttp(content.url)} target="_blank" rel="noopener">
|
||||
<TintableSvg src="img/download.svg" width="12" height="14"/>
|
||||
Download {text}
|
||||
</a>
|
||||
</div>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
var extra = text ? ': '+text : '';
|
||||
var extra = text ? (': ' + text) : '';
|
||||
return <span className="mx_MFileBody">
|
||||
Invalid file{extra}
|
||||
</span>
|
||||
|
|
|
@ -123,6 +123,30 @@ module.exports = React.createClass({
|
|||
var content = this.props.mxEvent.getContent();
|
||||
var cli = MatrixClientPeg.get();
|
||||
|
||||
var download;
|
||||
if (this.props.tileShape === "file_grid") {
|
||||
download = (
|
||||
<div className="mx_MImageBody_download">
|
||||
<a className="mx_MImageBody_downloadLink" href={cli.mxcUrlToHttp(content.url)} target="_blank" rel="noopener">
|
||||
{content.body}
|
||||
</a>
|
||||
<div className="mx_MImageBody_size">
|
||||
{ content.info && content.info.size ? filesize(content.info.size) : "" }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
else {
|
||||
download = (
|
||||
<div className="mx_MImageBody_download">
|
||||
<a href={cli.mxcUrlToHttp(content.url)} target="_blank" rel="noopener">
|
||||
<TintableSvg src="img/download.svg" width="12" height="14"/>
|
||||
Download {content.body} ({ content.info && content.info.size ? filesize(content.info.size) : "Unknown size" })
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
var thumbUrl = this._getThumbUrl();
|
||||
if (thumbUrl) {
|
||||
return (
|
||||
|
@ -133,12 +157,7 @@ module.exports = React.createClass({
|
|||
onMouseEnter={this.onImageEnter}
|
||||
onMouseLeave={this.onImageLeave} />
|
||||
</a>
|
||||
<div className="mx_MImageBody_download">
|
||||
<a href={cli.mxcUrlToHttp(content.url)} target="_blank" rel="noopener">
|
||||
<TintableSvg src="img/download.svg" width="12" height="14"/>
|
||||
Download {content.body} ({ content.info && content.info.size ? filesize(content.info.size) : "Unknown size" })
|
||||
</a>
|
||||
</div>
|
||||
{ download }
|
||||
</span>
|
||||
);
|
||||
} else if (content.body) {
|
||||
|
|
|
@ -69,12 +69,38 @@ module.exports = React.createClass({
|
|||
}
|
||||
}
|
||||
|
||||
var download;
|
||||
if (this.props.tileShape === "file_grid") {
|
||||
download = (
|
||||
<div className="mx_MImageBody_download">
|
||||
<a className="mx_MImageBody_downloadLink" href={cli.mxcUrlToHttp(content.url)} target="_blank" rel="noopener">
|
||||
{content.body}
|
||||
</a>
|
||||
<div className="mx_MImageBody_size">
|
||||
{ content.info && content.info.size ? filesize(content.info.size) : "" }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
else {
|
||||
var TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||
download = (
|
||||
<div className="mx_MImageBody_download">
|
||||
<a href={cli.mxcUrlToHttp(content.url)} target="_blank" rel="noopener">
|
||||
<TintableSvg src="img/download.svg" width="12" height="14"/>
|
||||
Download {content.body} ({ content.info && content.info.size ? filesize(content.info.size) : "Unknown size" })
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="mx_MVideoBody">
|
||||
<video className="mx_MVideoBody" src={cli.mxcUrlToHttp(content.url)} alt={content.body}
|
||||
controls preload={preload} autoPlay={false}
|
||||
height={height} width={width} poster={poster}>
|
||||
</video>
|
||||
{ download }
|
||||
</span>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -37,6 +37,9 @@ module.exports = React.createClass({
|
|||
|
||||
/* callback called when dynamic content in events are loaded */
|
||||
onWidgetLoad: React.PropTypes.func,
|
||||
|
||||
/* the shsape of the tile, used */
|
||||
tileShape: React.PropTypes.string,
|
||||
},
|
||||
|
||||
getEventTileOps: function() {
|
||||
|
@ -69,6 +72,7 @@ module.exports = React.createClass({
|
|||
return <BodyType ref="body" mxEvent={this.props.mxEvent} highlights={this.props.highlights}
|
||||
highlightLink={this.props.highlightLink}
|
||||
showUrlPreview={this.props.showUrlPreview}
|
||||
tileShape={this.props.tileShape}
|
||||
onWidgetLoad={this.props.onWidgetLoad} />;
|
||||
},
|
||||
});
|
||||
|
|
|
@ -93,8 +93,9 @@ module.exports = React.createClass({
|
|||
}
|
||||
else {
|
||||
joinText = (<span>
|
||||
Join as <a onClick={(event)=>{ this.onConferenceNotificationClick(event, 'voice')}} href="#">voice</a>
|
||||
or <a onClick={(event)=>{ this.onConferenceNotificationClick(event, 'video') }} href="#">video</a>.
|
||||
Join as <a onClick={(event)=>{ this.onConferenceNotificationClick(event, 'voice')}}
|
||||
href="#">voice</a> or <a onClick={(event)=>{ this.onConferenceNotificationClick(event, 'video') }}
|
||||
href="#">video</a>.
|
||||
</span>);
|
||||
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ limitations under the License.
|
|||
|
||||
var React = require('react');
|
||||
var classNames = require("classnames");
|
||||
var Modal = require('../../../Modal');
|
||||
|
||||
var sdk = require('../../../index');
|
||||
var MatrixClientPeg = require('../../../MatrixClientPeg')
|
||||
|
@ -128,6 +129,15 @@ module.exports = React.createClass({
|
|||
/* the status of this event - ie, mxEvent.status. Denormalised to here so
|
||||
* that we can tell when it changes. */
|
||||
eventSendStatus: React.PropTypes.string,
|
||||
|
||||
/* the shape of the tile. by default, the layout is intended for the
|
||||
* normal room timeline. alternative values are: "file_list", "file_grid"
|
||||
* and "notif". This could be done by CSS, but it'd be horribly inefficient.
|
||||
* It could also be done by subclassing EventTile, but that'd be quite
|
||||
* boiilerplatey. So just make the necessary render decisions conditional
|
||||
* for now.
|
||||
*/
|
||||
tileShape: React.PropTypes.string,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
|
@ -239,8 +249,7 @@ module.exports = React.createClass({
|
|||
if (!actions || !actions.tweaks) { return false; }
|
||||
|
||||
// don't show self-highlights from another of our clients
|
||||
if (this.props.mxEvent.sender &&
|
||||
this.props.mxEvent.sender.userId === MatrixClientPeg.get().credentials.userId)
|
||||
if (this.props.mxEvent.getSender() === MatrixClientPeg.get().credentials.userId)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -353,6 +362,15 @@ module.exports = React.createClass({
|
|||
});
|
||||
},
|
||||
|
||||
onCryptoClicked: function(e) {
|
||||
var EncryptedEventDialog = sdk.getComponent("dialogs.EncryptedEventDialog");
|
||||
var event = this.props.mxEvent;
|
||||
|
||||
Modal.createDialog(EncryptedEventDialog, {
|
||||
event: event,
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var MessageTimestamp = sdk.getComponent('messages.MessageTimestamp');
|
||||
var SenderProfile = sdk.getComponent('messages.SenderProfile');
|
||||
|
@ -366,7 +384,7 @@ module.exports = React.createClass({
|
|||
|
||||
// Info messages are basically information about commands processed on a
|
||||
// room, or emote messages
|
||||
var isInfoMessage = (msgtype === 'm.emote' || eventType !== 'm.room.message');
|
||||
var isInfoMessage = (eventType !== 'm.room.message');
|
||||
|
||||
var EventTileType = sdk.getComponent(eventTileTypes[eventType]);
|
||||
// This shouldn't happen: the caller should check we support this type
|
||||
|
@ -375,25 +393,26 @@ module.exports = React.createClass({
|
|||
throw new Error("Event type not supported");
|
||||
}
|
||||
|
||||
var e2eEnabled = MatrixClientPeg.get().isRoomEncrypted(this.props.mxEvent.getRoomId());
|
||||
var isSending = (['sending', 'queued', 'encrypting'].indexOf(this.props.eventSendStatus) !== -1);
|
||||
|
||||
var classes = classNames({
|
||||
mx_EventTile: true,
|
||||
mx_EventTile_info: isInfoMessage,
|
||||
mx_EventTile_sending: ['sending', 'queued'].indexOf(
|
||||
this.props.eventSendStatus
|
||||
) !== -1,
|
||||
mx_EventTile_encrypting: this.props.eventSendStatus == 'encrypting',
|
||||
mx_EventTile_sending: isSending,
|
||||
mx_EventTile_notSent: this.props.eventSendStatus == 'not_sent',
|
||||
mx_EventTile_highlight: this.shouldHighlight(),
|
||||
mx_EventTile_highlight: this.props.tileShape == 'notif' ? false : this.shouldHighlight(),
|
||||
mx_EventTile_selected: this.props.isSelectedEvent,
|
||||
mx_EventTile_continuation: this.props.continuation,
|
||||
mx_EventTile_continuation: this.props.tileShape ? '' : this.props.continuation,
|
||||
mx_EventTile_last: this.props.last,
|
||||
mx_EventTile_contextual: this.props.contextual,
|
||||
menu: this.state.menu,
|
||||
mx_EventTile_verified: this.state.verified == true,
|
||||
mx_EventTile_verified: this.state.verified == true || (e2eEnabled && isSending),
|
||||
mx_EventTile_unverified: this.state.verified == false,
|
||||
mx_EventTile_bad: this.props.mxEvent.getContent().msgtype === 'm.bad.encrypted',
|
||||
});
|
||||
var timestamp = <a href={ "#/room/" + this.props.mxEvent.getRoomId() +"/"+ this.props.mxEvent.getId() }>
|
||||
<MessageTimestamp ts={this.props.mxEvent.getTs()} />
|
||||
</a>
|
||||
var permalink = "#/room/" + this.props.mxEvent.getRoomId() +"/"+ this.props.mxEvent.getId();
|
||||
|
||||
var readAvatars = this.getReadAvatars();
|
||||
|
||||
|
@ -401,8 +420,11 @@ module.exports = React.createClass({
|
|||
let avatarSize;
|
||||
let needsSenderProfile;
|
||||
|
||||
if (isInfoMessage) {
|
||||
// a small avatar, with no sender profile, for emotes and
|
||||
if (this.props.tileShape === "notif") {
|
||||
avatarSize = 24;
|
||||
needsSenderProfile = true;
|
||||
} else if (isInfoMessage) {
|
||||
// a small avatar, with no sender profile, for
|
||||
// joins/parts/etc
|
||||
avatarSize = 14;
|
||||
needsSenderProfile = false;
|
||||
|
@ -428,35 +450,109 @@ module.exports = React.createClass({
|
|||
|
||||
if (needsSenderProfile) {
|
||||
let aux = null;
|
||||
if (msgtype === 'm.image') aux = "sent an image";
|
||||
else if (msgtype === 'm.video') aux = "sent a video";
|
||||
else if (msgtype === 'm.file') aux = "uploaded a file";
|
||||
|
||||
sender = <SenderProfile onClick={ this.onSenderProfileClick } mxEvent={this.props.mxEvent} aux={aux} />;
|
||||
if (!this.props.tileShape) {
|
||||
if (msgtype === 'm.image') aux = "sent an image";
|
||||
else if (msgtype === 'm.video') aux = "sent a video";
|
||||
else if (msgtype === 'm.file') aux = "uploaded a file";
|
||||
sender = <SenderProfile onClick={ this.onSenderProfileClick } mxEvent={this.props.mxEvent} aux={aux} />;
|
||||
}
|
||||
else {
|
||||
sender = <SenderProfile mxEvent={this.props.mxEvent} />;
|
||||
}
|
||||
}
|
||||
|
||||
var editButton = (
|
||||
<img className="mx_EventTile_editButton" src="img/icon_context_message.svg" width="19" height="19" alt="Options" title="Options" onClick={this.onEditClicked} />
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={classes}>
|
||||
<div className="mx_EventTile_msgOption">
|
||||
{ readAvatars }
|
||||
var e2e;
|
||||
if (e2eEnabled) {
|
||||
if (this.props.mxEvent.getContent().msgtype === 'm.bad.encrypted') {
|
||||
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" src="img/e2e-blocked.svg" width="12" height="12" style={{ marginLeft: "-1px" }} />;
|
||||
}
|
||||
else if (this.state.verified == true) {
|
||||
e2e = <img className="mx_EventTile_e2eIcon" src="img/e2e-verified.svg" width="10" height="12" alt="Encrypted by a verified device"/>;
|
||||
}
|
||||
else if (this.state.verified == false) {
|
||||
e2e = <img className="mx_EventTile_e2eIcon" src="img/e2e-warning.svg" width="15" height="12" style={{ marginLeft: "-2px" }} alt="Encrypted by an unverified device!"/>;
|
||||
}
|
||||
else {
|
||||
e2e = <img className="mx_EventTile_e2eIcon" src="img/e2e-unencrypted.svg" width="12" height="12"/>;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.props.tileShape === "notif") {
|
||||
var room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
|
||||
|
||||
return (
|
||||
<div className={classes}>
|
||||
<div className="mx_EventTile_roomName">
|
||||
<a href={ permalink }>
|
||||
{ room ? room.name : '' }
|
||||
</a>
|
||||
</div>
|
||||
<div className="mx_EventTile_senderDetails">
|
||||
{ avatar }
|
||||
<a href={ permalink }>
|
||||
{ sender }
|
||||
<MessageTimestamp ts={this.props.mxEvent.getTs()} />
|
||||
</a>
|
||||
</div>
|
||||
<div className="mx_EventTile_line" >
|
||||
<EventTileType ref="tile"
|
||||
mxEvent={this.props.mxEvent}
|
||||
highlights={this.props.highlights}
|
||||
highlightLink={this.props.highlightLink}
|
||||
showUrlPreview={this.props.showUrlPreview}
|
||||
onWidgetLoad={this.props.onWidgetLoad} />
|
||||
</div>
|
||||
</div>
|
||||
{ avatar }
|
||||
{ sender }
|
||||
<div className="mx_EventTile_line">
|
||||
{ timestamp }
|
||||
<EventTileType ref="tile"
|
||||
mxEvent={this.props.mxEvent}
|
||||
highlights={this.props.highlights}
|
||||
highlightLink={this.props.highlightLink}
|
||||
showUrlPreview={this.props.showUrlPreview}
|
||||
onWidgetLoad={this.props.onWidgetLoad} />
|
||||
{ editButton }
|
||||
);
|
||||
}
|
||||
else if (this.props.tileShape === "file_grid") {
|
||||
return (
|
||||
<div className={classes}>
|
||||
<div className="mx_EventTile_line" >
|
||||
<EventTileType ref="tile"
|
||||
mxEvent={this.props.mxEvent}
|
||||
highlights={this.props.highlights}
|
||||
highlightLink={this.props.highlightLink}
|
||||
showUrlPreview={this.props.showUrlPreview}
|
||||
tileShape={this.props.tileShape}
|
||||
onWidgetLoad={this.props.onWidgetLoad} />
|
||||
</div>
|
||||
<a className="mx_EventTile_senderDetailsLink" href={ permalink }>
|
||||
<div className="mx_EventTile_senderDetails">
|
||||
{ sender }
|
||||
<MessageTimestamp ts={this.props.mxEvent.getTs()} />
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
else {
|
||||
return (
|
||||
<div className={classes}>
|
||||
<div className="mx_EventTile_msgOption">
|
||||
{ readAvatars }
|
||||
</div>
|
||||
{ avatar }
|
||||
{ sender }
|
||||
<div className="mx_EventTile_line">
|
||||
<a href={ permalink }>
|
||||
<MessageTimestamp ts={this.props.mxEvent.getTs()} />
|
||||
</a>
|
||||
{ e2e }
|
||||
<EventTileType ref="tile"
|
||||
mxEvent={this.props.mxEvent}
|
||||
highlights={this.props.highlights}
|
||||
highlightLink={this.props.highlightLink}
|
||||
showUrlPreview={this.props.showUrlPreview}
|
||||
onWidgetLoad={this.props.onWidgetLoad} />
|
||||
{ editButton }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -54,33 +54,33 @@ export default class MemberDeviceInfo extends React.Component {
|
|||
var indicator = null, blockButton = null, verifyButton = null;
|
||||
if (this.props.device.isBlocked()) {
|
||||
blockButton = (
|
||||
<div className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_unblock"
|
||||
<button className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_unblock"
|
||||
onClick={this.onUnblockClick}>
|
||||
Unblock
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
} else {
|
||||
blockButton = (
|
||||
<div className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_block"
|
||||
<button className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_block"
|
||||
onClick={this.onBlockClick}>
|
||||
Block
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.device.isVerified()) {
|
||||
verifyButton = (
|
||||
<div className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_unverify"
|
||||
<button className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_unverify"
|
||||
onClick={this.onUnverifyClick}>
|
||||
Unverify
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
} else {
|
||||
verifyButton = (
|
||||
<div className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_verify"
|
||||
<button className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_verify"
|
||||
onClick={this.onVerifyClick}>
|
||||
Verify
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -101,16 +101,25 @@ export default class MemberDeviceInfo extends React.Component {
|
|||
|
||||
var deviceName = this.props.device.getDisplayName() || this.props.device.deviceId;
|
||||
|
||||
var info;
|
||||
if (!this.props.hideInfo) {
|
||||
info = (
|
||||
<div>
|
||||
<div className="mx_MemberDeviceInfo_deviceId">{deviceName}</div>
|
||||
{indicator}
|
||||
<div className="mx_MemberDeviceInfo_deviceKey">
|
||||
{this.props.device.getFingerprint()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// add the deviceId as a titletext to help with debugging
|
||||
return (
|
||||
<div className="mx_MemberDeviceInfo" title={this.props.device.deviceId}>
|
||||
<div className="mx_MemberDeviceInfo_deviceId">{deviceName}</div>
|
||||
{indicator}
|
||||
<div className="mx_MemberDeviceInfo_deviceKey">
|
||||
{this.props.device.getFingerprint()}
|
||||
</div>
|
||||
{verifyButton}
|
||||
{blockButton}
|
||||
{ info }
|
||||
{ verifyButton }
|
||||
{ blockButton }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -120,4 +129,5 @@ MemberDeviceInfo.displayName = 'MemberDeviceInfo';
|
|||
MemberDeviceInfo.propTypes = {
|
||||
userId: React.PropTypes.string.isRequired,
|
||||
device: React.PropTypes.object.isRequired,
|
||||
hideInfo: React.PropTypes.bool,
|
||||
};
|
||||
|
|
|
@ -421,11 +421,7 @@ module.exports = React.createClass({
|
|||
|
||||
onNewDMClick: function() {
|
||||
this.setState({ updating: this.state.updating + 1 });
|
||||
createRoom({
|
||||
createOpts: {
|
||||
invite: [this.props.member.userId],
|
||||
},
|
||||
}).finally(() => {
|
||||
createRoom({dmUserId: this.props.member.userId}).finally(() => {
|
||||
this.props.onFinished();
|
||||
this.setState({ updating: this.state.updating - 1 });
|
||||
}).done();
|
||||
|
|
|
@ -78,7 +78,7 @@ export default class MessageComposer extends React.Component {
|
|||
|
||||
let fileList = [];
|
||||
for (let i=0; i<files.length; i++) {
|
||||
fileList.push(<li>
|
||||
fileList.push(<li key={i}>
|
||||
<TintableSvg key={i} src="img/files.svg" width="16" height="16" /> {files[i].name}
|
||||
</li>);
|
||||
}
|
||||
|
@ -201,6 +201,13 @@ export default class MessageComposer extends React.Component {
|
|||
</div>
|
||||
);
|
||||
|
||||
if (MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId)) {
|
||||
// FIXME: show a /!\ if there are untrusted devices in the room...
|
||||
controls.push(
|
||||
<img key="e2eIcon" className="mx_MessageComposer_e2eIcon" src="img/e2e-verified.svg" width="10" height="12" alt="Encrypted room"/>
|
||||
);
|
||||
}
|
||||
|
||||
var callButton, videoCallButton, hangupButton;
|
||||
if (this.props.callState && this.props.callState !== 'ended') {
|
||||
hangupButton =
|
||||
|
|
|
@ -147,8 +147,10 @@ module.exports = React.createClass({
|
|||
this._updateStickyHeaders(true, scrollToPosition);
|
||||
},
|
||||
|
||||
onRoomTimeline: function(ev, room, toStartOfTimeline) {
|
||||
onRoomTimeline: function(ev, room, toStartOfTimeline, removed, data) {
|
||||
if (toStartOfTimeline) return;
|
||||
if (!room) return;
|
||||
if (data.timeline.getTimelineSet() !== room.getUnfilteredTimelineSet()) return;
|
||||
this._delayedRefreshRoomList();
|
||||
},
|
||||
|
||||
|
|
|
@ -106,14 +106,16 @@ module.exports = React.createClass({
|
|||
|
||||
onMouseEnter: function() {
|
||||
this.setState( { hover : true });
|
||||
this.badgeOnMouseEnter();
|
||||
},
|
||||
|
||||
onMouseLeave: function() {
|
||||
this.setState( { hover : false });
|
||||
this.badgeOnMouseLeave();
|
||||
},
|
||||
|
||||
badgeOnMouseEnter: function() {
|
||||
// Only allow none guests to access the context menu
|
||||
// Only allow non-guests to access the context menu
|
||||
// and only change it if it needs to change
|
||||
if (!MatrixClientPeg.get().isGuest() && !this.state.badgeHover) {
|
||||
this.setState( { badgeHover : true } );
|
||||
|
@ -241,7 +243,7 @@ module.exports = React.createClass({
|
|||
badgeContent = '\u200B';
|
||||
}
|
||||
|
||||
badge = <div className={ badgeClasses } onClick={this.onBadgeClicked} onMouseEnter={this.badgeOnMouseEnter} onMouseLeave={this.badgeOnMouseLeave}>{ badgeContent }</div>;
|
||||
badge = <div className={ badgeClasses } onClick={this.onBadgeClicked}>{ badgeContent }</div>;
|
||||
|
||||
const EmojiText = sdk.getComponent('elements.EmojiText');
|
||||
var label;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue