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:
commit
017fc84862
1 changed files with 40 additions and 25 deletions
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue