From 424aae6b91283edd1a4e25142cec48dacf3fb6ac Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Wed, 12 Apr 2017 15:04:38 +0100 Subject: [PATCH 01/18] Prevent the ghost and real RM tile from both appearing --- src/components/structures/MessagePanel.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 0f8d35f525..6ee308a5a7 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -386,9 +386,7 @@ module.exports = React.createClass({ ret.push(this._getReadMarkerTile(visible)); readMarkerVisible = visible; isVisibleReadMarker = visible; - } - - if (eventId == this.currentGhostEventId) { + } else if (eventId == this.currentGhostEventId) { // if we're showing an animation, continue to show it. ret.push(this._getReadMarkerGhostTile()); } else if (!isVisibleReadMarker && From 1c25ed89b01345da3af185bb1900dd7943c388aa Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Wed, 12 Apr 2017 15:05:39 +0100 Subject: [PATCH 02/18] Initial implementation of using new RM API As detailed here https://docs.google.com/document/d/1UWqdS-e1sdwkLDUY0wA4gZyIkRp-ekjsLZ8k6g_Zvso/edit, the RM state is no longer kept locally, but rather server-side. The client now uses it's locally-calculated RM to update the server and receives server updates via the per-room account data. The sending of the RR has been bundled in to reduce traffic when sending both. In effect, whenever a RR is sent the RM is sent with it but using the new API. This uses a js-sdk change which has set to be finalised and so might change. --- src/components/structures/TimelinePanel.js | 55 +++++++++++++++------- 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index 8cd820c284..4fbca4d40a 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -102,9 +102,6 @@ var TimelinePanel = React.createClass({ }, statics: { - // a map from room id to read marker event ID - roomReadMarkerMap: {}, - // a map from room id to read marker event timestamp roomReadMarkerTsMap: {}, }, @@ -121,10 +118,15 @@ var TimelinePanel = React.createClass({ getInitialState: function() { // XXX: we could track RM per TimelineSet rather than per Room. // but for now we just do it per room for simplicity. + let initialReadMarker = null; if (this.props.manageReadMarkers) { - var initialReadMarker = - TimelinePanel.roomReadMarkerMap[this.props.timelineSet.room.roomId] - || this._getCurrentReadReceipt(); + const readmarker = this.props.timelineSet.room.getAccountData('m.read_marker'); + if (readmarker){ + initialReadMarker = readmarker.getContent().marker; + } else { + initialReadMarker = this._getCurrentReadReceipt(); + } + console.info('Read marker initially', initialReadMarker); } return { @@ -180,6 +182,7 @@ var TimelinePanel = React.createClass({ MatrixClientPeg.get().on("Room.redaction", this.onRoomRedaction); MatrixClientPeg.get().on("Room.receipt", this.onRoomReceipt); MatrixClientPeg.get().on("Room.localEchoUpdated", this.onLocalEchoUpdated); + MatrixClientPeg.get().on("Room.accountData", this.onAccountData); this._initTimeline(this.props); }, @@ -466,6 +469,21 @@ var TimelinePanel = React.createClass({ this._reloadEvents(); }, + onAccountData: function(ev, room) { + if (this.unmounted) return; + + // ignore events for other rooms + if (room !== this.props.timelineSet.room) return; + + if (ev.getType() !== "m.read_marker") return; + + const markerEventId = ev.getContent().marker; + console.log('TimelinePanel: Read marker received from server', markerEventId); + + this.setState({ + readMarkerEventId: markerEventId, + }, this.props.onReadMarkerUpdated); + }, sendReadReceipt: function() { if (!this.refs.messagePanel) return; @@ -505,13 +523,23 @@ var TimelinePanel = React.createClass({ // we also remember the last read receipt we sent to avoid spamming the // same one at the server repeatedly - if (lastReadEventIndex > currentReadUpToEventIndex - && this.last_rr_sent_event_id != lastReadEvent.getId()) { + if ((lastReadEventIndex > currentReadUpToEventIndex && + this.last_rr_sent_event_id != lastReadEvent.getId()) || + this.last_rm_sent_event_id != this.state.readMarkerEventId) { + this.last_rr_sent_event_id = lastReadEvent.getId(); - MatrixClientPeg.get().sendReadReceipt(lastReadEvent).catch(() => { + this.last_rm_sent_event_id = this.state.readMarkerEventId; + + MatrixClientPeg.get().setRoomReadMarker( + this.props.timelineSet.room.roomId, + this.state.readMarkerEventId, + lastReadEvent + ).catch(() => { // it failed, so allow retries next time the user is active this.last_rr_sent_event_id = undefined; + this.last_rm_sent_event_id = undefined; }); + console.log('TimelinePanel: Read marker sent to the server ', this.state.readMarkerEventId, ); // do a quick-reset of our unreadNotificationCount to avoid having // to wait from the remote echo from the homeserver. @@ -956,16 +984,10 @@ var TimelinePanel = React.createClass({ _setReadMarker: function(eventId, eventTs, inhibitSetState) { var roomId = this.props.timelineSet.room.roomId; - if (TimelinePanel.roomReadMarkerMap[roomId] == eventId) { - // don't update the state (and cause a re-render) if there is - // no change to the RM. + if (eventId === this.state.readMarkerEventId) { return; } - // ideally we'd sync these via the server, but for now just stash them - // in a map. - TimelinePanel.roomReadMarkerMap[roomId] = eventId; - // in order to later figure out if the read marker is // above or below the visible timeline, we stash the timestamp. TimelinePanel.roomReadMarkerTsMap[roomId] = eventTs; @@ -974,6 +996,7 @@ var TimelinePanel = React.createClass({ return; } + // Do the local echo of the RM // run the render cycle before calling the callback, so that // getReadMarkerPosition() returns the right thing. this.setState({ From 249e42747b2435df6d431ee0170dd167133b4479 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Wed, 12 Apr 2017 15:09:56 +0100 Subject: [PATCH 03/18] Fix bug where `roomId` was expected to be a property on timelineSet --- src/components/structures/TimelinePanel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index 4fbca4d40a..18f52d1f07 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -734,7 +734,7 @@ var TimelinePanel = React.createClass({ // the messagePanel doesn't know where the read marker is. // if we know the timestamp of the read marker, make a guess based on that. - var rmTs = TimelinePanel.roomReadMarkerTsMap[this.props.timelineSet.roomId]; + const rmTs = TimelinePanel.roomReadMarkerTsMap[this.props.timelineSet.room.roomId]; if (rmTs && this.state.events.length > 0) { if (rmTs < this.state.events[0].getTs()) { return -1; From 9c9dc84f45e0b26df4c444babc42a614749c92c0 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Wed, 12 Apr 2017 15:12:37 +0100 Subject: [PATCH 04/18] Remove redundant setting of readMarkerEventId --- src/components/structures/TimelinePanel.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index 18f52d1f07..9277c3f2b7 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -417,9 +417,10 @@ var TimelinePanel = React.createClass({ } else if(lastEv && this.getReadMarkerPosition() === 0) { // we know we're stuckAtBottom, so we can advance the RM // immediately, to save a later render cycle + + // This call will setState with readMarkerEventId = lastEv.getId() this._setReadMarker(lastEv.getId(), lastEv.getTs(), true); updatedState.readMarkerVisible = false; - updatedState.readMarkerEventId = lastEv.getId(); callback = this.props.onReadMarkerUpdated; } } From 28ed69b61761abe28b0454c09e58841f0f4f19a1 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Tue, 18 Apr 2017 14:44:43 +0100 Subject: [PATCH 05/18] m.read_marker -> m.fully_read --- src/components/structures/TimelinePanel.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index 9277c3f2b7..162c474a25 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -120,7 +120,7 @@ var TimelinePanel = React.createClass({ // but for now we just do it per room for simplicity. let initialReadMarker = null; if (this.props.manageReadMarkers) { - const readmarker = this.props.timelineSet.room.getAccountData('m.read_marker'); + const readmarker = this.props.timelineSet.room.getAccountData('m.fully_read'); if (readmarker){ initialReadMarker = readmarker.getContent().marker; } else { @@ -476,7 +476,7 @@ var TimelinePanel = React.createClass({ // ignore events for other rooms if (room !== this.props.timelineSet.room) return; - if (ev.getType() !== "m.read_marker") return; + if (ev.getType() !== "m.fully_read") return; const markerEventId = ev.getContent().marker; console.log('TimelinePanel: Read marker received from server', markerEventId); From d33afa99ab25f85b2932f7d9c9621347eabaa40c Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Tue, 18 Apr 2017 15:13:05 +0100 Subject: [PATCH 06/18] marker -> event_id --- src/components/structures/TimelinePanel.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index 162c474a25..74cf549c4d 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -122,7 +122,7 @@ var TimelinePanel = React.createClass({ if (this.props.manageReadMarkers) { const readmarker = this.props.timelineSet.room.getAccountData('m.fully_read'); if (readmarker){ - initialReadMarker = readmarker.getContent().marker; + initialReadMarker = readmarker.getContent().event_id; } else { initialReadMarker = this._getCurrentReadReceipt(); } @@ -478,7 +478,7 @@ var TimelinePanel = React.createClass({ if (ev.getType() !== "m.fully_read") return; - const markerEventId = ev.getContent().marker; + const markerEventId = ev.getContent().event_id; console.log('TimelinePanel: Read marker received from server', markerEventId); this.setState({ @@ -1045,7 +1045,6 @@ var TimelinePanel = React.createClass({ // events when viewing historical messages, we get stuck in a loop // of paginating our way through the entire history of the room. var stickyBottom = !this._timelineWindow.canPaginate(EventTimeline.FORWARDS); - return (