Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hide pre-join UTDs #3881

Merged
merged 16 commits into from
Jan 28, 2020
93 changes: 90 additions & 3 deletions src/components/structures/MessagePanel.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2018 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Copyright 2019-2020 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -20,6 +20,7 @@ import React, {createRef} from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import {EventTimeline} from 'matrix-js-sdk/src/models/event-timeline';
import shouldHideEvent from '../../shouldHideEvent';
import {wantsDateSeparator} from '../../DateUtils';
import * as sdk from '../../index';
Expand Down Expand Up @@ -115,6 +116,7 @@ export default class MessagePanel extends React.Component {
// previous positions the read marker has been in, so we can
// display 'ghost' read markers that are animating away
ghostReadMarkers: [],
preventBackPaginating: false,
};

// opaque readreceipt info for each userId; used by ReadReceiptMarker
Expand Down Expand Up @@ -419,6 +421,7 @@ export default class MessagePanel extends React.Component {
let lastShownEvent;

let lastShownNonLocalEchoIndex = -1;

for (i = this.props.events.length-1; i >= 0; i--) {
const mxEv = this.props.events[i];
if (!this._shouldShowEvent(mxEv)) {
Expand All @@ -440,14 +443,32 @@ export default class MessagePanel extends React.Component {

const ret = [];

const firstShownEventIndex = checkForPreJoinUISI(
uhoreg marked this conversation as resolved.
Show resolved Hide resolved
this.props.events, this.props.room, this.props.ourUserId,
);

if (firstShownEventIndex > 0) {
if (!this.state.preventBackPaginating) {
this.setState({
preventBackPaginating: true,
});
}
} else {
if (this.state.preventBackPaginating) {
this.setState({
preventBackPaginating: false,
});
}
}

let prevEvent = null; // the last event we showed

this._readReceiptsByEvent = {};
if (this.props.showReadReceipts) {
this._readReceiptsByEvent = this._getReadReceiptsByShownEvent();
}

for (i = 0; i < this.props.events.length; i++) {
for (i = firstShownEventIndex; i < this.props.events.length; i++) {
const mxEv = this.props.events[i];
const eventId = mxEv.getId();
const last = (mxEv === lastShownEvent);
Expand Down Expand Up @@ -883,6 +904,13 @@ export default class MessagePanel extends React.Component {
}
}

onFillRequest = (backwards) => {
if (this.state.preventBackPaginating) {
return Promise.resolve(false);
}
return this.props.onFillRequest(backwards);
}

render() {
const ScrollPanel = sdk.getComponent("structures.ScrollPanel");
const WhoIsTypingTile = sdk.getComponent("rooms.WhoIsTypingTile");
Expand Down Expand Up @@ -921,7 +949,7 @@ export default class MessagePanel extends React.Component {
className={className}
onScroll={this.props.onScroll}
onResize={this.onResize}
onFillRequest={this.props.onFillRequest}
onFillRequest={this.onFillRequest}
onUnfillRequest={this.props.onUnfillRequest}
style={style}
stickyBottom={this.props.stickyBottom}
Expand All @@ -935,3 +963,62 @@ export default class MessagePanel extends React.Component {
);
}
}

/**
* Check for undecryptable messages that were sent while the user was not in
* the room.
*
* @param {Array<MatrixEvent>} events The timeline events to check
* @param {Room} room The room that the events are in
* @param {string} userId The user's ID
*
* @return {Number} The index within `events` of the event after the most recent
* undecryptable event that was sent while the user was not in the room. If no
* such events were found, then it returns 0.
*/
function checkForPreJoinUISI(events, room, userId) {
uhoreg marked this conversation as resolved.
Show resolved Hide resolved
if (events.length === 0 ||
!MatrixClientPeg.get().isRoomEncrypted(room.roomId)) {
return 0;
}

let i;
let userMembership = "leave";
// find the last event that is in a timeline (events may not be in a
// timeline if they have not been sent yet) and get the user's membership
// at that point.
for (i = events.length - 1; i >= 0; i--) {
const timeline = room.getTimelineForEvent(events[i].getId());
uhoreg marked this conversation as resolved.
Show resolved Hide resolved
if (timeline) {
const userMembershipEvent =
timeline.getState(EventTimeline.FORWARDS).getMember(userId);
userMembership = userMembershipEvent ? userMembershipEvent.membership : "leave";
const timelineEvents = timeline.getEvents();
uhoreg marked this conversation as resolved.
Show resolved Hide resolved
for (let j = timelineEvents.length - 1; j >= 0; j--) {
jryans marked this conversation as resolved.
Show resolved Hide resolved
const event = timelineEvents[i];
if (event.getStateKey() === userId
&& event.getType() === "m.room.member") {
const prevContent = event.getPrevContent();
userMembership = prevContent.membership || "leave";
}
}
break;
}
}

// now go through the rest of the events and find the first undecryptable
// one that was sent when the user wasn't in the room
for (; i >= 0; i--) {
const event = events[i];
if (event.getStateKey() === userId
&& event.getType() === "m.room.member") {
const prevContent = event.getPrevContent();
userMembership = prevContent.membership || "leave";
} else if (userMembership === "leave" && event.isDecryptionFailure()) {
// reached an undecryptable message when the user wasn't in
// the room -- don't try to load any more
return i + 1;
}
}
return 0;
}