merge in develop

This commit is contained in:
Matthew Hodgson 2018-09-16 19:06:52 +01:00
commit 3b868064c7
1006 changed files with 78601 additions and 15855 deletions

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.
@ -17,39 +18,56 @@ limitations under the License.
'use strict';
var React = require('react');
var classNames = require("classnames");
import { _t } from '../../../languageHandler';
var Modal = require('../../../Modal');
import ReplyThread from "../elements/ReplyThread";
var sdk = require('../../../index');
var TextForEvent = require('../../../TextForEvent');
const React = require('react');
import PropTypes from 'prop-types';
const classNames = require("classnames");
import { _t, _td } from '../../../languageHandler';
const Modal = require('../../../Modal');
const sdk = require('../../../index');
const TextForEvent = require('../../../TextForEvent');
import withMatrixClient from '../../../wrappers/withMatrixClient';
var ContextualMenu = require('../../structures/ContextualMenu');
const ContextualMenu = require('../../structures/ContextualMenu');
import dis from '../../../dispatcher';
import {makeEventPermalink} from "../../../matrix-to";
import SettingsStore from "../../../settings/SettingsStore";
import {EventStatus} from 'matrix-js-sdk';
var ObjectUtils = require('../../../ObjectUtils');
const ObjectUtils = require('../../../ObjectUtils');
var eventTileTypes = {
const eventTileTypes = {
'm.room.message': 'messages.MessageEvent',
'm.room.aliases': 'messages.RoomAliasesEvent',
'm.room.member' : 'messages.TextualEvent',
'm.call.invite' : 'messages.TextualEvent',
'm.call.answer' : 'messages.TextualEvent',
'm.call.hangup' : 'messages.TextualEvent',
'm.room.name' : 'messages.TextualEvent',
'm.room.avatar' : 'messages.RoomAvatarEvent',
'm.room.topic' : 'messages.TextualEvent',
'm.room.third_party_invite' : 'messages.TextualEvent',
'm.room.history_visibility' : 'messages.TextualEvent',
'm.room.encryption' : 'messages.TextualEvent',
'm.room.power_levels' : 'messages.TextualEvent',
'm.sticker': 'messages.MessageEvent',
'm.call.invite': 'messages.TextualEvent',
'm.call.answer': 'messages.TextualEvent',
'm.call.hangup': 'messages.TextualEvent',
};
const stateEventTileTypes = {
'm.room.aliases': 'messages.RoomAliasesEvent',
'm.room.create': 'messages.RoomCreate',
'm.room.member': 'messages.TextualEvent',
'm.room.name': 'messages.TextualEvent',
'm.room.avatar': 'messages.RoomAvatarEvent',
'm.room.third_party_invite': 'messages.TextualEvent',
'm.room.history_visibility': 'messages.TextualEvent',
'm.room.encryption': 'messages.TextualEvent',
'm.room.topic': 'messages.TextualEvent',
'm.room.power_levels': 'messages.TextualEvent',
'm.room.pinned_events': 'messages.TextualEvent',
'm.room.server_acl': 'messages.TextualEvent',
'im.vector.modular.widgets': 'messages.TextualEvent',
};
var MAX_READ_AVATARS = 5;
function getHandlerTile(ev) {
const type = ev.getType();
return ev.isState() ? stateEventTileTypes[type] : eventTileTypes[type];
}
const MAX_READ_AVATARS = 5;
// Our component structure for EventTiles on the timeline is:
//
@ -67,65 +85,65 @@ module.exports = withMatrixClient(React.createClass({
propTypes: {
/* MatrixClient instance for sender verification etc */
matrixClient: React.PropTypes.object.isRequired,
matrixClient: PropTypes.object.isRequired,
/* the MatrixEvent to show */
mxEvent: React.PropTypes.object.isRequired,
mxEvent: PropTypes.object.isRequired,
/* true if mxEvent is redacted. This is a prop because using mxEvent.isRedacted()
* might not be enough when deciding shouldComponentUpdate - prevProps.mxEvent
* references the same this.props.mxEvent.
*/
isRedacted: React.PropTypes.bool,
isRedacted: PropTypes.bool,
/* true if this is a continuation of the previous event (which has the
* effect of not showing another avatar/displayname
*/
continuation: React.PropTypes.bool,
continuation: PropTypes.bool,
/* true if this is the last event in the timeline (which has the effect
* of always showing the timestamp)
*/
last: React.PropTypes.bool,
last: PropTypes.bool,
/* true if this is search context (which has the effect of greying out
* the text
*/
contextual: React.PropTypes.bool,
contextual: PropTypes.bool,
/* a list of words to highlight, ordered by longest first */
highlights: React.PropTypes.array,
highlights: PropTypes.array,
/* link URL for the highlights */
highlightLink: React.PropTypes.string,
highlightLink: PropTypes.string,
/* should show URL previews for this event */
showUrlPreview: React.PropTypes.bool,
showUrlPreview: PropTypes.bool,
/* is this the focused event */
isSelectedEvent: React.PropTypes.bool,
isSelectedEvent: PropTypes.bool,
/* callback called when dynamic content in events are loaded */
onWidgetLoad: React.PropTypes.func,
onWidgetLoad: PropTypes.func,
/* a list of read-receipts we should show. Each object has a 'roomMember' and 'ts'. */
readReceipts: React.PropTypes.arrayOf(React.PropTypes.object),
readReceipts: PropTypes.arrayOf(React.PropTypes.object),
/* opaque readreceipt info for each userId; used by ReadReceiptMarker
* to manage its animations. Should be an empty object when the room
* first loads
*/
readReceiptMap: React.PropTypes.object,
readReceiptMap: PropTypes.object,
/* A function which is used to check if the parent panel is being
* unmounted, to avoid unnecessary work. Should return true if we
* are being unmounted.
*/
checkUnmounting: React.PropTypes.func,
checkUnmounting: PropTypes.func,
/* the status of this event - ie, mxEvent.status. Denormalised to here so
* that we can tell when it changes. */
eventSendStatus: React.PropTypes.string,
eventSendStatus: 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"
@ -134,14 +152,31 @@ module.exports = withMatrixClient(React.createClass({
* boiilerplatey. So just make the necessary render decisions conditional
* for now.
*/
tileShape: React.PropTypes.string,
tileShape: PropTypes.string,
// show twelve hour timestamps
isTwelveHour: React.PropTypes.bool,
isTwelveHour: PropTypes.bool,
},
getDefaultProps: function() {
return {
// no-op function because onWidgetLoad is optional yet some sub-components assume its existence
onWidgetLoad: function() {},
};
},
getInitialState: function() {
return {menu: false, allReadAvatars: false, verified: null};
return {
// Whether the context menu is being displayed.
menu: false,
// Whether all read receipts are being displayed. If not, only display
// a truncation of them.
allReadAvatars: false,
// Whether the event's sender has been verified.
verified: null,
// Whether onRequestKeysClick has been called since mounting.
previouslyRequestedKeys: false,
};
},
componentWillMount: function() {
@ -170,17 +205,12 @@ module.exports = withMatrixClient(React.createClass({
return true;
}
if (!this._propsEqual(this.props, nextProps)) {
return true;
}
return false;
return !this._propsEqual(this.props, nextProps);
},
componentWillUnmount: function() {
var client = this.props.matrixClient;
client.removeListener("deviceVerificationChanged",
this.onDeviceVerificationChanged);
const client = this.props.matrixClient;
client.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged);
this.props.mxEvent.removeListener("Event.decrypted", this._onDecrypted);
},
@ -188,12 +218,14 @@ module.exports = withMatrixClient(React.createClass({
*/
_onDecrypted: function() {
// we need to re-verify the sending device.
// (we call onWidgetLoad in _verifyEvent to handle the case where decryption
// has caused a change in size of the event tile)
this._verifyEvent(this.props.mxEvent);
this.forceUpdate();
},
onDeviceVerificationChanged: function(userId, device) {
if (userId == this.props.mxEvent.getSender()) {
if (userId === this.props.mxEvent.getSender()) {
this._verifyEvent(this.props.mxEvent);
}
},
@ -205,29 +237,32 @@ module.exports = withMatrixClient(React.createClass({
const verified = await this.props.matrixClient.isEventSenderVerified(mxEvent);
this.setState({
verified: verified
verified: verified,
}, () => {
// Decryption may have caused a change in size
this.props.onWidgetLoad();
});
},
_propsEqual: function(objA, objB) {
var keysA = Object.keys(objA);
var keysB = Object.keys(objB);
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
for (var i = 0; i < keysA.length; i++) {
var key = keysA[i];
for (let i = 0; i < keysA.length; i++) {
const key = keysA[i];
if (!objB.hasOwnProperty(key)) {
return false;
}
// need to deep-compare readReceipts
if (key == 'readReceipts') {
var rA = objA[key];
var rB = objB[key];
if (key === 'readReceipts') {
const rA = objA[key];
const rB = objB[key];
if (rA === rB) {
continue;
}
@ -239,7 +274,7 @@ module.exports = withMatrixClient(React.createClass({
if (rA.length !== rB.length) {
return false;
}
for (var j = 0; j < rA.length; j++) {
for (let j = 0; j < rA.length; j++) {
if (rA[j].roomMember.userId !== rB[j].roomMember.userId) {
return false;
}
@ -254,12 +289,11 @@ module.exports = withMatrixClient(React.createClass({
},
shouldHighlight: function() {
var actions = this.props.matrixClient.getPushActionsForEvent(this.props.mxEvent);
const actions = this.props.matrixClient.getPushActionsForEvent(this.props.mxEvent);
if (!actions || !actions.tweaks) { return false; }
// don't show self-highlights from another of our clients
if (this.props.mxEvent.getSender() === this.props.matrixClient.credentials.userId)
{
if (this.props.mxEvent.getSender() === this.props.matrixClient.credentials.userId) {
return false;
}
@ -267,37 +301,40 @@ module.exports = withMatrixClient(React.createClass({
},
onEditClicked: function(e) {
var MessageContextMenu = sdk.getComponent('context_menus.MessageContextMenu');
var buttonRect = e.target.getBoundingClientRect();
const MessageContextMenu = sdk.getComponent('context_menus.MessageContextMenu');
const buttonRect = e.target.getBoundingClientRect();
// The window X and Y offsets are to adjust position when zoomed in to page
var x = buttonRect.right + window.pageXOffset;
var y = (buttonRect.top + (buttonRect.height / 2) + window.pageYOffset) - 19;
var self = this;
const x = buttonRect.right + window.pageXOffset;
const y = (buttonRect.top + (buttonRect.height / 2) + window.pageYOffset) - 19;
const self = this;
const {tile, replyThread} = this.refs;
ContextualMenu.createMenu(MessageContextMenu, {
chevronOffset: 10,
mxEvent: this.props.mxEvent,
left: x,
top: y,
eventTileOps: this.refs.tile && this.refs.tile.getEventTileOps ? this.refs.tile.getEventTileOps() : undefined,
eventTileOps: tile && tile.getEventTileOps ? tile.getEventTileOps() : undefined,
collapseReplyThread: replyThread && replyThread.canCollapse() ? replyThread.collapse : undefined,
onFinished: function() {
self.setState({menu: false});
}
},
});
this.setState({menu: true});
},
toggleAllReadAvatars: function() {
this.setState({
allReadAvatars: !this.state.allReadAvatars
allReadAvatars: !this.state.allReadAvatars,
});
},
getReadAvatars: function() {
// return early if there are no read receipts
if (!this.props.readReceipts || this.props.readReceipts.length === 0) {
return (<span className="mx_EventTile_readAvatars"></span>);
return (<span className="mx_EventTile_readAvatars" />);
}
const ReadReceiptMarker = sdk.getComponent('rooms.ReadReceiptMarker');
@ -305,11 +342,11 @@ module.exports = withMatrixClient(React.createClass({
const receiptOffset = 15;
let left = 0;
var receipts = this.props.readReceipts || [];
for (var i = 0; i < receipts.length; ++i) {
var receipt = receipts[i];
const receipts = this.props.readReceipts || [];
for (let i = 0; i < receipts.length; ++i) {
const receipt = receipts[i];
var hidden = true;
let hidden = true;
if ((i < MAX_READ_AVATARS) || this.state.allReadAvatars) {
hidden = false;
}
@ -320,8 +357,8 @@ module.exports = withMatrixClient(React.createClass({
// else set it proportional to index
left = (hidden ? MAX_READ_AVATARS - 1 : i) * -receiptOffset;
var userId = receipt.roomMember.userId;
var readReceiptInfo;
const userId = receipt.roomMember.userId;
let readReceiptInfo;
if (this.props.readReceiptMap) {
readReceiptInfo = this.props.readReceiptMap[userId];
@ -341,12 +378,12 @@ module.exports = withMatrixClient(React.createClass({
onClick={this.toggleAllReadAvatars}
timestamp={receipt.ts}
showTwelveHour={this.props.isTwelveHour}
/>
/>,
);
}
var remText;
let remText;
if (!this.state.allReadAvatars) {
var remainder = receipts.length - MAX_READ_AVATARS;
const remainder = receipts.length - MAX_READ_AVATARS;
if (remainder > 0) {
remText = <span className="mx_EventTile_readAvatarRemainder"
onClick={this.toggleAllReadAvatars}
@ -370,7 +407,7 @@ module.exports = withMatrixClient(React.createClass({
},
onCryptoClicked: function(e) {
var event = this.props.mxEvent;
const event = this.props.mxEvent;
Modal.createTrackedDialogAsync('Encrypted Event Dialog', '', (cb) => {
require(['../../../async-components/views/dialogs/EncryptedEventDialog'], cb);
@ -379,6 +416,19 @@ module.exports = withMatrixClient(React.createClass({
});
},
onRequestKeysClick: function() {
this.setState({
// Indicate in the UI that the keys have been requested (this is expected to
// be reset if the component is mounted in the future).
previouslyRequestedKeys: true,
});
// Cancel any outgoing key request for this event and resend it. If a response
// is received for the request with the required keys, the event could be
// decrypted successfully.
this.props.matrixClient.cancelAndResendEventRoomKeyRequest(this.props.mxEvent);
},
onPermalinkClicked: function(e) {
// This allows the permalink to be opened in a new tab/window or copied as
// matrix.to, but also for it to enable routing within Riot when clicked.
@ -395,26 +445,27 @@ module.exports = withMatrixClient(React.createClass({
const ev = this.props.mxEvent;
const props = {onClick: this.onCryptoClicked};
// event could not be decrypted
if (ev.getContent().msgtype === 'm.bad.encrypted') {
return <E2ePadlockUndecryptable {...props}/>;
} else if (ev.isEncrypted()) {
if (this.state.verified) {
return <E2ePadlockVerified {...props}/>;
} else {
return <E2ePadlockUnverified {...props}/>;
}
} else {
// XXX: if the event is being encrypted (ie eventSendStatus ===
// encrypting), it might be nice to show something other than the
// open padlock?
return <E2ePadlockUndecryptable {...props} />;
}
// if the event is not encrypted, but it's an e2e room, show the
// open padlock
const e2eEnabled = this.props.matrixClient.isRoomEncrypted(ev.getRoomId());
if (e2eEnabled) {
return <E2ePadlockUnencrypted {...props}/>;
// event is encrypted, display padlock corresponding to whether or not it is verified
if (ev.isEncrypted()) {
return this.state.verified ? <E2ePadlockVerified {...props} /> : <E2ePadlockUnverified {...props} />;
}
if (this.props.matrixClient.isRoomEncrypted(ev.getRoomId())) {
// else if room is encrypted
// and event is being encrypted or is not_sent (Unknown Devices/Network Error)
if (ev.status === EventStatus.ENCRYPTING) {
return <E2ePadlockEncrypting {...props} />;
}
if (ev.status === EventStatus.NOT_SENT) {
return <E2ePadlockNotSent {...props} />;
}
// if the event is not encrypted, but it's an e2e room, show the open padlock
return <E2ePadlockUnencrypted {...props} />;
}
// no padlock needed
@ -422,62 +473,72 @@ module.exports = withMatrixClient(React.createClass({
},
render: function() {
var MessageTimestamp = sdk.getComponent('messages.MessageTimestamp');
var SenderProfile = sdk.getComponent('messages.SenderProfile');
var MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
const MessageTimestamp = sdk.getComponent('messages.MessageTimestamp');
const SenderProfile = sdk.getComponent('messages.SenderProfile');
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
//console.log("EventTile showUrlPreview for %s is %s", this.props.mxEvent.getId(), this.props.showUrlPreview);
var content = this.props.mxEvent.getContent();
var msgtype = content.msgtype;
var eventType = this.props.mxEvent.getType();
const content = this.props.mxEvent.getContent();
const msgtype = content.msgtype;
const eventType = this.props.mxEvent.getType();
// Info messages are basically information about commands processed on a room
var isInfoMessage = (eventType !== 'm.room.message');
const isInfoMessage = (eventType !== 'm.room.message' && eventType !== 'm.sticker' && eventType != 'm.room.create');
var EventTileType = sdk.getComponent(eventTileTypes[eventType]);
const tileHandler = getHandlerTile(this.props.mxEvent);
// This shouldn't happen: the caller should check we support this type
// before trying to instantiate us
if (!EventTileType) {
throw new Error("Event type not supported");
if (!tileHandler) {
const {mxEvent} = this.props;
console.warn(`Event type not supported: type:${mxEvent.getType()} isState:${mxEvent.isState()}`);
return <div className="mx_EventTile mx_EventTile_info mx_MNoticeBody">
<div className="mx_EventTile_line">
{ _t('This event could not be displayed') }
</div>
</div>;
}
const EventTileType = sdk.getComponent(tileHandler);
var isSending = (['sending', 'queued', 'encrypting'].indexOf(this.props.eventSendStatus) !== -1);
const isRedacted = (eventType === 'm.room.message') && this.props.isRedacted;
const isSending = (['sending', 'queued', 'encrypting'].indexOf(this.props.eventSendStatus) !== -1);
const isRedacted = isMessageEvent(this.props.mxEvent) && this.props.isRedacted;
const isEncryptionFailure = this.props.mxEvent.isDecryptionFailure();
const classes = classNames({
mx_EventTile: true,
mx_EventTile_info: isInfoMessage,
mx_EventTile_12hr: this.props.isTwelveHour,
mx_EventTile_encrypting: this.props.eventSendStatus == 'encrypting',
mx_EventTile_encrypting: this.props.eventSendStatus === 'encrypting',
mx_EventTile_sending: isSending,
mx_EventTile_notSent: this.props.eventSendStatus == 'not_sent',
mx_EventTile_highlight: this.props.tileShape == 'notif' ? false : this.shouldHighlight(),
mx_EventTile_notSent: this.props.eventSendStatus === 'not_sent',
mx_EventTile_highlight: this.props.tileShape === 'notif' ? false : this.shouldHighlight(),
mx_EventTile_selected: this.props.isSelectedEvent,
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_unverified: this.state.verified == false,
mx_EventTile_bad: msgtype === 'm.bad.encrypted',
mx_EventTile_verified: this.state.verified === true,
mx_EventTile_unverified: this.state.verified === false,
mx_EventTile_bad: isEncryptionFailure,
mx_EventTile_emote: msgtype === 'm.emote',
mx_EventTile_redacted: isRedacted,
});
const permalink = "https://matrix.to/#/" +
this.props.mxEvent.getRoomId() + "/" +
this.props.mxEvent.getId();
const permalink = makeEventPermalink(this.props.mxEvent.getRoomId(), this.props.mxEvent.getId());
var readAvatars = this.getReadAvatars();
const readAvatars = this.getReadAvatars();
var avatar, sender;
let avatar;
let sender;
let avatarSize;
let needsSenderProfile;
if (this.props.tileShape === "notif") {
avatarSize = 24;
needsSenderProfile = true;
} else if (tileHandler === 'messages.RoomCreate') {
avatarSize = 0;
needsSenderProfile = false;
} else if (isInfoMessage) {
// a small avatar, with no sender profile, for
// joins/parts/etc
@ -504,110 +565,192 @@ module.exports = withMatrixClient(React.createClass({
}
if (needsSenderProfile) {
let aux = null;
if (!this.props.tileShape) {
if (msgtype === 'm.image') aux = _t('sent an image');
else if (msgtype === 'm.video') aux = _t('sent a video');
else if (msgtype === 'm.file') aux = _t('uploaded a file');
sender = <SenderProfile onClick={ this.onSenderProfileClick } mxEvent={this.props.mxEvent} aux={aux} />;
}
else {
sender = <SenderProfile mxEvent={this.props.mxEvent} />;
let text = null;
if (!this.props.tileShape || this.props.tileShape === 'reply' || this.props.tileShape === 'reply_preview') {
if (msgtype === 'm.image') text = _td('%(senderName)s sent an image');
else if (msgtype === 'm.video') text = _td('%(senderName)s sent a video');
else if (msgtype === 'm.file') text = _td('%(senderName)s uploaded a file');
sender = <SenderProfile onClick={this.onSenderProfileClick}
mxEvent={this.props.mxEvent}
enableFlair={!text}
text={text} />;
} else {
sender = <SenderProfile mxEvent={this.props.mxEvent} enableFlair={true} />;
}
}
const editButton = (
<span className="mx_EventTile_editButton" title={ _t("Options") } onClick={this.onEditClicked} />
<span className="mx_EventTile_editButton" title={_t("Options")} onClick={this.onEditClicked} />
);
const timestamp = this.props.mxEvent.getTs() ?
<MessageTimestamp showTwelveHour={this.props.isTwelveHour} ts={this.props.mxEvent.getTs()} /> : null;
if (this.props.tileShape === "notif") {
const room = this.props.matrixClient.getRoom(this.props.mxEvent.getRoomId());
return (
<div className={classes}>
<div className="mx_EventTile_roomName">
<a href={ permalink } onClick={this.onPermalinkClicked}>
{ room ? room.name : '' }
</a>
</div>
<div className="mx_EventTile_senderDetails">
{ avatar }
<a href={ permalink } onClick={this.onPermalinkClicked}>
{ sender }
{ timestamp }
</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>
const keyRequestHelpText =
<div className="mx_EventTile_keyRequestInfo_tooltip_contents">
<p>
{ this.state.previouslyRequestedKeys ?
_t( 'Your key share request has been sent - please check your other devices ' +
'for key share requests.') :
_t( 'Key share requests are sent to your other devices automatically. If you ' +
'rejected or dismissed the key share request on your other devices, click ' +
'here to request the keys for this session again.')
}
</p>
<p>
{ _t( 'If your other devices do not have the key for this message you will not ' +
'be able to decrypt them.')
}
</p>
</div>;
const keyRequestInfoContent = this.state.previouslyRequestedKeys ?
_t('Key request sent.') :
_t(
'<requestLink>Re-request encryption keys</requestLink> from your other devices.',
{},
{'requestLink': (sub) => <a onClick={this.onRequestKeysClick}>{ sub }</a>},
);
}
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 }
onClick={this.onPermalinkClicked}
>
const ToolTipButton = sdk.getComponent('elements.ToolTipButton');
const keyRequestInfo = isEncryptionFailure ?
<div className="mx_EventTile_keyRequestInfo">
<span className="mx_EventTile_keyRequestInfo_text">
{ keyRequestInfoContent }
</span>
<ToolTipButton helpText={keyRequestHelpText} />
</div> : null;
switch (this.props.tileShape) {
case 'notif': {
const EmojiText = sdk.getComponent('elements.EmojiText');
const room = this.props.matrixClient.getRoom(this.props.mxEvent.getRoomId());
return (
<div className={classes}>
<div className="mx_EventTile_roomName">
<EmojiText element="a" href={permalink} onClick={this.onPermalinkClicked}>
{ room ? room.name : '' }
</EmojiText>
</div>
<div className="mx_EventTile_senderDetails">
{ avatar }
<a href={permalink} onClick={this.onPermalinkClicked}>
{ sender }
{ timestamp }
</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>
</a>
</div>
);
}
else {
return (
<div className={classes}>
<div className="mx_EventTile_msgOption">
{ readAvatars }
</div>
{ avatar }
{ sender }
<div className="mx_EventTile_line">
<a href={ permalink } onClick={this.onPermalinkClicked}>
{ timestamp }
);
}
case '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}
onClick={this.onPermalinkClicked}
>
<div className="mx_EventTile_senderDetails">
{ sender }
{ timestamp }
</div>
</a>
{ this._renderE2EPadlock() }
<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>
);
);
}
case 'reply':
case 'reply_preview': {
return (
<div className={classes}>
{ avatar }
{ sender }
<div className="mx_EventTile_reply">
<a href={permalink} onClick={this.onPermalinkClicked}>
{ timestamp }
</a>
{ this._renderE2EPadlock() }
{
this.props.tileShape === 'reply_preview'
&& ReplyThread.makeThread(this.props.mxEvent, this.props.onWidgetLoad, 'replyThread')
}
<EventTileType ref="tile"
mxEvent={this.props.mxEvent}
highlights={this.props.highlights}
highlightLink={this.props.highlightLink}
onWidgetLoad={this.props.onWidgetLoad}
showUrlPreview={false} />
</div>
</div>
);
}
default: {
return (
<div className={classes}>
<div className="mx_EventTile_msgOption">
{ readAvatars }
</div>
{ sender }
<div className="mx_EventTile_line">
<a href={permalink} onClick={this.onPermalinkClicked}>
{ timestamp }
</a>
{ this._renderE2EPadlock() }
{ ReplyThread.makeThread(this.props.mxEvent, this.props.onWidgetLoad, 'replyThread') }
<EventTileType ref="tile"
mxEvent={this.props.mxEvent}
highlights={this.props.highlights}
highlightLink={this.props.highlightLink}
showUrlPreview={this.props.showUrlPreview}
onWidgetLoad={this.props.onWidgetLoad} />
{ keyRequestInfo }
{ editButton }
</div>
{
// The avatar goes after the event tile as it's absolutly positioned to be over the
// event tile line, so needs to be later in the DOM so it appears on top (this avoids
// the need for further z-indexing chaos)
}
{ avatar }
</div>
);
}
}
},
}));
// XXX this'll eventually be dynamic based on the fields once we have extensible event types
const messageTypes = ['m.room.message', 'm.sticker'];
function isMessageEvent(ev) {
return (messageTypes.includes(ev.getType()));
}
module.exports.haveTileForEvent = function(e) {
// Only messages have a tile (black-rectangle) if redacted
if (e.isRedacted() && e.getType() !== 'm.room.message') return false;
if (eventTileTypes[e.getType()] == undefined) return false;
if (eventTileTypes[e.getType()] == 'messages.TextualEvent') {
if (e.isRedacted() && !isMessageEvent(e)) return false;
const handler = getHandlerTile(e);
if (handler === undefined) return false;
if (handler === 'messages.TextualEvent') {
return TextForEvent.textForEvent(e) !== '';
} else if (handler === 'messages.RoomCreate') {
return Boolean(e.getContent()['predecessor']);
} else {
return true;
}
@ -621,6 +764,14 @@ function E2ePadlockUndecryptable(props) {
);
}
function E2ePadlockEncrypting(props) {
return <E2ePadlock alt={_t("Encrypting")} src="img/e2e-encrypting.svg" width="10" height="12" {...props} />;
}
function E2ePadlockNotSent(props) {
return <E2ePadlock alt={_t("Encrypted, not sent")} src="img/e2e-not_sent.svg" width="10" height="12" {...props} />;
}
function E2ePadlockVerified(props) {
return (
<E2ePadlock alt={_t("Encrypted by a verified device")}
@ -646,5 +797,11 @@ function E2ePadlockUnencrypted(props) {
}
function E2ePadlock(props) {
return <img className="mx_EventTile_e2eIcon" {...props} />;
if (SettingsStore.getValue("alwaysShowEncryptionIcons")) {
return <img className="mx_EventTile_e2eIcon" {...props} />;
} else {
return <img className="mx_EventTile_e2eIcon mx_EventTile_e2eIcon_hidden" {...props} />;
}
}
module.exports.getHandlerTile = getHandlerTile;