diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 0f8d35f525..8d50789eb0 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -388,6 +388,8 @@ module.exports = React.createClass({ isVisibleReadMarker = visible; } + // XXX: there should be no need for a ghost tile - we should just use a + // a dispatch (user_activity_end) to start the RM animation. if (eventId == this.currentGhostEventId) { // if we're showing an animation, continue to show it. ret.push(this._getReadMarkerGhostTile()); diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index 3aec582f89..7325cea2da 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,14 @@ 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.fully_read'); + if (readmarker){ + initialReadMarker = readmarker.getContent().event_id; + } else { + initialReadMarker = this._getCurrentReadReceipt(); + } } return { @@ -173,6 +174,7 @@ var TimelinePanel = React.createClass({ debuglog("TimelinePanel: mounting"); this.last_rr_sent_event_id = undefined; + this.last_rm_sent_event_id = undefined; this.dispatcherRef = dis.register(this.onAction); MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline); @@ -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); }, @@ -247,6 +250,7 @@ var TimelinePanel = React.createClass({ client.removeListener("Room.redaction", this.onRoomRedaction); client.removeListener("Room.receipt", this.onRoomReceipt); client.removeListener("Room.localEchoUpdated", this.onLocalEchoUpdated); + client.removeListener("Room.accountData", this.onAccountData); } }, @@ -414,6 +418,7 @@ 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._setReadMarker(lastEv.getId(), lastEv.getTs(), true); updatedState.readMarkerVisible = false; updatedState.readMarkerEventId = lastEv.getId(); @@ -466,6 +471,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.fully_read") return; + + // XXX: roomReadMarkerTsMap not updated here so it is now inconsistent. Replace + // this mechanism of determining where the RM is relative to the view-port with + // one supported by the server (the client needs more than an event ID). + this.setState({ + readMarkerEventId: ev.getContent().event_id, + }, this.props.onReadMarkerUpdated); + }, sendReadReceipt: function() { if (!this.refs.messagePanel) return; @@ -505,12 +525,29 @@ 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().setRoomReadMarkers( + this.props.timelineSet.room.roomId, + this.state.readMarkerEventId, + lastReadEvent + ).catch((e) => { + // /read_markers API is not implemented on this HS, fallback to just RR + if (e.errcode === 'M_UNRECOGNIZED') { + return MatrixClientPeg.get().sendReadReceipt( + lastReadEvent + ).catch(() => { + this.last_rr_sent_event_id = undefined; + }); + } // 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; }); // do a quick-reset of our unreadNotificationCount to avoid having @@ -707,7 +744,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; @@ -957,16 +994,12 @@ 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. + // 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; @@ -975,6 +1008,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({ @@ -1022,7 +1056,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 (