Merge pull request #805 from matrix-org/luke/feature-read-marker

Sync RM across instances of Riot
pull/21833/head
Luke Barnard 2017-04-21 16:52:46 +01:00 committed by GitHub
commit ae8d04b35d
2 changed files with 53 additions and 18 deletions

View File

@ -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());

View File

@ -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 (
<MessagePanel ref="messagePanel"
hidden={ this.props.hidden }