2015-11-27 18:02:32 +03:00
|
|
|
/*
|
2016-01-07 07:06:39 +03:00
|
|
|
Copyright 2015, 2016 OpenMarket Ltd
|
2015-11-27 18:02:32 +03:00
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
var React = require('react');
|
|
|
|
var classNames = require("classnames");
|
2016-09-12 20:50:46 +03:00
|
|
|
var Modal = require('../../../Modal');
|
2015-11-27 18:02:32 +03:00
|
|
|
|
|
|
|
var sdk = require('../../../index');
|
|
|
|
var TextForEvent = require('../../../TextForEvent');
|
2016-11-14 19:00:24 +03:00
|
|
|
import WithMatrixClient from '../../../wrappers/WithMatrixClient';
|
2015-11-27 18:02:32 +03:00
|
|
|
|
2016-07-27 16:49:10 +03:00
|
|
|
var ContextualMenu = require('../../structures/ContextualMenu');
|
2016-03-17 00:15:38 +03:00
|
|
|
var dispatcher = require("../../../dispatcher");
|
2015-11-27 18:02:32 +03:00
|
|
|
|
2016-04-19 21:38:54 +03:00
|
|
|
var ObjectUtils = require('../../../ObjectUtils');
|
|
|
|
|
2015-11-27 18:02:32 +03:00
|
|
|
var bounce = false;
|
|
|
|
try {
|
|
|
|
if (global.localStorage) {
|
|
|
|
bounce = global.localStorage.getItem('avatar_bounce') == 'true';
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
}
|
|
|
|
|
|
|
|
var eventTileTypes = {
|
2015-11-30 18:19:43 +03:00
|
|
|
'm.room.message': 'messages.MessageEvent',
|
2015-11-27 18:02:32 +03:00
|
|
|
'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.topic' : 'messages.TextualEvent',
|
2016-03-16 02:47:40 +03:00
|
|
|
'm.room.third_party_invite' : 'messages.TextualEvent',
|
|
|
|
'm.room.history_visibility' : 'messages.TextualEvent',
|
2016-09-15 19:01:02 +03:00
|
|
|
'm.room.encryption' : 'messages.TextualEvent',
|
2015-11-27 18:02:32 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
var MAX_READ_AVATARS = 5;
|
|
|
|
|
2015-11-30 18:19:43 +03:00
|
|
|
// Our component structure for EventTiles on the timeline is:
|
|
|
|
//
|
|
|
|
// .-EventTile------------------------------------------------.
|
|
|
|
// | MemberAvatar (SenderProfile) TimeStamp |
|
|
|
|
// | .-{Message,Textual}Event---------------. Read Avatars |
|
|
|
|
// | | .-MFooBody-------------------. | |
|
|
|
|
// | | | (only if MessageEvent) | | |
|
|
|
|
// | | '----------------------------' | |
|
|
|
|
// | '--------------------------------------' |
|
|
|
|
// '----------------------------------------------------------'
|
|
|
|
|
2016-11-14 19:00:24 +03:00
|
|
|
module.exports = WithMatrixClient(React.createClass({
|
2016-08-25 18:55:09 +03:00
|
|
|
displayName: 'EventTile',
|
2015-11-27 18:02:32 +03:00
|
|
|
|
2015-12-24 03:12:37 +03:00
|
|
|
propTypes: {
|
2016-11-14 19:00:24 +03:00
|
|
|
/* MatrixClient instance for sender verification etc */
|
|
|
|
matrixClient: React.PropTypes.object.isRequired,
|
|
|
|
|
2015-12-24 03:12:37 +03:00
|
|
|
/* the MatrixEvent to show */
|
|
|
|
mxEvent: React.PropTypes.object.isRequired,
|
|
|
|
|
|
|
|
/* true if this is a continuation of the previous event (which has the
|
|
|
|
* effect of not showing another avatar/displayname
|
|
|
|
*/
|
|
|
|
continuation: React.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,
|
|
|
|
|
|
|
|
/* true if this is search context (which has the effect of greying out
|
|
|
|
* the text
|
|
|
|
*/
|
|
|
|
contextual: React.PropTypes.bool,
|
|
|
|
|
2016-03-05 05:05:29 +03:00
|
|
|
/* a list of words to highlight, ordered by longest first */
|
2015-12-24 03:12:37 +03:00
|
|
|
highlights: React.PropTypes.array,
|
|
|
|
|
2016-02-17 22:50:04 +03:00
|
|
|
/* link URL for the highlights */
|
|
|
|
highlightLink: React.PropTypes.string,
|
2016-02-01 19:31:12 +03:00
|
|
|
|
2016-07-18 03:35:42 +03:00
|
|
|
/* should show URL previews for this event */
|
|
|
|
showUrlPreview: React.PropTypes.bool,
|
|
|
|
|
2016-03-05 05:05:29 +03:00
|
|
|
/* is this the focused event */
|
2016-02-03 11:03:10 +03:00
|
|
|
isSelectedEvent: React.PropTypes.bool,
|
2016-02-22 20:19:04 +03:00
|
|
|
|
2016-04-02 02:36:19 +03:00
|
|
|
/* callback called when dynamic content in events are loaded */
|
|
|
|
onWidgetLoad: React.PropTypes.func,
|
2016-04-19 21:38:54 +03:00
|
|
|
|
2016-12-09 14:24:10 +03:00
|
|
|
/* a list of read-receipts we should show. Each object has a 'roomMember' and 'ts'. */
|
2016-04-19 21:38:54 +03:00
|
|
|
readReceipts: React.PropTypes.arrayOf(React.PropTypes.object),
|
|
|
|
|
2016-04-21 01:03:05 +03:00
|
|
|
/* 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,
|
|
|
|
|
2016-04-22 19:03:15 +03:00
|
|
|
/* 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,
|
|
|
|
|
2016-04-19 21:38:54 +03:00
|
|
|
/* the status of this event - ie, mxEvent.status. Denormalised to here so
|
|
|
|
* that we can tell when it changes. */
|
|
|
|
eventSendStatus: React.PropTypes.string,
|
2016-09-11 04:14:27 +03:00
|
|
|
|
|
|
|
/* 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,
|
2015-12-24 03:12:37 +03:00
|
|
|
},
|
|
|
|
|
2015-11-27 18:02:32 +03:00
|
|
|
getInitialState: function() {
|
2016-06-08 19:01:13 +03:00
|
|
|
return {menu: false, allReadAvatars: false, verified: null};
|
2015-11-27 18:02:32 +03:00
|
|
|
},
|
|
|
|
|
2016-04-21 01:03:05 +03:00
|
|
|
componentWillMount: function() {
|
|
|
|
// don't do RR animations until we are mounted
|
|
|
|
this._suppressReadReceiptAnimation = true;
|
2016-06-08 19:01:13 +03:00
|
|
|
this._verifyEvent(this.props.mxEvent);
|
2016-04-21 01:03:05 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
componentDidMount: function() {
|
|
|
|
this._suppressReadReceiptAnimation = false;
|
2016-11-14 19:00:24 +03:00
|
|
|
this.props.matrixClient.on("deviceVerificationChanged",
|
2016-06-23 19:27:23 +03:00
|
|
|
this.onDeviceVerificationChanged);
|
2016-11-15 14:12:52 +03:00
|
|
|
this.props.mxEvent.on("Event.decrypted", this._onDecrypted);
|
2016-04-21 01:03:05 +03:00
|
|
|
},
|
|
|
|
|
2016-06-08 19:01:13 +03:00
|
|
|
componentWillReceiveProps: function (nextProps) {
|
|
|
|
if (nextProps.mxEvent !== this.props.mxEvent) {
|
|
|
|
this._verifyEvent(nextProps.mxEvent);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2016-04-19 21:38:54 +03:00
|
|
|
shouldComponentUpdate: function (nextProps, nextState) {
|
|
|
|
if (!ObjectUtils.shallowEqual(this.state, nextState)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!this._propsEqual(this.props, nextProps)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
2016-06-08 20:35:43 +03:00
|
|
|
componentWillUnmount: function() {
|
2016-11-14 19:00:24 +03:00
|
|
|
var client = this.props.matrixClient;
|
|
|
|
client.removeListener("deviceVerificationChanged",
|
|
|
|
this.onDeviceVerificationChanged);
|
2016-11-15 14:12:52 +03:00
|
|
|
this.props.mxEvent.removeListener("Event.decrypted", this._onDecrypted);
|
|
|
|
},
|
|
|
|
|
|
|
|
/** called when the event is decrypted after we show it.
|
|
|
|
*/
|
|
|
|
_onDecrypted: function() {
|
|
|
|
// we need to re-verify the sending device.
|
|
|
|
this._verifyEvent(this.props.mxEvent);
|
|
|
|
this.forceUpdate();
|
2016-06-08 20:35:43 +03:00
|
|
|
},
|
|
|
|
|
2016-06-23 19:27:23 +03:00
|
|
|
onDeviceVerificationChanged: function(userId, device) {
|
2016-06-08 23:25:42 +03:00
|
|
|
if (userId == this.props.mxEvent.getSender()) {
|
|
|
|
this._verifyEvent(this.props.mxEvent);
|
2016-06-08 20:35:43 +03:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2016-06-08 19:01:13 +03:00
|
|
|
_verifyEvent: function(mxEvent) {
|
|
|
|
var verified = null;
|
|
|
|
|
|
|
|
if (mxEvent.isEncrypted()) {
|
2016-11-14 19:00:24 +03:00
|
|
|
verified = this.props.matrixClient.isEventSenderVerified(mxEvent);
|
2016-06-08 19:01:13 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
verified: verified
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2016-04-19 21:38:54 +03:00
|
|
|
_propsEqual: function(objA, objB) {
|
|
|
|
var keysA = Object.keys(objA);
|
|
|
|
var keysB = Object.keys(objB);
|
|
|
|
|
|
|
|
if (keysA.length !== keysB.length) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (var i = 0; i < keysA.length; i++) {
|
|
|
|
var 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];
|
2016-04-19 23:10:23 +03:00
|
|
|
if (rA === rB) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!rA || !rB) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-04-19 21:38:54 +03:00
|
|
|
if (rA.length !== rB.length) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
for (var j = 0; j < rA.length; j++) {
|
2016-12-09 14:24:10 +03:00
|
|
|
if (rA[j].roomMember.userId !== rB[j].roomMember.userId) {
|
2016-04-19 21:38:54 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (objA[key] !== objB[key]) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
2015-11-27 18:02:32 +03:00
|
|
|
shouldHighlight: function() {
|
2016-11-14 19:00:24 +03:00
|
|
|
var actions = this.props.matrixClient.getPushActionsForEvent(this.props.mxEvent);
|
2015-11-27 18:02:32 +03:00
|
|
|
if (!actions || !actions.tweaks) { return false; }
|
2016-02-19 04:56:03 +03:00
|
|
|
|
|
|
|
// don't show self-highlights from another of our clients
|
2016-11-14 19:00:24 +03:00
|
|
|
if (this.props.mxEvent.getSender() === this.props.matrixClient.credentials.userId)
|
2016-02-19 04:56:03 +03:00
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
2016-04-08 23:42:29 +03:00
|
|
|
|
2015-11-27 18:02:32 +03:00
|
|
|
return actions.tweaks.highlight;
|
|
|
|
},
|
|
|
|
|
|
|
|
onEditClicked: function(e) {
|
2016-07-27 12:41:24 +03:00
|
|
|
var MessageContextMenu = sdk.getComponent('context_menus.MessageContextMenu');
|
2015-11-27 18:02:32 +03:00
|
|
|
var buttonRect = e.target.getBoundingClientRect()
|
2016-07-27 11:51:50 +03:00
|
|
|
|
|
|
|
// The window X and Y offsets are to adjust position when zoomed in to page
|
|
|
|
var x = buttonRect.right + window.pageXOffset;
|
2017-01-17 17:11:15 +03:00
|
|
|
var y = (buttonRect.top + (buttonRect.height / 2) + window.pageYOffset) - 19;
|
2015-11-27 18:02:32 +03:00
|
|
|
var self = this;
|
|
|
|
ContextualMenu.createMenu(MessageContextMenu, {
|
2016-07-27 18:09:07 +03:00
|
|
|
chevronOffset: 10,
|
2015-11-27 18:02:32 +03:00
|
|
|
mxEvent: this.props.mxEvent,
|
|
|
|
left: x,
|
|
|
|
top: y,
|
2016-04-14 17:50:00 +03:00
|
|
|
eventTileOps: this.refs.tile && this.refs.tile.getEventTileOps ? this.refs.tile.getEventTileOps() : undefined,
|
2015-11-27 18:02:32 +03:00
|
|
|
onFinished: function() {
|
|
|
|
self.setState({menu: false});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
this.setState({menu: true});
|
|
|
|
},
|
|
|
|
|
|
|
|
toggleAllReadAvatars: function() {
|
|
|
|
this.setState({
|
|
|
|
allReadAvatars: !this.state.allReadAvatars
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
getReadAvatars: function() {
|
2016-04-21 01:03:05 +03:00
|
|
|
var ReadReceiptMarker = sdk.getComponent('rooms.ReadReceiptMarker');
|
2015-11-27 18:02:32 +03:00
|
|
|
var avatars = [];
|
|
|
|
var left = 0;
|
|
|
|
|
2016-12-09 14:43:23 +03:00
|
|
|
// It's possible that the receipt was sent several days AFTER the event.
|
|
|
|
// If it is, we want to display the complete date along with the HH:MM:SS,
|
|
|
|
// rather than just HH:MM:SS.
|
|
|
|
let dayAfterEvent = new Date(this.props.mxEvent.getTs());
|
|
|
|
dayAfterEvent.setDate(dayAfterEvent.getDate() + 1)
|
|
|
|
dayAfterEvent.setHours(0);
|
|
|
|
dayAfterEvent.setMinutes(0);
|
|
|
|
dayAfterEvent.setSeconds(0);
|
|
|
|
let dayAfterEventTime = dayAfterEvent.getTime();
|
|
|
|
|
2016-04-19 21:38:54 +03:00
|
|
|
var receipts = this.props.readReceipts || [];
|
2015-11-27 18:02:32 +03:00
|
|
|
for (var i = 0; i < receipts.length; ++i) {
|
2016-12-09 14:24:10 +03:00
|
|
|
var receipt = receipts[i];
|
2015-11-27 18:02:32 +03:00
|
|
|
|
2016-04-21 01:03:05 +03:00
|
|
|
var hidden = true;
|
|
|
|
if ((i < MAX_READ_AVATARS) || this.state.allReadAvatars) {
|
|
|
|
hidden = false;
|
2015-11-27 18:02:32 +03:00
|
|
|
}
|
|
|
|
|
2016-12-09 14:24:10 +03:00
|
|
|
var userId = receipt.roomMember.userId;
|
2016-04-21 01:03:05 +03:00
|
|
|
var readReceiptInfo;
|
2015-11-27 18:02:32 +03:00
|
|
|
|
2016-04-21 01:03:05 +03:00
|
|
|
if (this.props.readReceiptMap) {
|
|
|
|
readReceiptInfo = this.props.readReceiptMap[userId];
|
|
|
|
if (!readReceiptInfo) {
|
|
|
|
readReceiptInfo = {};
|
|
|
|
this.props.readReceiptMap[userId] = readReceiptInfo;
|
2015-11-27 18:02:32 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//console.log("i = " + i + ", MAX_READ_AVATARS = " + MAX_READ_AVATARS + ", allReadAvatars = " + this.state.allReadAvatars + " visibility = " + style.visibility);
|
|
|
|
// add to the start so the most recent is on the end (ie. ends up rightmost)
|
|
|
|
avatars.unshift(
|
2016-12-09 14:24:10 +03:00
|
|
|
<ReadReceiptMarker key={userId} member={receipt.roomMember}
|
2016-04-21 01:03:05 +03:00
|
|
|
leftOffset={left} hidden={hidden}
|
|
|
|
readReceiptInfo={readReceiptInfo}
|
2016-04-22 19:03:15 +03:00
|
|
|
checkUnmounting={this.props.checkUnmounting}
|
2016-04-21 01:03:05 +03:00
|
|
|
suppressAnimation={this._suppressReadReceiptAnimation}
|
2015-11-27 18:02:32 +03:00
|
|
|
onClick={this.toggleAllReadAvatars}
|
2016-12-09 14:24:10 +03:00
|
|
|
timestamp={receipt.ts}
|
2016-12-09 14:43:23 +03:00
|
|
|
showFullTimestamp={receipt.ts >= dayAfterEventTime}
|
2015-11-27 18:02:32 +03:00
|
|
|
/>
|
|
|
|
);
|
2016-04-21 01:03:05 +03:00
|
|
|
|
2015-11-27 18:02:32 +03:00
|
|
|
// TODO: we keep the extra read avatars in the dom to make animation simpler
|
|
|
|
// we could optimise this to reduce the dom size.
|
2016-04-21 01:03:05 +03:00
|
|
|
if (!hidden) {
|
2015-11-27 18:02:32 +03:00
|
|
|
left -= 15;
|
|
|
|
}
|
|
|
|
}
|
2016-04-21 01:03:05 +03:00
|
|
|
var remText;
|
2015-11-27 18:02:32 +03:00
|
|
|
if (!this.state.allReadAvatars) {
|
|
|
|
var remainder = receipts.length - MAX_READ_AVATARS;
|
|
|
|
if (remainder > 0) {
|
|
|
|
remText = <span className="mx_EventTile_readAvatarRemainder"
|
|
|
|
onClick={this.toggleAllReadAvatars}
|
|
|
|
style={{ left: left }}>{ remainder }+
|
|
|
|
</span>;
|
|
|
|
left -= 15;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-21 01:03:05 +03:00
|
|
|
return <span className="mx_EventTile_readAvatars">
|
2015-11-27 18:02:32 +03:00
|
|
|
{ remText }
|
2016-04-21 01:03:05 +03:00
|
|
|
{ avatars }
|
2015-11-27 18:02:32 +03:00
|
|
|
</span>;
|
|
|
|
},
|
|
|
|
|
2016-03-17 18:35:23 +03:00
|
|
|
onSenderProfileClick: function(event) {
|
2016-03-18 19:33:22 +03:00
|
|
|
var mxEvent = this.props.mxEvent;
|
2016-03-17 18:35:23 +03:00
|
|
|
dispatcher.dispatch({
|
|
|
|
action: 'insert_displayname',
|
2016-12-03 03:20:50 +03:00
|
|
|
displayname: (mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender()).replace(' (IRC)', ''),
|
2016-03-17 14:56:46 +03:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2016-09-12 20:50:46 +03:00
|
|
|
onCryptoClicked: function(e) {
|
|
|
|
var event = this.props.mxEvent;
|
|
|
|
|
2017-01-16 20:01:26 +03:00
|
|
|
Modal.createDialogAsync((cb) => {
|
|
|
|
require(['../../../async-components/views/dialogs/EncryptedEventDialog'], cb)
|
|
|
|
}, {
|
2016-09-13 01:42:24 +03:00
|
|
|
event: event,
|
2016-09-12 20:50:46 +03:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2015-11-27 18:02:32 +03:00
|
|
|
render: function() {
|
|
|
|
var MessageTimestamp = sdk.getComponent('messages.MessageTimestamp');
|
2015-12-01 14:19:25 +03:00
|
|
|
var SenderProfile = sdk.getComponent('messages.SenderProfile');
|
2015-11-27 18:02:32 +03:00
|
|
|
var MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
|
|
|
|
|
2016-07-20 14:03:13 +03:00
|
|
|
//console.log("EventTile showUrlPreview for %s is %s", this.props.mxEvent.getId(), this.props.showUrlPreview);
|
|
|
|
|
2015-11-27 18:02:32 +03:00
|
|
|
var content = this.props.mxEvent.getContent();
|
|
|
|
var msgtype = content.msgtype;
|
2016-08-18 23:53:37 +03:00
|
|
|
var eventType = this.props.mxEvent.getType();
|
2016-08-19 00:19:23 +03:00
|
|
|
|
|
|
|
// Info messages are basically information about commands processed on a
|
|
|
|
// room, or emote messages
|
2016-09-12 01:01:20 +03:00
|
|
|
var isInfoMessage = (eventType !== 'm.room.message');
|
2015-11-27 18:02:32 +03:00
|
|
|
|
2016-08-25 18:55:09 +03:00
|
|
|
var EventTileType = sdk.getComponent(eventTileTypes[eventType]);
|
2015-11-27 18:02:32 +03:00
|
|
|
// 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");
|
|
|
|
}
|
|
|
|
|
2016-11-14 19:00:24 +03:00
|
|
|
var e2eEnabled = this.props.matrixClient.isRoomEncrypted(this.props.mxEvent.getRoomId());
|
2016-09-12 03:37:51 +03:00
|
|
|
var isSending = (['sending', 'queued', 'encrypting'].indexOf(this.props.eventSendStatus) !== -1);
|
|
|
|
|
2015-11-27 18:02:32 +03:00
|
|
|
var classes = classNames({
|
|
|
|
mx_EventTile: true,
|
2016-08-18 23:53:37 +03:00
|
|
|
mx_EventTile_info: isInfoMessage,
|
2016-09-12 03:37:51 +03:00
|
|
|
mx_EventTile_encrypting: this.props.eventSendStatus == 'encrypting',
|
|
|
|
mx_EventTile_sending: isSending,
|
2016-04-19 21:38:54 +03:00
|
|
|
mx_EventTile_notSent: this.props.eventSendStatus == 'not_sent',
|
2016-09-12 03:37:51 +03:00
|
|
|
mx_EventTile_highlight: this.props.tileShape == 'notif' ? false : this.shouldHighlight(),
|
2016-02-03 11:03:10 +03:00
|
|
|
mx_EventTile_selected: this.props.isSelectedEvent,
|
2016-09-11 04:14:27 +03:00
|
|
|
mx_EventTile_continuation: this.props.tileShape ? '' : this.props.continuation,
|
2015-11-27 18:02:32 +03:00
|
|
|
mx_EventTile_last: this.props.last,
|
|
|
|
mx_EventTile_contextual: this.props.contextual,
|
|
|
|
menu: this.state.menu,
|
2016-09-16 00:49:00 +03:00
|
|
|
mx_EventTile_verified: this.state.verified == true,
|
2016-06-08 19:01:13 +03:00
|
|
|
mx_EventTile_unverified: this.state.verified == false,
|
2016-09-12 03:37:51 +03:00
|
|
|
mx_EventTile_bad: this.props.mxEvent.getContent().msgtype === 'm.bad.encrypted',
|
2015-11-27 18:02:32 +03:00
|
|
|
});
|
2016-09-11 04:14:27 +03:00
|
|
|
var permalink = "#/room/" + this.props.mxEvent.getRoomId() +"/"+ this.props.mxEvent.getId();
|
2015-11-27 18:02:32 +03:00
|
|
|
|
|
|
|
var readAvatars = this.getReadAvatars();
|
|
|
|
|
|
|
|
var avatar, sender;
|
2016-08-25 18:55:09 +03:00
|
|
|
let avatarSize;
|
|
|
|
let needsSenderProfile;
|
|
|
|
|
2016-09-11 04:14:27 +03:00
|
|
|
if (this.props.tileShape === "notif") {
|
|
|
|
avatarSize = 24;
|
|
|
|
needsSenderProfile = true;
|
|
|
|
} else if (isInfoMessage) {
|
2016-09-12 01:01:20 +03:00
|
|
|
// a small avatar, with no sender profile, for
|
2016-08-25 18:55:09 +03:00
|
|
|
// joins/parts/etc
|
|
|
|
avatarSize = 14;
|
|
|
|
needsSenderProfile = false;
|
2016-09-16 04:40:09 +03:00
|
|
|
} else if (this.props.continuation && this.props.tileShape !== "file_grid") {
|
2016-08-25 18:55:09 +03:00
|
|
|
// no avatar or sender profile for continuation messages
|
|
|
|
avatarSize = 0;
|
|
|
|
needsSenderProfile = false;
|
|
|
|
} else {
|
|
|
|
avatarSize = 30;
|
2016-09-12 01:01:20 +03:00
|
|
|
needsSenderProfile = true;
|
2016-08-25 18:55:09 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (this.props.mxEvent.sender && avatarSize) {
|
2016-08-23 17:58:27 +03:00
|
|
|
avatar = (
|
2016-03-17 14:56:46 +03:00
|
|
|
<div className="mx_EventTile_avatar">
|
2016-08-25 18:55:09 +03:00
|
|
|
<MemberAvatar member={this.props.mxEvent.sender}
|
|
|
|
width={avatarSize} height={avatarSize}
|
2016-11-10 18:18:59 +03:00
|
|
|
viewUserOnClick={true}
|
2016-08-25 18:55:09 +03:00
|
|
|
/>
|
2015-11-27 18:02:32 +03:00
|
|
|
</div>
|
2016-08-25 18:55:09 +03:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (needsSenderProfile) {
|
|
|
|
let aux = null;
|
2016-09-11 04:14:27 +03:00
|
|
|
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} />;
|
|
|
|
}
|
2015-11-27 18:02:32 +03:00
|
|
|
}
|
2016-08-16 13:59:26 +03:00
|
|
|
|
|
|
|
var editButton = (
|
2017-01-17 17:11:15 +03:00
|
|
|
<span className="mx_EventTile_editButton" title="Options" onClick={this.onEditClicked} />
|
2016-08-16 13:59:26 +03:00
|
|
|
);
|
|
|
|
|
2016-09-12 03:37:51 +03:00
|
|
|
var e2e;
|
2016-09-15 21:25:53 +03:00
|
|
|
// cosmetic padlocks:
|
|
|
|
if ((e2eEnabled && this.props.eventSendStatus) || this.props.mxEvent.getType() === 'm.room.encryption') {
|
|
|
|
e2e = <img style={{ cursor: 'initial', marginLeft: '-1px' }} className="mx_EventTile_e2eIcon" src="img/e2e-verified.svg" width="10" height="12" />;
|
|
|
|
}
|
|
|
|
// real padlocks
|
|
|
|
else if (this.props.mxEvent.isEncrypted() || (e2eEnabled && this.props.eventSendStatus)) {
|
2016-09-12 03:37:51 +03:00
|
|
|
if (this.props.mxEvent.getContent().msgtype === 'm.bad.encrypted') {
|
2016-09-12 20:50:46 +03:00
|
|
|
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" src="img/e2e-blocked.svg" width="12" height="12" style={{ marginLeft: "-1px" }} />;
|
2016-09-12 03:37:51 +03:00
|
|
|
}
|
2016-09-14 03:25:43 +03:00
|
|
|
else if (this.state.verified == true || (e2eEnabled && this.props.eventSendStatus)) {
|
2016-09-15 04:44:55 +03:00
|
|
|
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" src="img/e2e-verified.svg" width="10" height="12"/>;
|
2016-09-12 03:37:51 +03:00
|
|
|
}
|
|
|
|
else {
|
2016-09-15 04:44:55 +03:00
|
|
|
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" src="img/e2e-warning.svg" width="15" height="12" style={{ marginLeft: "-2px" }}/>;
|
2016-09-12 03:37:51 +03:00
|
|
|
}
|
|
|
|
}
|
2016-09-14 03:25:43 +03:00
|
|
|
else if (e2eEnabled) {
|
|
|
|
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" src="img/e2e-unencrypted.svg" width="12" height="12"/>;
|
|
|
|
}
|
2016-09-12 03:37:51 +03:00
|
|
|
|
2016-09-11 04:14:27 +03:00
|
|
|
if (this.props.tileShape === "notif") {
|
2016-11-14 19:00:24 +03:00
|
|
|
var room = this.props.matrixClient.getRoom(this.props.mxEvent.getRoomId());
|
2016-09-11 04:14:27 +03:00
|
|
|
|
|
|
|
return (
|
|
|
|
<div className={classes}>
|
|
|
|
<div className="mx_EventTile_roomName">
|
|
|
|
<a href={ permalink }>
|
2016-09-12 19:29:10 +03:00
|
|
|
{ room ? room.name : '' }
|
2016-09-11 04:14:27 +03:00
|
|
|
</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>
|
2015-11-27 18:02:32 +03:00
|
|
|
</div>
|
2016-09-11 04:14:27 +03:00
|
|
|
);
|
|
|
|
}
|
|
|
|
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>
|
2015-11-27 18:02:32 +03:00
|
|
|
</div>
|
2016-09-11 04:14:27 +03:00
|
|
|
);
|
|
|
|
}
|
|
|
|
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>
|
2016-09-12 03:37:51 +03:00
|
|
|
{ e2e }
|
2016-09-11 04:14:27 +03:00
|
|
|
<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>
|
|
|
|
);
|
|
|
|
}
|
2015-11-27 18:02:32 +03:00
|
|
|
},
|
2016-11-14 19:00:24 +03:00
|
|
|
}));
|
|
|
|
|
|
|
|
module.exports.haveTileForEvent = function(e) {
|
|
|
|
if (e.isRedacted()) return false;
|
|
|
|
if (eventTileTypes[e.getType()] == undefined) return false;
|
|
|
|
if (eventTileTypes[e.getType()] == 'messages.TextualEvent') {
|
|
|
|
return TextForEvent.textForEvent(e) !== '';
|
|
|
|
} else {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
};
|