Merge remote-tracking branch 'upstream/develop' into compact-reply-rendering

This commit is contained in:
Tulir Asokan 2021-06-20 21:19:15 +03:00
commit 29eff06c52
2 changed files with 48 additions and 75 deletions

View file

@ -263,8 +263,7 @@ export default class TextualBody extends React.Component {
//console.info("shouldComponentUpdate: ShowUrlPreview for %s is %s", this.props.mxEvent.getId(), this.props.showUrlPreview); //console.info("shouldComponentUpdate: ShowUrlPreview for %s is %s", this.props.mxEvent.getId(), this.props.showUrlPreview);
// exploit that events are immutable :) // exploit that events are immutable :)
return (nextProps.mxEvent !== this.props.mxEvent || return (nextProps.mxEvent.getId() !== this.props.mxEvent.getId() ||
nextProps.mxEvent.getId() !== this.props.mxEvent.getId() ||
nextProps.highlights !== this.props.highlights || nextProps.highlights !== this.props.highlights ||
nextProps.replacingEventId !== this.props.replacingEventId || nextProps.replacingEventId !== this.props.replacingEventId ||
nextProps.highlightLink !== this.props.highlightLink || nextProps.highlightLink !== this.props.highlightLink ||

View file

@ -300,9 +300,6 @@ interface IState {
// The Relations model from the JS SDK for reactions to `mxEvent` // The Relations model from the JS SDK for reactions to `mxEvent`
reactions: Relations; reactions: Relations;
// Our snapshotted/local copy of the props.mxEvent, for local echo reasons
mxEvent: MatrixEvent;
hover: boolean; hover: boolean;
} }
@ -337,8 +334,6 @@ export default class EventTile extends React.Component<IProps, IState> {
// The Relations model from the JS SDK for reactions to `mxEvent` // The Relations model from the JS SDK for reactions to `mxEvent`
reactions: this.getReactions(), reactions: this.getReactions(),
mxEvent: this.mxEvent.toSnapshot(), // snapshot up front to verify it all works
hover: false, hover: false,
}; };
@ -355,10 +350,6 @@ export default class EventTile extends React.Component<IProps, IState> {
this.ref = React.createRef(); this.ref = React.createRef();
} }
private get mxEvent(): MatrixEvent {
return this.state?.mxEvent ?? this.props.mxEvent;
}
/** /**
* When true, the tile qualifies for some sort of special read receipt. This could be a 'sending' * When true, the tile qualifies for some sort of special read receipt. This could be a 'sending'
* or 'sent' receipt, for example. * or 'sent' receipt, for example.
@ -367,16 +358,16 @@ export default class EventTile extends React.Component<IProps, IState> {
private get isEligibleForSpecialReceipt() { private get isEligibleForSpecialReceipt() {
// First, if there are other read receipts then just short-circuit this. // First, if there are other read receipts then just short-circuit this.
if (this.props.readReceipts && this.props.readReceipts.length > 0) return false; if (this.props.readReceipts && this.props.readReceipts.length > 0) return false;
if (!this.mxEvent) return false; if (!this.props.mxEvent) return false;
// Sanity check (should never happen, but we shouldn't explode if it does) // Sanity check (should never happen, but we shouldn't explode if it does)
const room = this.context.getRoom(this.mxEvent.getRoomId()); const room = this.context.getRoom(this.props.mxEvent.getRoomId());
if (!room) return false; if (!room) return false;
// Quickly check to see if the event was sent by us. If it wasn't, it won't qualify for // Quickly check to see if the event was sent by us. If it wasn't, it won't qualify for
// special read receipts. // special read receipts.
const myUserId = MatrixClientPeg.get().getUserId(); const myUserId = MatrixClientPeg.get().getUserId();
if (this.mxEvent.getSender() !== myUserId) return false; if (this.props.mxEvent.getSender() !== myUserId) return false;
// Finally, determine if the type is relevant to the user. This notably excludes state // Finally, determine if the type is relevant to the user. This notably excludes state
// events and pretty much anything that can't be sent by the composer as a message. For // events and pretty much anything that can't be sent by the composer as a message. For
@ -387,7 +378,7 @@ export default class EventTile extends React.Component<IProps, IState> {
EventType.RoomMessage, EventType.RoomMessage,
EventType.RoomMessageEncrypted, EventType.RoomMessageEncrypted,
]; ];
if (!simpleSendableEvents.includes(this.mxEvent.getType() as EventType)) return false; if (!simpleSendableEvents.includes(this.props.mxEvent.getType() as EventType)) return false;
// Default case // Default case
return true; return true;
@ -429,7 +420,7 @@ export default class EventTile extends React.Component<IProps, IState> {
// TODO: [REACT-WARNING] Move into constructor // TODO: [REACT-WARNING] Move into constructor
// eslint-disable-next-line camelcase // eslint-disable-next-line camelcase
UNSAFE_componentWillMount() { UNSAFE_componentWillMount() {
this.verifyEvent(this.mxEvent); this.verifyEvent(this.props.mxEvent);
} }
componentDidMount() { componentDidMount() {
@ -459,21 +450,11 @@ export default class EventTile extends React.Component<IProps, IState> {
} }
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
// If the echo changed meaningfully, update.
if (!this.state.mxEvent?.isEquivalentTo(nextProps.mxEvent)) {
return true;
}
if (objectHasDiff(this.state, nextState)) { if (objectHasDiff(this.state, nextState)) {
return true; return true;
} }
if (!this.propsEqual(this.props, nextProps)) { return !this.propsEqual(this.props, nextProps);
return true;
}
// Always assume there's no significant change.
return false;
} }
componentWillUnmount() { componentWillUnmount() {
@ -494,18 +475,11 @@ export default class EventTile extends React.Component<IProps, IState> {
this.context.on("Room.receipt", this.onRoomReceipt); this.context.on("Room.receipt", this.onRoomReceipt);
this.isListeningForReceipts = true; this.isListeningForReceipts = true;
} }
// Update the state again if the snapshot needs updating. Note that this will fire
// a second state update to re-render child components, which ultimately calls didUpdate
// again, so we break that loop with a reference check first (faster than comparing events).
if (this.state.mxEvent === prevState.mxEvent && !this.state?.mxEvent.isEquivalentTo(this.props.mxEvent)) {
this.setState({mxEvent: this.props.mxEvent.toSnapshot()});
}
} }
private onRoomReceipt = (ev, room) => { private onRoomReceipt = (ev, room) => {
// ignore events for other rooms // ignore events for other rooms
const tileRoom = MatrixClientPeg.get().getRoom(this.mxEvent.getRoomId()); const tileRoom = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
if (room !== tileRoom) return; if (room !== tileRoom) return;
if (!this.shouldShowSentReceipt && !this.shouldShowSendingReceipt && !this.isListeningForReceipts) { if (!this.shouldShowSentReceipt && !this.shouldShowSendingReceipt && !this.isListeningForReceipts) {
@ -529,19 +503,19 @@ export default class EventTile extends React.Component<IProps, IState> {
// we need to re-verify the sending device. // we need to re-verify the sending device.
// (we call onHeightChanged in verifyEvent to handle the case where decryption // (we call onHeightChanged in verifyEvent to handle the case where decryption
// has caused a change in size of the event tile) // has caused a change in size of the event tile)
this.verifyEvent(this.mxEvent); this.verifyEvent(this.props.mxEvent);
this.forceUpdate(); this.forceUpdate();
}; };
private onDeviceVerificationChanged = (userId, device) => { private onDeviceVerificationChanged = (userId, device) => {
if (userId === this.mxEvent.getSender()) { if (userId === this.props.mxEvent.getSender()) {
this.verifyEvent(this.mxEvent); this.verifyEvent(this.props.mxEvent);
} }
}; };
private onUserVerificationChanged = (userId, _trustStatus) => { private onUserVerificationChanged = (userId, _trustStatus) => {
if (userId === this.mxEvent.getSender()) { if (userId === this.props.mxEvent.getSender()) {
this.verifyEvent(this.mxEvent); this.verifyEvent(this.props.mxEvent);
} }
}; };
@ -648,11 +622,11 @@ export default class EventTile extends React.Component<IProps, IState> {
} }
shouldHighlight() { shouldHighlight() {
const actions = this.context.getPushActionsForEvent(this.mxEvent.replacingEvent() || this.mxEvent); const actions = this.context.getPushActionsForEvent(this.props.mxEvent.replacingEvent() || this.props.mxEvent);
if (!actions || !actions.tweaks) { return false; } if (!actions || !actions.tweaks) { return false; }
// don't show self-highlights from another of our clients // don't show self-highlights from another of our clients
if (this.mxEvent.getSender() === this.context.credentials.userId) { if (this.props.mxEvent.getSender() === this.context.credentials.userId) {
return false; return false;
} }
@ -667,7 +641,7 @@ export default class EventTile extends React.Component<IProps, IState> {
getReadAvatars() { getReadAvatars() {
if (this.shouldShowSentReceipt || this.shouldShowSendingReceipt) { if (this.shouldShowSentReceipt || this.shouldShowSendingReceipt) {
return <SentReceipt messageState={this.mxEvent.getAssociatedStatus()} />; return <SentReceipt messageState={this.props.mxEvent.getAssociatedStatus()} />;
} }
// return early if there are no read receipts // return early if there are no read receipts
@ -754,7 +728,7 @@ export default class EventTile extends React.Component<IProps, IState> {
} }
onSenderProfileClick = event => { onSenderProfileClick = event => {
const mxEvent = this.mxEvent; const mxEvent = this.props.mxEvent;
dis.dispatch<ComposerInsertPayload>({ dis.dispatch<ComposerInsertPayload>({
action: Action.ComposerInsert, action: Action.ComposerInsert,
userId: mxEvent.getSender(), userId: mxEvent.getSender(),
@ -771,7 +745,7 @@ export default class EventTile extends React.Component<IProps, IState> {
// Cancel any outgoing key request for this event and resend it. If a response // 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 // is received for the request with the required keys, the event could be
// decrypted successfully. // decrypted successfully.
this.context.cancelAndResendEventRoomKeyRequest(this.mxEvent); this.context.cancelAndResendEventRoomKeyRequest(this.props.mxEvent);
}; };
onPermalinkClicked = e => { onPermalinkClicked = e => {
@ -780,14 +754,14 @@ export default class EventTile extends React.Component<IProps, IState> {
e.preventDefault(); e.preventDefault();
dis.dispatch({ dis.dispatch({
action: 'view_room', action: 'view_room',
event_id: this.mxEvent.getId(), event_id: this.props.mxEvent.getId(),
highlighted: true, highlighted: true,
room_id: this.mxEvent.getRoomId(), room_id: this.props.mxEvent.getRoomId(),
}); });
}; };
private renderE2EPadlock() { private renderE2EPadlock() {
const ev = this.mxEvent; const ev = this.props.mxEvent;
// event could not be decrypted // event could not be decrypted
if (ev.getContent().msgtype === 'm.bad.encrypted') { if (ev.getContent().msgtype === 'm.bad.encrypted') {
@ -846,7 +820,7 @@ export default class EventTile extends React.Component<IProps, IState> {
) { ) {
return null; return null;
} }
const eventId = this.mxEvent.getId(); const eventId = this.props.mxEvent.getId();
return this.props.getRelationsForEvent(eventId, "m.annotation", "m.reaction"); return this.props.getRelationsForEvent(eventId, "m.annotation", "m.reaction");
}; };
@ -865,13 +839,13 @@ export default class EventTile extends React.Component<IProps, IState> {
const SenderProfile = sdk.getComponent('messages.SenderProfile'); const SenderProfile = sdk.getComponent('messages.SenderProfile');
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar'); const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
//console.info("EventTile showUrlPreview for %s is %s", this.mxEvent.getId(), this.props.showUrlPreview); //console.info("EventTile showUrlPreview for %s is %s", this.props.mxEvent.getId(), this.props.showUrlPreview);
const content = this.mxEvent.getContent(); const content = this.props.mxEvent.getContent();
const msgtype = content.msgtype; const msgtype = content.msgtype;
const eventType = this.mxEvent.getType(); const eventType = this.props.mxEvent.getType();
let tileHandler = getHandlerTile(this.mxEvent); let tileHandler = getHandlerTile(this.props.mxEvent);
// Info messages are basically information about commands processed on a room // Info messages are basically information about commands processed on a room
const isBubbleMessage = eventType.startsWith("m.key.verification") || const isBubbleMessage = eventType.startsWith("m.key.verification") ||
@ -888,7 +862,7 @@ export default class EventTile extends React.Component<IProps, IState> {
// source tile when there's no regular tile for an event and also for // source tile when there's no regular tile for an event and also for
// replace relations (which otherwise would display as a confusing // replace relations (which otherwise would display as a confusing
// duplicate of the thing they are replacing). // duplicate of the thing they are replacing).
if (SettingsStore.getValue("showHiddenEventsInTimeline") && !haveTileForEvent(this.mxEvent)) { if (SettingsStore.getValue("showHiddenEventsInTimeline") && !haveTileForEvent(this.props.mxEvent)) {
tileHandler = "messages.ViewSourceEvent"; tileHandler = "messages.ViewSourceEvent";
// Reuse info message avatar and sender profile styling // Reuse info message avatar and sender profile styling
isInfoMessage = true; isInfoMessage = true;
@ -907,8 +881,8 @@ export default class EventTile extends React.Component<IProps, IState> {
const EventTileType = sdk.getComponent(tileHandler); const EventTileType = sdk.getComponent(tileHandler);
const isSending = (['sending', 'queued', 'encrypting'].indexOf(this.props.eventSendStatus) !== -1); const isSending = (['sending', 'queued', 'encrypting'].indexOf(this.props.eventSendStatus) !== -1);
const isRedacted = isMessageEvent(this.mxEvent) && this.props.isRedacted; const isRedacted = isMessageEvent(this.props.mxEvent) && this.props.isRedacted;
const isEncryptionFailure = this.mxEvent.isDecryptionFailure(); const isEncryptionFailure = this.props.mxEvent.isDecryptionFailure();
const isEditing = !!this.props.editState; const isEditing = !!this.props.editState;
const classes = classNames({ const classes = classNames({
@ -938,14 +912,14 @@ export default class EventTile extends React.Component<IProps, IState> {
let permalink = "#"; let permalink = "#";
if (this.props.permalinkCreator) { if (this.props.permalinkCreator) {
permalink = this.props.permalinkCreator.forEvent(this.mxEvent.getId()); permalink = this.props.permalinkCreator.forEvent(this.props.mxEvent.getId());
} }
// we can't use local echoes as scroll tokens, because their event IDs change. // we can't use local echoes as scroll tokens, because their event IDs change.
// Local echos have a send "status". // Local echos have a send "status".
const scrollToken = this.mxEvent.status const scrollToken = this.props.mxEvent.status
? undefined ? undefined
: this.mxEvent.getId(); : this.props.mxEvent.getId();
let avatar; let avatar;
let sender; let sender;
@ -975,15 +949,15 @@ export default class EventTile extends React.Component<IProps, IState> {
needsSenderProfile = true; needsSenderProfile = true;
} }
if (this.mxEvent.sender && avatarSize) { if (this.props.mxEvent.sender && avatarSize) {
let member; let member;
// set member to receiver (target) if it is a 3PID invite // set member to receiver (target) if it is a 3PID invite
// so that the correct avatar is shown as the text is // so that the correct avatar is shown as the text is
// `$target accepted the invitation for $email` // `$target accepted the invitation for $email`
if (this.mxEvent.getContent().third_party_invite) { if (this.props.mxEvent.getContent().third_party_invite) {
member = this.mxEvent.target; member = this.props.mxEvent.target;
} else { } else {
member = this.mxEvent.sender; member = this.props.mxEvent.sender;
} }
avatar = ( avatar = (
<div className="mx_EventTile_avatar"> <div className="mx_EventTile_avatar">
@ -998,17 +972,17 @@ export default class EventTile extends React.Component<IProps, IState> {
if (needsSenderProfile) { if (needsSenderProfile) {
if (!this.props.tileShape) { if (!this.props.tileShape) {
sender = <SenderProfile onClick={this.onSenderProfileClick} sender = <SenderProfile onClick={this.onSenderProfileClick}
mxEvent={this.mxEvent} mxEvent={this.props.mxEvent}
enableFlair={this.props.enableFlair} enableFlair={this.props.enableFlair}
/>; />;
} else { } else {
sender = <SenderProfile mxEvent={this.mxEvent} enableFlair={this.props.enableFlair} />; sender = <SenderProfile mxEvent={this.props.mxEvent} enableFlair={this.props.enableFlair} />;
} }
} }
const MessageActionBar = sdk.getComponent('messages.MessageActionBar'); const MessageActionBar = sdk.getComponent('messages.MessageActionBar');
const actionBar = !isEditing ? <MessageActionBar const actionBar = !isEditing ? <MessageActionBar
mxEvent={this.mxEvent} mxEvent={this.props.mxEvent}
reactions={this.state.reactions} reactions={this.state.reactions}
permalinkCreator={this.props.permalinkCreator} permalinkCreator={this.props.permalinkCreator}
getTile={this.getTile} getTile={this.getTile}
@ -1016,10 +990,10 @@ export default class EventTile extends React.Component<IProps, IState> {
onFocusChange={this.onActionBarFocusChange} onFocusChange={this.onActionBarFocusChange}
/> : undefined; /> : undefined;
const showTimestamp = this.mxEvent.getTs() && const showTimestamp = this.props.mxEvent.getTs() &&
(this.props.alwaysShowTimestamps || this.props.last || this.state.hover || this.state.actionBarFocused); (this.props.alwaysShowTimestamps || this.props.last || this.state.hover || this.state.actionBarFocused);
const timestamp = showTimestamp ? const timestamp = showTimestamp ?
<MessageTimestamp showTwelveHour={this.props.isTwelveHour} ts={this.mxEvent.getTs()} /> : null; <MessageTimestamp showTwelveHour={this.props.isTwelveHour} ts={this.props.mxEvent.getTs()} /> : null;
const keyRequestHelpText = const keyRequestHelpText =
<div className="mx_EventTile_keyRequestInfo_tooltip_contents"> <div className="mx_EventTile_keyRequestInfo_tooltip_contents">
@ -1059,7 +1033,7 @@ export default class EventTile extends React.Component<IProps, IState> {
if (!isRedacted) { if (!isRedacted) {
const ReactionsRow = sdk.getComponent('messages.ReactionsRow'); const ReactionsRow = sdk.getComponent('messages.ReactionsRow');
reactionsRow = <ReactionsRow reactionsRow = <ReactionsRow
mxEvent={this.mxEvent} mxEvent={this.props.mxEvent}
reactions={this.state.reactions} reactions={this.state.reactions}
/>; />;
} }
@ -1067,7 +1041,7 @@ export default class EventTile extends React.Component<IProps, IState> {
const linkedTimestamp = <a const linkedTimestamp = <a
href={permalink} href={permalink}
onClick={this.onPermalinkClicked} onClick={this.onPermalinkClicked}
aria-label={formatTime(new Date(this.mxEvent.getTs()), this.props.isTwelveHour)} aria-label={formatTime(new Date(this.props.mxEvent.getTs()), this.props.isTwelveHour)}
> >
{ timestamp } { timestamp }
</a>; </a>;
@ -1086,7 +1060,7 @@ export default class EventTile extends React.Component<IProps, IState> {
switch (this.props.tileShape) { switch (this.props.tileShape) {
case 'notif': { case 'notif': {
const room = this.context.getRoom(this.mxEvent.getRoomId()); const room = this.context.getRoom(this.props.mxEvent.getRoomId());
return React.createElement(this.props.as || "li", { return React.createElement(this.props.as || "li", {
"className": classes, "className": classes,
"aria-live": ariaLive, "aria-live": ariaLive,
@ -1108,7 +1082,7 @@ export default class EventTile extends React.Component<IProps, IState> {
</div>, </div>,
<div className="mx_EventTile_line" key="mx_EventTile_line"> <div className="mx_EventTile_line" key="mx_EventTile_line">
<EventTileType ref={this.tile} <EventTileType ref={this.tile}
mxEvent={this.mxEvent} mxEvent={this.props.mxEvent}
highlights={this.props.highlights} highlights={this.props.highlights}
highlightLink={this.props.highlightLink} highlightLink={this.props.highlightLink}
showUrlPreview={this.props.showUrlPreview} showUrlPreview={this.props.showUrlPreview}
@ -1126,7 +1100,7 @@ export default class EventTile extends React.Component<IProps, IState> {
}, [ }, [
<div className="mx_EventTile_line" key="mx_EventTile_line"> <div className="mx_EventTile_line" key="mx_EventTile_line">
<EventTileType ref={this.tile} <EventTileType ref={this.tile}
mxEvent={this.mxEvent} mxEvent={this.props.mxEvent}
highlights={this.props.highlights} highlights={this.props.highlights}
highlightLink={this.props.highlightLink} highlightLink={this.props.highlightLink}
showUrlPreview={this.props.showUrlPreview} showUrlPreview={this.props.showUrlPreview}
@ -1150,7 +1124,7 @@ export default class EventTile extends React.Component<IProps, IState> {
default: { default: {
const thread = ReplyThread.makeThread( const thread = ReplyThread.makeThread(
this.mxEvent, this.props.mxEvent,
this.props.onHeightChanged, this.props.onHeightChanged,
this.props.permalinkCreator, this.props.permalinkCreator,
this.replyThread, this.replyThread,
@ -1178,7 +1152,7 @@ export default class EventTile extends React.Component<IProps, IState> {
{ groupPadlock } { groupPadlock }
{ thread } { thread }
<EventTileType ref={this.tile} <EventTileType ref={this.tile}
mxEvent={this.mxEvent} mxEvent={this.props.mxEvent}
replacingEventId={this.props.replacingEventId} replacingEventId={this.props.replacingEventId}
editState={this.props.editState} editState={this.props.editState}
highlights={this.props.highlights} highlights={this.props.highlights}