2015-11-27 18:02:32 +03:00
|
|
|
/*
|
2016-01-07 07:06:39 +03:00
|
|
|
Copyright 2015, 2016 OpenMarket Ltd
|
2017-12-10 15:50:41 +03:00
|
|
|
Copyright 2017 New Vector 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';
|
|
|
|
|
2018-04-13 14:28:58 +03:00
|
|
|
import ReplyThread from "../elements/ReplyThread";
|
2015-11-27 18:02:32 +03:00
|
|
|
|
2017-10-11 19:56:17 +03:00
|
|
|
const React = require('react');
|
2017-12-26 04:03:18 +03:00
|
|
|
import PropTypes from 'prop-types';
|
2017-10-11 19:56:17 +03:00
|
|
|
const classNames = require("classnames");
|
2017-10-15 22:08:41 +03:00
|
|
|
import { _t, _td } from '../../../languageHandler';
|
2017-10-11 19:56:17 +03:00
|
|
|
const Modal = require('../../../Modal');
|
2015-11-27 18:02:32 +03:00
|
|
|
|
2017-10-11 19:56:17 +03:00
|
|
|
const sdk = require('../../../index');
|
|
|
|
const TextForEvent = require('../../../TextForEvent');
|
2017-07-07 13:34:20 +03:00
|
|
|
import withMatrixClient from '../../../wrappers/withMatrixClient';
|
2015-11-27 18:02:32 +03:00
|
|
|
|
2017-03-08 19:55:44 +03:00
|
|
|
import dis from '../../../dispatcher';
|
2018-04-30 19:22:16 +03:00
|
|
|
import SettingsStore from "../../../settings/SettingsStore";
|
2018-06-13 15:51:04 +03:00
|
|
|
import {EventStatus} from 'matrix-js-sdk';
|
2015-11-27 18:02:32 +03:00
|
|
|
|
2017-10-11 19:56:17 +03:00
|
|
|
const ObjectUtils = require('../../../ObjectUtils');
|
2016-04-19 21:38:54 +03:00
|
|
|
|
2017-10-11 19:56:17 +03:00
|
|
|
const eventTileTypes = {
|
2015-11-30 18:19:43 +03:00
|
|
|
'm.room.message': 'messages.MessageEvent',
|
2018-03-12 16:56:02 +03:00
|
|
|
'm.sticker': 'messages.MessageEvent',
|
2017-10-11 19:56:17 +03:00
|
|
|
'm.call.invite': 'messages.TextualEvent',
|
|
|
|
'm.call.answer': 'messages.TextualEvent',
|
|
|
|
'm.call.hangup': 'messages.TextualEvent',
|
2017-11-15 18:56:54 +03:00
|
|
|
};
|
2017-08-16 19:46:20 +03:00
|
|
|
|
2017-11-15 18:56:54 +03:00
|
|
|
const stateEventTileTypes = {
|
2018-09-17 20:20:12 +03:00
|
|
|
'm.room.aliases': 'messages.TextualEvent',
|
|
|
|
// 'm.room.aliases': 'messages.RoomAliasesEvent', // too complex
|
2018-09-20 03:07:01 +03:00
|
|
|
'm.room.canonical_alias': 'messages.TextualEvent',
|
2018-08-20 18:20:59 +03:00
|
|
|
'm.room.create': 'messages.RoomCreate',
|
2017-11-15 18:56:54 +03:00
|
|
|
'm.room.member': 'messages.TextualEvent',
|
2017-10-11 19:56:17 +03:00
|
|
|
'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',
|
2017-11-15 18:56:54 +03:00
|
|
|
'm.room.topic': 'messages.TextualEvent',
|
2017-10-11 19:56:17 +03:00
|
|
|
'm.room.power_levels': 'messages.TextualEvent',
|
2017-11-15 18:56:54 +03:00
|
|
|
'm.room.pinned_events': 'messages.TextualEvent',
|
2018-07-09 22:11:17 +03:00
|
|
|
'm.room.server_acl': 'messages.TextualEvent',
|
2017-08-16 19:46:20 +03:00
|
|
|
'im.vector.modular.widgets': 'messages.TextualEvent',
|
2019-01-11 01:15:45 +03:00
|
|
|
'm.room.tombstone': 'messages.TextualEvent',
|
2019-02-07 22:14:58 +03:00
|
|
|
'm.room.join_rules': 'messages.TextualEvent',
|
|
|
|
'm.room.guest_access': 'messages.TextualEvent',
|
2019-02-22 03:03:15 +03:00
|
|
|
'm.room.related_groups': 'messages.TextualEvent',
|
2015-11-27 18:02:32 +03:00
|
|
|
};
|
|
|
|
|
2017-11-15 18:56:54 +03:00
|
|
|
function getHandlerTile(ev) {
|
|
|
|
const type = ev.getType();
|
|
|
|
return ev.isState() ? stateEventTileTypes[type] : eventTileTypes[type];
|
|
|
|
}
|
|
|
|
|
2017-10-11 19:56:17 +03:00
|
|
|
const MAX_READ_AVATARS = 5;
|
2015-11-27 18:02:32 +03:00
|
|
|
|
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) | | |
|
|
|
|
// | | '----------------------------' | |
|
|
|
|
// | '--------------------------------------' |
|
|
|
|
// '----------------------------------------------------------'
|
|
|
|
|
2017-07-07 13:34:20 +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 */
|
2017-12-26 04:03:18 +03:00
|
|
|
matrixClient: PropTypes.object.isRequired,
|
2016-11-14 19:00:24 +03:00
|
|
|
|
2015-12-24 03:12:37 +03:00
|
|
|
/* the MatrixEvent to show */
|
2017-12-26 04:03:18 +03:00
|
|
|
mxEvent: PropTypes.object.isRequired,
|
2015-12-24 03:12:37 +03:00
|
|
|
|
2017-03-06 17:20:24 +03:00
|
|
|
/* 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.
|
|
|
|
*/
|
2017-12-26 04:03:18 +03:00
|
|
|
isRedacted: PropTypes.bool,
|
2017-03-06 17:20:24 +03:00
|
|
|
|
2015-12-24 03:12:37 +03:00
|
|
|
/* true if this is a continuation of the previous event (which has the
|
|
|
|
* effect of not showing another avatar/displayname
|
|
|
|
*/
|
2017-12-26 04:03:18 +03:00
|
|
|
continuation: PropTypes.bool,
|
2015-12-24 03:12:37 +03:00
|
|
|
|
|
|
|
/* true if this is the last event in the timeline (which has the effect
|
|
|
|
* of always showing the timestamp)
|
|
|
|
*/
|
2017-12-26 04:03:18 +03:00
|
|
|
last: PropTypes.bool,
|
2015-12-24 03:12:37 +03:00
|
|
|
|
|
|
|
/* true if this is search context (which has the effect of greying out
|
|
|
|
* the text
|
|
|
|
*/
|
2017-12-26 04:03:18 +03:00
|
|
|
contextual: PropTypes.bool,
|
2015-12-24 03:12:37 +03:00
|
|
|
|
2016-03-05 05:05:29 +03:00
|
|
|
/* a list of words to highlight, ordered by longest first */
|
2017-12-26 04:03:18 +03:00
|
|
|
highlights: PropTypes.array,
|
2015-12-24 03:12:37 +03:00
|
|
|
|
2016-02-17 22:50:04 +03:00
|
|
|
/* link URL for the highlights */
|
2017-12-26 04:03:18 +03:00
|
|
|
highlightLink: 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 */
|
2017-12-26 04:03:18 +03:00
|
|
|
showUrlPreview: PropTypes.bool,
|
2016-07-18 03:35:42 +03:00
|
|
|
|
2016-03-05 05:05:29 +03:00
|
|
|
/* is this the focused event */
|
2017-12-26 04:03:18 +03:00
|
|
|
isSelectedEvent: 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 */
|
2019-03-06 14:27:16 +03:00
|
|
|
onHeightChanged: 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'. */
|
2017-12-26 04:03:18 +03:00
|
|
|
readReceipts: PropTypes.arrayOf(React.PropTypes.object),
|
2016-04-19 21:38:54 +03:00
|
|
|
|
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
|
|
|
|
*/
|
2017-12-26 04:03:18 +03:00
|
|
|
readReceiptMap: PropTypes.object,
|
2016-04-21 01:03:05 +03:00
|
|
|
|
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.
|
|
|
|
*/
|
2017-12-26 04:03:18 +03:00
|
|
|
checkUnmounting: PropTypes.func,
|
2016-04-22 19:03:15 +03:00
|
|
|
|
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. */
|
2017-12-26 04:03:18 +03:00
|
|
|
eventSendStatus: 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.
|
|
|
|
*/
|
2017-12-26 04:03:18 +03:00
|
|
|
tileShape: PropTypes.string,
|
2017-05-20 00:45:56 +03:00
|
|
|
|
|
|
|
// show twelve hour timestamps
|
2017-12-26 04:03:18 +03:00
|
|
|
isTwelveHour: PropTypes.bool,
|
2015-12-24 03:12:37 +03:00
|
|
|
},
|
|
|
|
|
2018-05-03 13:23:41 +03:00
|
|
|
getDefaultProps: function() {
|
|
|
|
return {
|
2019-03-06 14:27:16 +03:00
|
|
|
// no-op function because onHeightChanged is optional yet some sub-components assume its existence
|
|
|
|
onHeightChanged: function() {},
|
2018-05-03 13:23:41 +03:00
|
|
|
};
|
2015-12-24 03:12:37 +03:00
|
|
|
},
|
|
|
|
|
2015-11-27 18:02:32 +03:00
|
|
|
getInitialState: function() {
|
2018-03-08 14:48:04 +03:00
|
|
|
return {
|
2019-04-26 14:14:30 +03:00
|
|
|
// Whether the action bar is focused.
|
|
|
|
actionBarFocused: false,
|
2018-03-08 14:48:04 +03:00
|
|
|
// 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,
|
|
|
|
};
|
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
|
|
|
},
|
|
|
|
|
2017-01-20 17:22:27 +03:00
|
|
|
componentWillReceiveProps: function(nextProps) {
|
2017-08-08 14:34:40 +03:00
|
|
|
// re-check the sender verification as outgoing events progress through
|
|
|
|
// the send process.
|
|
|
|
if (nextProps.eventSendStatus !== this.props.eventSendStatus) {
|
2016-06-08 19:01:13 +03:00
|
|
|
this._verifyEvent(nextProps.mxEvent);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2017-01-20 17:22:27 +03:00
|
|
|
shouldComponentUpdate: function(nextProps, nextState) {
|
2016-04-19 21:38:54 +03:00
|
|
|
if (!ObjectUtils.shallowEqual(this.state, nextState)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-05-06 21:28:18 +03:00
|
|
|
return !this._propsEqual(this.props, nextProps);
|
2016-04-19 21:38:54 +03:00
|
|
|
},
|
|
|
|
|
2016-06-08 20:35:43 +03:00
|
|
|
componentWillUnmount: function() {
|
2017-10-11 19:56:17 +03:00
|
|
|
const client = this.props.matrixClient;
|
2018-05-06 21:28:18 +03:00
|
|
|
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.
|
2019-03-06 14:27:16 +03:00
|
|
|
// (we call onHeightChanged in _verifyEvent to handle the case where decryption
|
2017-12-15 17:01:07 +03:00
|
|
|
// has caused a change in size of the event tile)
|
2016-11-15 14:12:52 +03:00
|
|
|
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) {
|
2018-05-06 21:28:18 +03:00
|
|
|
if (userId === this.props.mxEvent.getSender()) {
|
2016-06-08 23:25:42 +03:00
|
|
|
this._verifyEvent(this.props.mxEvent);
|
2016-06-08 20:35:43 +03:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2017-07-19 01:46:03 +03:00
|
|
|
_verifyEvent: async function(mxEvent) {
|
|
|
|
if (!mxEvent.isEncrypted()) {
|
|
|
|
return;
|
2016-06-08 19:01:13 +03:00
|
|
|
}
|
|
|
|
|
2017-07-19 01:46:03 +03:00
|
|
|
const verified = await this.props.matrixClient.isEventSenderVerified(mxEvent);
|
2016-06-08 19:01:13 +03:00
|
|
|
this.setState({
|
2017-10-11 19:56:17 +03:00
|
|
|
verified: verified,
|
2017-12-15 16:58:58 +03:00
|
|
|
}, () => {
|
|
|
|
// Decryption may have caused a change in size
|
2019-03-06 14:27:16 +03:00
|
|
|
this.props.onHeightChanged();
|
2016-06-08 19:01:13 +03:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2016-04-19 21:38:54 +03:00
|
|
|
_propsEqual: function(objA, objB) {
|
2017-10-11 19:56:17 +03:00
|
|
|
const keysA = Object.keys(objA);
|
|
|
|
const keysB = Object.keys(objB);
|
2016-04-19 21:38:54 +03:00
|
|
|
|
|
|
|
if (keysA.length !== keysB.length) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-10-11 19:56:17 +03:00
|
|
|
for (let i = 0; i < keysA.length; i++) {
|
|
|
|
const key = keysA[i];
|
2016-04-19 21:38:54 +03:00
|
|
|
|
|
|
|
if (!objB.hasOwnProperty(key)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// need to deep-compare readReceipts
|
2018-05-06 21:28:18 +03:00
|
|
|
if (key === 'readReceipts') {
|
2017-10-11 19:56:17 +03:00
|
|
|
const rA = objA[key];
|
|
|
|
const 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;
|
|
|
|
}
|
2017-10-11 19:56:17 +03:00
|
|
|
for (let j = 0; j < rA.length; j++) {
|
2018-10-10 17:14:09 +03:00
|
|
|
if (rA[j].userId !== rB[j].userId) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// one has a member set and the other doesn't?
|
|
|
|
if (rA[j].roomMember !== rB[j].roomMember) {
|
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() {
|
2017-10-11 19:56:17 +03:00
|
|
|
const 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
|
2017-10-11 19:56:17 +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;
|
|
|
|
},
|
|
|
|
|
|
|
|
toggleAllReadAvatars: function() {
|
|
|
|
this.setState({
|
2017-10-11 19:56:17 +03:00
|
|
|
allReadAvatars: !this.state.allReadAvatars,
|
2015-11-27 18:02:32 +03:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
getReadAvatars: function() {
|
2017-04-22 17:40:29 +03:00
|
|
|
// return early if there are no read receipts
|
|
|
|
if (!this.props.readReceipts || this.props.readReceipts.length === 0) {
|
2018-05-06 21:28:18 +03:00
|
|
|
return (<span className="mx_EventTile_readAvatars" />);
|
2017-04-21 23:28:28 +03:00
|
|
|
}
|
|
|
|
|
2017-03-27 16:34:05 +03:00
|
|
|
const ReadReceiptMarker = sdk.getComponent('rooms.ReadReceiptMarker');
|
|
|
|
const avatars = [];
|
|
|
|
const receiptOffset = 15;
|
|
|
|
let left = 0;
|
2015-11-27 18:02:32 +03:00
|
|
|
|
2017-10-11 19:56:17 +03:00
|
|
|
const receipts = this.props.readReceipts || [];
|
|
|
|
for (let i = 0; i < receipts.length; ++i) {
|
|
|
|
const receipt = receipts[i];
|
2015-11-27 18:02:32 +03:00
|
|
|
|
2017-10-11 19:56:17 +03:00
|
|
|
let hidden = true;
|
2016-04-21 01:03:05 +03:00
|
|
|
if ((i < MAX_READ_AVATARS) || this.state.allReadAvatars) {
|
|
|
|
hidden = false;
|
2015-11-27 18:02:32 +03:00
|
|
|
}
|
2017-03-27 16:34:05 +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.
|
|
|
|
|
|
|
|
// If hidden, set offset equal to the offset of the final visible avatar or
|
|
|
|
// else set it proportional to index
|
|
|
|
left = (hidden ? MAX_READ_AVATARS - 1 : i) * -receiptOffset;
|
2015-11-27 18:02:32 +03:00
|
|
|
|
2018-10-10 17:14:09 +03:00
|
|
|
const userId = receipt.userId;
|
2018-05-06 21:28:18 +03:00
|
|
|
let 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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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}
|
2018-10-10 17:14:09 +03:00
|
|
|
fallbackUserId={userId}
|
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}
|
2017-06-22 17:53:58 +03:00
|
|
|
showTwelveHour={this.props.isTwelveHour}
|
2017-10-11 19:56:17 +03:00
|
|
|
/>,
|
2015-11-27 18:02:32 +03:00
|
|
|
);
|
|
|
|
}
|
2017-10-11 19:56:17 +03:00
|
|
|
let remText;
|
2015-11-27 18:02:32 +03:00
|
|
|
if (!this.state.allReadAvatars) {
|
2017-10-11 19:56:17 +03:00
|
|
|
const remainder = receipts.length - MAX_READ_AVATARS;
|
2015-11-27 18:02:32 +03:00
|
|
|
if (remainder > 0) {
|
|
|
|
remText = <span className="mx_EventTile_readAvatarRemainder"
|
|
|
|
onClick={this.toggleAllReadAvatars}
|
2017-03-27 16:34:05 +03:00
|
|
|
style={{ right: -(left - receiptOffset) }}>{ remainder }+
|
2015-11-27 18:02:32 +03:00
|
|
|
</span>;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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) {
|
2017-07-20 17:46:36 +03:00
|
|
|
const mxEvent = this.props.mxEvent;
|
2017-03-08 19:55:44 +03:00
|
|
|
dis.dispatch({
|
2017-07-20 20:02:54 +03:00
|
|
|
action: 'insert_mention',
|
2017-07-20 17:46:36 +03:00
|
|
|
user_id: mxEvent.getSender(),
|
2016-03-17 14:56:46 +03:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2016-09-12 20:50:46 +03:00
|
|
|
onCryptoClicked: function(e) {
|
2017-10-11 19:56:17 +03:00
|
|
|
const event = this.props.mxEvent;
|
2016-09-12 20:50:46 +03:00
|
|
|
|
2018-11-21 19:56:44 +03:00
|
|
|
Modal.createTrackedDialogAsync('Encrypted Event Dialog', '',
|
|
|
|
import('../../../async-components/views/dialogs/EncryptedEventDialog'),
|
|
|
|
{event},
|
|
|
|
);
|
2016-09-12 20:50:46 +03:00
|
|
|
},
|
|
|
|
|
2018-03-08 14:48:04 +03:00
|
|
|
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);
|
|
|
|
},
|
|
|
|
|
2017-03-08 19:55:44 +03:00
|
|
|
onPermalinkClicked: function(e) {
|
2017-03-09 12:56:52 +03:00
|
|
|
// This allows the permalink to be opened in a new tab/window or copied as
|
2017-03-08 19:55:44 +03:00
|
|
|
// matrix.to, but also for it to enable routing within Riot when clicked.
|
|
|
|
e.preventDefault();
|
|
|
|
dis.dispatch({
|
|
|
|
action: 'view_room',
|
|
|
|
event_id: this.props.mxEvent.getId(),
|
2017-06-08 16:17:49 +03:00
|
|
|
highlighted: true,
|
2017-03-08 19:55:44 +03:00
|
|
|
room_id: this.props.mxEvent.getRoomId(),
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2017-08-08 14:34:40 +03:00
|
|
|
_renderE2EPadlock: function() {
|
|
|
|
const ev = this.props.mxEvent;
|
|
|
|
const props = {onClick: this.onCryptoClicked};
|
|
|
|
|
2018-06-13 17:52:50 +03:00
|
|
|
// event could not be decrypted
|
2017-08-08 14:34:40 +03:00
|
|
|
if (ev.getContent().msgtype === 'm.bad.encrypted') {
|
2017-10-11 19:56:17 +03:00
|
|
|
return <E2ePadlockUndecryptable {...props} />;
|
2018-06-13 17:52:50 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// event is encrypted, display padlock corresponding to whether or not it is verified
|
|
|
|
if (ev.isEncrypted()) {
|
2019-02-01 18:43:12 +03:00
|
|
|
if (this.state.verified) {
|
|
|
|
return; // no icon for verified
|
|
|
|
} else {
|
|
|
|
return (<E2ePadlockUnverified {...props} />);
|
|
|
|
}
|
2018-06-13 17:52:50 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (this.props.matrixClient.isRoomEncrypted(ev.getRoomId())) {
|
2018-06-13 15:51:04 +03:00
|
|
|
// else if room is encrypted
|
|
|
|
// and event is being encrypted or is not_sent (Unknown Devices/Network Error)
|
2018-06-13 17:52:50 +03:00
|
|
|
if (ev.status === EventStatus.ENCRYPTING) {
|
2019-02-01 18:43:12 +03:00
|
|
|
return;
|
2017-08-08 14:34:40 +03:00
|
|
|
}
|
2018-06-13 17:52:50 +03:00
|
|
|
if (ev.status === EventStatus.NOT_SENT) {
|
2019-02-01 18:43:12 +03:00
|
|
|
return;
|
2017-08-08 14:34:40 +03:00
|
|
|
}
|
2018-06-13 17:52:50 +03:00
|
|
|
// if the event is not encrypted, but it's an e2e room, show the open padlock
|
|
|
|
return <E2ePadlockUnencrypted {...props} />;
|
2017-08-08 14:34:40 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// no padlock needed
|
|
|
|
return null;
|
|
|
|
},
|
|
|
|
|
2019-04-26 14:14:30 +03:00
|
|
|
onActionBarFocusChange(focused) {
|
|
|
|
this.setState({
|
|
|
|
actionBarFocused: focused,
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2015-11-27 18:02:32 +03:00
|
|
|
render: function() {
|
2017-10-11 19:56:17 +03:00
|
|
|
const MessageTimestamp = sdk.getComponent('messages.MessageTimestamp');
|
|
|
|
const SenderProfile = sdk.getComponent('messages.SenderProfile');
|
|
|
|
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
|
2015-11-27 18:02:32 +03:00
|
|
|
|
2016-07-20 14:03:13 +03:00
|
|
|
//console.log("EventTile showUrlPreview for %s is %s", this.props.mxEvent.getId(), this.props.showUrlPreview);
|
|
|
|
|
2017-10-11 19:56:17 +03:00
|
|
|
const content = this.props.mxEvent.getContent();
|
|
|
|
const msgtype = content.msgtype;
|
|
|
|
const eventType = this.props.mxEvent.getType();
|
2016-08-19 00:19:23 +03:00
|
|
|
|
2017-04-26 20:00:25 +03:00
|
|
|
// Info messages are basically information about commands processed on a room
|
2018-09-17 20:20:12 +03:00
|
|
|
const isInfoMessage = (
|
|
|
|
eventType !== 'm.room.message' && eventType !== 'm.sticker' && eventType != 'm.room.create'
|
|
|
|
);
|
2015-11-27 18:02:32 +03:00
|
|
|
|
2018-06-27 13:44:11 +03:00
|
|
|
const tileHandler = getHandlerTile(this.props.mxEvent);
|
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
|
2018-06-27 13:44:11 +03:00
|
|
|
if (!tileHandler) {
|
|
|
|
const {mxEvent} = this.props;
|
2018-06-29 14:54:36 +03:00
|
|
|
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>;
|
2015-11-27 18:02:32 +03:00
|
|
|
}
|
2018-06-27 13:44:11 +03:00
|
|
|
const EventTileType = sdk.getComponent(tileHandler);
|
2015-11-27 18:02:32 +03:00
|
|
|
|
2017-10-11 19:56:17 +03:00
|
|
|
const isSending = (['sending', 'queued', 'encrypting'].indexOf(this.props.eventSendStatus) !== -1);
|
2018-06-13 11:28:35 +03:00
|
|
|
const isRedacted = isMessageEvent(this.props.mxEvent) && this.props.isRedacted;
|
2018-03-08 14:48:04 +03:00
|
|
|
const isEncryptionFailure = this.props.mxEvent.isDecryptionFailure();
|
2016-09-12 03:37:51 +03:00
|
|
|
|
2017-05-20 00:26:24 +03:00
|
|
|
const classes = classNames({
|
2015-11-27 18:02:32 +03:00
|
|
|
mx_EventTile: true,
|
2016-08-18 23:53:37 +03:00
|
|
|
mx_EventTile_info: isInfoMessage,
|
2017-05-20 00:45:56 +03:00
|
|
|
mx_EventTile_12hr: this.props.isTwelveHour,
|
2018-05-06 21:28:18 +03:00
|
|
|
mx_EventTile_encrypting: this.props.eventSendStatus === 'encrypting',
|
2016-09-12 03:37:51 +03:00
|
|
|
mx_EventTile_sending: isSending,
|
2018-05-06 21:28:18 +03:00
|
|
|
mx_EventTile_notSent: this.props.eventSendStatus === 'not_sent',
|
|
|
|
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,
|
2019-04-26 14:14:30 +03:00
|
|
|
mx_EventTile_actionBarFocused: this.state.actionBarFocused,
|
2018-05-06 21:28:18 +03:00
|
|
|
mx_EventTile_verified: this.state.verified === true,
|
|
|
|
mx_EventTile_unverified: this.state.verified === false,
|
2018-03-08 14:48:04 +03:00
|
|
|
mx_EventTile_bad: isEncryptionFailure,
|
2017-04-26 20:00:25 +03:00
|
|
|
mx_EventTile_emote: msgtype === 'm.emote',
|
2017-03-03 18:42:24 +03:00
|
|
|
mx_EventTile_redacted: isRedacted,
|
2015-11-27 18:02:32 +03:00
|
|
|
});
|
2017-03-08 19:55:44 +03:00
|
|
|
|
2019-02-21 17:44:20 +03:00
|
|
|
let permalink = "#";
|
2019-02-22 14:22:36 +03:00
|
|
|
if (this.props.permalinkCreator) {
|
|
|
|
permalink = this.props.permalinkCreator.forEvent(this.props.mxEvent.getId());
|
2019-02-21 17:44:20 +03:00
|
|
|
}
|
2015-11-27 18:02:32 +03:00
|
|
|
|
2017-10-11 19:56:17 +03:00
|
|
|
const readAvatars = this.getReadAvatars();
|
2015-11-27 18:02:32 +03:00
|
|
|
|
2018-05-06 21:28:18 +03:00
|
|
|
let avatar;
|
|
|
|
let sender;
|
2016-08-25 18:55:09 +03:00
|
|
|
let avatarSize;
|
|
|
|
let needsSenderProfile;
|
|
|
|
|
2017-03-16 20:00:10 +03:00
|
|
|
if (this.props.tileShape === "notif") {
|
2016-09-11 04:14:27 +03:00
|
|
|
avatarSize = 24;
|
|
|
|
needsSenderProfile = true;
|
2018-08-20 18:20:59 +03:00
|
|
|
} else if (tileHandler === 'messages.RoomCreate') {
|
|
|
|
avatarSize = 0;
|
|
|
|
needsSenderProfile = false;
|
2016-09-11 04:14:27 +03:00
|
|
|
} 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) {
|
2017-10-15 22:08:41 +03:00
|
|
|
let text = null;
|
2018-05-06 21:18:41 +03:00
|
|
|
if (!this.props.tileShape || this.props.tileShape === 'reply' || this.props.tileShape === 'reply_preview') {
|
2017-10-15 22:08:41 +03:00
|
|
|
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');
|
2018-05-06 21:18:41 +03:00
|
|
|
sender = <SenderProfile onClick={this.onSenderProfileClick}
|
|
|
|
mxEvent={this.props.mxEvent}
|
|
|
|
enableFlair={!text}
|
|
|
|
text={text} />;
|
2017-10-11 19:56:17 +03:00
|
|
|
} else {
|
2017-08-30 12:55:25 +03:00
|
|
|
sender = <SenderProfile mxEvent={this.props.mxEvent} enableFlair={true} />;
|
2016-09-11 04:14:27 +03:00
|
|
|
}
|
2015-11-27 18:02:32 +03:00
|
|
|
}
|
2016-08-16 13:59:26 +03:00
|
|
|
|
2019-04-26 14:14:30 +03:00
|
|
|
const MessageActionBar = sdk.getComponent('messages.MessageActionBar');
|
|
|
|
const actionBar = <MessageActionBar
|
|
|
|
mxEvent={this.props.mxEvent}
|
|
|
|
permalinkCreator={this.props.permalinkCreator}
|
|
|
|
tile={this.refs.tile}
|
|
|
|
replyThread={this.refs.replyThread}
|
|
|
|
onFocusChange={this.onActionBarFocusChange}
|
|
|
|
/>;
|
2017-08-08 14:34:40 +03:00
|
|
|
|
2017-03-16 20:00:10 +03:00
|
|
|
const timestamp = this.props.mxEvent.getTs() ?
|
2017-05-20 00:45:56 +03:00
|
|
|
<MessageTimestamp showTwelveHour={this.props.isTwelveHour} ts={this.props.mxEvent.getTs()} /> : null;
|
2016-09-12 03:37:51 +03:00
|
|
|
|
2018-03-08 14:48:04 +03:00
|
|
|
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>},
|
2016-09-11 04:14:27 +03:00
|
|
|
);
|
2018-03-08 14:48:04 +03:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2019-05-01 20:05:11 +03:00
|
|
|
let reactions;
|
|
|
|
if (SettingsStore.isFeatureEnabled("feature_reactions")) {
|
|
|
|
const ReactionsRow = sdk.getComponent('messages.ReactionsRow');
|
|
|
|
reactions = <ReactionsRow
|
|
|
|
mxEvent={this.props.mxEvent}
|
|
|
|
/>;
|
|
|
|
}
|
|
|
|
|
2017-12-10 15:50:41 +03:00
|
|
|
switch (this.props.tileShape) {
|
|
|
|
case 'notif': {
|
2018-06-23 03:21:42 +03:00
|
|
|
const EmojiText = sdk.getComponent('elements.EmojiText');
|
2017-12-10 15:50:41 +03:00
|
|
|
const room = this.props.matrixClient.getRoom(this.props.mxEvent.getRoomId());
|
|
|
|
return (
|
|
|
|
<div className={classes}>
|
|
|
|
<div className="mx_EventTile_roomName">
|
2018-06-23 03:21:42 +03:00
|
|
|
<EmojiText element="a" href={permalink} onClick={this.onPermalinkClicked}>
|
2017-12-10 15:50:41 +03:00
|
|
|
{ room ? room.name : '' }
|
2018-06-23 03:21:42 +03:00
|
|
|
</EmojiText>
|
2017-12-10 15:50:41 +03:00
|
|
|
</div>
|
2016-09-11 04:14:27 +03:00
|
|
|
<div className="mx_EventTile_senderDetails">
|
2017-12-10 15:50:41 +03:00
|
|
|
{ avatar }
|
|
|
|
<a href={permalink} onClick={this.onPermalinkClicked}>
|
2016-09-11 04:14:27 +03:00
|
|
|
{ sender }
|
2017-03-03 18:42:24 +03:00
|
|
|
{ timestamp }
|
2017-12-10 15:50:41 +03:00
|
|
|
</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}
|
2019-03-06 14:27:16 +03:00
|
|
|
onHeightChanged={this.props.onHeightChanged} />
|
2016-09-11 04:14:27 +03:00
|
|
|
</div>
|
|
|
|
</div>
|
2017-12-10 15:50:41 +03:00
|
|
|
);
|
|
|
|
}
|
|
|
|
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}
|
2019-03-06 14:27:16 +03:00
|
|
|
onHeightChanged={this.props.onHeightChanged} />
|
2017-12-10 15:50:41 +03:00
|
|
|
</div>
|
|
|
|
<a
|
|
|
|
className="mx_EventTile_senderDetailsLink"
|
|
|
|
href={permalink}
|
|
|
|
onClick={this.onPermalinkClicked}
|
|
|
|
>
|
|
|
|
<div className="mx_EventTile_senderDetails">
|
|
|
|
{ sender }
|
|
|
|
{ timestamp }
|
|
|
|
</div>
|
2016-09-11 04:14:27 +03:00
|
|
|
</a>
|
|
|
|
</div>
|
2017-12-10 15:50:41 +03:00
|
|
|
);
|
|
|
|
}
|
2018-02-17 00:07:25 +03:00
|
|
|
|
|
|
|
case 'reply':
|
2018-02-16 23:58:39 +03:00
|
|
|
case 'reply_preview': {
|
2019-02-26 12:26:33 +03:00
|
|
|
let thread;
|
|
|
|
if (this.props.tileShape === 'reply_preview') {
|
|
|
|
thread = ReplyThread.makeThread(
|
|
|
|
this.props.mxEvent,
|
2019-03-06 14:27:16 +03:00
|
|
|
this.props.onHeightChanged,
|
2019-02-26 12:26:33 +03:00
|
|
|
this.props.permalinkCreator,
|
|
|
|
'replyThread',
|
|
|
|
);
|
|
|
|
}
|
2018-02-16 23:58:39 +03:00
|
|
|
return (
|
|
|
|
<div className={classes}>
|
|
|
|
{ avatar }
|
|
|
|
{ sender }
|
2018-02-17 00:07:25 +03:00
|
|
|
<div className="mx_EventTile_reply">
|
2018-06-26 18:59:49 +03:00
|
|
|
<a href={permalink} onClick={this.onPermalinkClicked}>
|
2018-02-16 23:58:39 +03:00
|
|
|
{ timestamp }
|
|
|
|
</a>
|
|
|
|
{ this._renderE2EPadlock() }
|
2019-02-26 12:26:33 +03:00
|
|
|
{ thread }
|
2018-02-16 23:58:39 +03:00
|
|
|
<EventTileType ref="tile"
|
|
|
|
mxEvent={this.props.mxEvent}
|
|
|
|
highlights={this.props.highlights}
|
2017-12-10 15:50:41 +03:00
|
|
|
highlightLink={this.props.highlightLink}
|
2019-03-06 14:27:16 +03:00
|
|
|
onHeightChanged={this.props.onHeightChanged}
|
2017-12-10 15:50:41 +03:00
|
|
|
showUrlPreview={false} />
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
default: {
|
2019-02-26 12:26:33 +03:00
|
|
|
const thread = ReplyThread.makeThread(
|
|
|
|
this.props.mxEvent,
|
2019-03-06 14:27:16 +03:00
|
|
|
this.props.onHeightChanged,
|
2019-02-26 12:26:33 +03:00
|
|
|
this.props.permalinkCreator,
|
|
|
|
'replyThread',
|
|
|
|
);
|
2017-12-10 15:50:41 +03:00
|
|
|
return (
|
|
|
|
<div className={classes}>
|
|
|
|
<div className="mx_EventTile_msgOption">
|
|
|
|
{ readAvatars }
|
|
|
|
</div>
|
|
|
|
{ sender }
|
|
|
|
<div className="mx_EventTile_line">
|
2018-06-26 18:59:49 +03:00
|
|
|
<a href={permalink} onClick={this.onPermalinkClicked}>
|
2017-12-10 15:50:41 +03:00
|
|
|
{ timestamp }
|
|
|
|
</a>
|
|
|
|
{ this._renderE2EPadlock() }
|
2019-02-26 12:26:33 +03:00
|
|
|
{ thread }
|
2017-12-10 15:50:41 +03:00
|
|
|
<EventTileType ref="tile"
|
|
|
|
mxEvent={this.props.mxEvent}
|
|
|
|
highlights={this.props.highlights}
|
|
|
|
highlightLink={this.props.highlightLink}
|
|
|
|
showUrlPreview={this.props.showUrlPreview}
|
2019-03-06 14:27:16 +03:00
|
|
|
onHeightChanged={this.props.onHeightChanged} />
|
2018-03-08 14:48:04 +03:00
|
|
|
{ keyRequestInfo }
|
2019-05-01 20:05:11 +03:00
|
|
|
{ reactions }
|
2019-04-26 14:14:30 +03:00
|
|
|
{ actionBar }
|
2017-12-10 15:50:41 +03:00
|
|
|
</div>
|
2018-07-12 19:48:49 +03:00
|
|
|
{
|
|
|
|
// 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 }
|
2017-12-10 15:50:41 +03:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2016-09-11 04:14:27 +03:00
|
|
|
}
|
2015-11-27 18:02:32 +03:00
|
|
|
},
|
2016-11-14 19:00:24 +03:00
|
|
|
}));
|
|
|
|
|
2018-06-13 11:28:35 +03:00
|
|
|
// 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()));
|
|
|
|
}
|
|
|
|
|
2016-11-14 19:00:24 +03:00
|
|
|
module.exports.haveTileForEvent = function(e) {
|
2017-03-03 19:45:29 +03:00
|
|
|
// Only messages have a tile (black-rectangle) if redacted
|
2018-06-13 11:28:35 +03:00
|
|
|
if (e.isRedacted() && !isMessageEvent(e)) return false;
|
2017-11-15 18:56:54 +03:00
|
|
|
|
|
|
|
const handler = getHandlerTile(e);
|
|
|
|
if (handler === undefined) return false;
|
|
|
|
if (handler === 'messages.TextualEvent') {
|
2016-11-14 19:00:24 +03:00
|
|
|
return TextForEvent.textForEvent(e) !== '';
|
2018-08-20 18:20:59 +03:00
|
|
|
} else if (handler === 'messages.RoomCreate') {
|
|
|
|
return Boolean(e.getContent()['predecessor']);
|
2016-11-14 19:00:24 +03:00
|
|
|
} else {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
};
|
2017-08-08 14:34:40 +03:00
|
|
|
|
|
|
|
function E2ePadlockUndecryptable(props) {
|
|
|
|
return (
|
2019-02-12 20:58:02 +03:00
|
|
|
<E2ePadlock title={_t("Undecryptable")} icon="undecryptable" {...props} />
|
2017-08-08 14:34:40 +03:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function E2ePadlockUnverified(props) {
|
|
|
|
return (
|
2019-02-12 20:58:02 +03:00
|
|
|
<E2ePadlock title={_t("Encrypted by an unverified device")} icon="unverified" {...props} />
|
2017-08-08 14:34:40 +03:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function E2ePadlockUnencrypted(props) {
|
|
|
|
return (
|
2019-02-12 20:58:02 +03:00
|
|
|
<E2ePadlock title={_t("Unencrypted message")} icon="unencrypted" {...props} />
|
2017-08-08 14:34:40 +03:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function E2ePadlock(props) {
|
2018-04-23 05:30:37 +03:00
|
|
|
if (SettingsStore.getValue("alwaysShowEncryptionIcons")) {
|
2019-02-12 20:58:02 +03:00
|
|
|
return (<div
|
|
|
|
className={`mx_EventTile_e2eIcon mx_EventTile_e2eIcon_${props.icon}`}
|
|
|
|
title={props.title} onClick={props.onClick} />);
|
2018-04-23 05:30:37 +03:00
|
|
|
} else {
|
2019-02-12 20:58:02 +03:00
|
|
|
return (<div
|
|
|
|
className={`mx_EventTile_e2eIcon mx_EventTile_e2eIcon_hidden mx_EventTile_e2eIcon_${props.icon}`}
|
|
|
|
onClick={props.onClick} />);
|
2018-04-23 05:30:37 +03:00
|
|
|
}
|
2017-08-08 14:34:40 +03:00
|
|
|
}
|
2017-12-10 15:50:41 +03:00
|
|
|
|
|
|
|
module.exports.getHandlerTile = getHandlerTile;
|