Merge pull request #3184 from matrix-org/jryans/reactions-send-marks-unread

Track live events in timeline and use for read receipts and read markers
This commit is contained in:
J. Ryan Stinnett 2019-07-05 17:55:59 +01:00 committed by GitHub
commit 017fc84862
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -2,6 +2,7 @@
Copyright 2016 OpenMarket Ltd Copyright 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd Copyright 2017 Vector Creations Ltd
Copyright 2019 New Vector Ltd Copyright 2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -141,6 +142,7 @@ const TimelinePanel = React.createClass({
return { return {
events: [], events: [],
liveEvents: [],
timelineLoading: true, // track whether our room timeline is loading timelineLoading: true, // track whether our room timeline is loading
// canBackPaginate == false may mean: // canBackPaginate == false may mean:
@ -322,9 +324,11 @@ const TimelinePanel = React.createClass({
// We can now paginate in the unpaginated direction // We can now paginate in the unpaginated direction
const canPaginateKey = (backwards) ? 'canBackPaginate' : 'canForwardPaginate'; const canPaginateKey = (backwards) ? 'canBackPaginate' : 'canForwardPaginate';
const { events, liveEvents } = this._getEvents();
this.setState({ this.setState({
[canPaginateKey]: true, [canPaginateKey]: true,
events: this._getEvents(), events,
liveEvents,
}); });
} }
}, },
@ -356,10 +360,12 @@ const TimelinePanel = React.createClass({
debuglog("TimelinePanel: paginate complete backwards:"+backwards+"; success:"+r); debuglog("TimelinePanel: paginate complete backwards:"+backwards+"; success:"+r);
const { events, liveEvents } = this._getEvents();
const newState = { const newState = {
[paginatingKey]: false, [paginatingKey]: false,
[canPaginateKey]: r, [canPaginateKey]: r,
events: this._getEvents(), events,
liveEvents,
}; };
// moving the window in this direction may mean that we can now // moving the window in this direction may mean that we can now
@ -453,15 +459,13 @@ const TimelinePanel = React.createClass({
this._timelineWindow.paginate(EventTimeline.FORWARDS, 1, false).done(() => { this._timelineWindow.paginate(EventTimeline.FORWARDS, 1, false).done(() => {
if (this.unmounted) { return; } if (this.unmounted) { return; }
const events = this._timelineWindow.getEvents(); const { events, liveEvents } = this._getEvents();
const lastEv = events[events.length-1]; const lastLiveEvent = liveEvents[liveEvents.length - 1];
// if we're at the end of the live timeline, append the pending events const updatedState = {
if (this.props.timelineSet.room && !this._timelineWindow.canPaginate(EventTimeline.FORWARDS)) { events,
events.push(...this.props.timelineSet.room.getPendingEvents()); liveEvents,
} };
const updatedState = {events: events};
let callRMUpdated; let callRMUpdated;
if (this.props.manageReadMarkers) { if (this.props.manageReadMarkers) {
@ -478,13 +482,13 @@ const TimelinePanel = React.createClass({
callRMUpdated = false; callRMUpdated = false;
if (sender != myUserId && !UserActivity.sharedInstance().userActiveRecently()) { if (sender != myUserId && !UserActivity.sharedInstance().userActiveRecently()) {
updatedState.readMarkerVisible = true; updatedState.readMarkerVisible = true;
} else if (lastEv && this.getReadMarkerPosition() === 0) { } else if (lastLiveEvent && this.getReadMarkerPosition() === 0) {
// we know we're stuckAtBottom, so we can advance the RM // we know we're stuckAtBottom, so we can advance the RM
// immediately, to save a later render cycle // immediately, to save a later render cycle
this._setReadMarker(lastEv.getId(), lastEv.getTs(), true); this._setReadMarker(lastLiveEvent.getId(), lastLiveEvent.getTs(), true);
updatedState.readMarkerVisible = false; updatedState.readMarkerVisible = false;
updatedState.readMarkerEventId = lastEv.getId(); updatedState.readMarkerEventId = lastLiveEvent.getId();
callRMUpdated = true; callRMUpdated = true;
} }
} }
@ -694,9 +698,12 @@ const TimelinePanel = React.createClass({
if (e.errcode === 'M_UNRECOGNIZED' && lastReadEvent) { if (e.errcode === 'M_UNRECOGNIZED' && lastReadEvent) {
return MatrixClientPeg.get().sendReadReceipt( return MatrixClientPeg.get().sendReadReceipt(
lastReadEvent, lastReadEvent,
).catch(() => { ).catch((e) => {
console.error(e);
this.lastRRSentEventId = undefined; this.lastRRSentEventId = undefined;
}); });
} else {
console.error(e);
} }
// it failed, so allow retries next time the user is active // it failed, so allow retries next time the user is active
this.lastRRSentEventId = undefined; this.lastRRSentEventId = undefined;
@ -762,9 +769,9 @@ const TimelinePanel = React.createClass({
_advanceReadMarkerPastMyEvents: function() { _advanceReadMarkerPastMyEvents: function() {
if (!this.props.manageReadMarkers) return; if (!this.props.manageReadMarkers) return;
// we call _timelineWindow.getEvents() rather than using // we call `_timelineWindow.getEvents()` rather than using
// this.state.events, because react batches the update to the latter, so it // `this.state.liveEvents`, because React batches the update to the
// may not have been updated yet. // latter, so it may not have been updated yet.
const events = this._timelineWindow.getEvents(); const events = this._timelineWindow.getEvents();
// first find where the current RM is // first find where the current RM is
@ -1067,6 +1074,7 @@ const TimelinePanel = React.createClass({
} else { } else {
this.setState({ this.setState({
events: [], events: [],
liveEvents: [],
canBackPaginate: false, canBackPaginate: false,
canForwardPaginate: false, canForwardPaginate: false,
timelineLoading: true, timelineLoading: true,
@ -1086,21 +1094,26 @@ const TimelinePanel = React.createClass({
// the results if so. // the results if so.
if (this.unmounted) return; if (this.unmounted) return;
this.setState({ this.setState(this._getEvents());
events: this._getEvents(),
});
}, },
// get the list of events from the timeline window and the pending event list // get the list of events from the timeline window and the pending event list
_getEvents: function() { _getEvents: function() {
const events = this._timelineWindow.getEvents(); const events = this._timelineWindow.getEvents();
// Hold onto the live events separately. The read receipt and read marker
// should use this list, so that they don't advance into pending events.
const liveEvents = [...events];
// if we're at the end of the live timeline, append the pending events // if we're at the end of the live timeline, append the pending events
if (!this._timelineWindow.canPaginate(EventTimeline.FORWARDS)) { if (!this._timelineWindow.canPaginate(EventTimeline.FORWARDS)) {
events.push(...this.props.timelineSet.getPendingEvents()); events.push(...this.props.timelineSet.getPendingEvents());
} }
return events; return {
events,
liveEvents,
};
}, },
_indexForEventId: function(evId) { _indexForEventId: function(evId) {
@ -1128,8 +1141,10 @@ const TimelinePanel = React.createClass({
const myUserId = MatrixClientPeg.get().credentials.userId; const myUserId = MatrixClientPeg.get().credentials.userId;
let lastDisplayedIndex = null; let lastDisplayedIndex = null;
for (let i = this.state.events.length - 1; i >= 0; --i) { // Use `liveEvents` here because we don't want the read marker or read
const ev = this.state.events[i]; // receipt to advance into pending events.
for (let i = this.state.liveEvents.length - 1; i >= 0; --i) {
const ev = this.state.liveEvents[i];
if (ignoreOwn && ev.sender && ev.sender.userId == myUserId) { if (ignoreOwn && ev.sender && ev.sender.userId == myUserId) {
continue; continue;
@ -1164,8 +1179,8 @@ const TimelinePanel = React.createClass({
// easier to reason about, so let's start there and optimise later if // easier to reason about, so let's start there and optimise later if
// needed. // needed.
if (allowEventsWithoutTiles) { if (allowEventsWithoutTiles) {
for (let i = lastDisplayedIndex + 1; i < this.state.events.length; i++) { for (let i = lastDisplayedIndex + 1; i < this.state.liveEvents.length; i++) {
const ev = this.state.events[i]; const ev = this.state.liveEvents[i];
if (EventTile.haveTileForEvent(ev)) { if (EventTile.haveTileForEvent(ev)) {
break; break;
} }