Merge pull request #805 from matrix-org/luke/feature-read-marker
Sync RM across instances of Riot
This commit is contained in:
commit
ae8d04b35d
2 changed files with 53 additions and 18 deletions
|
@ -388,6 +388,8 @@ module.exports = React.createClass({
|
||||||
isVisibleReadMarker = visible;
|
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 (eventId == this.currentGhostEventId) {
|
||||||
// if we're showing an animation, continue to show it.
|
// if we're showing an animation, continue to show it.
|
||||||
ret.push(this._getReadMarkerGhostTile());
|
ret.push(this._getReadMarkerGhostTile());
|
||||||
|
|
|
@ -102,9 +102,6 @@ var TimelinePanel = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
statics: {
|
statics: {
|
||||||
// a map from room id to read marker event ID
|
|
||||||
roomReadMarkerMap: {},
|
|
||||||
|
|
||||||
// a map from room id to read marker event timestamp
|
// a map from room id to read marker event timestamp
|
||||||
roomReadMarkerTsMap: {},
|
roomReadMarkerTsMap: {},
|
||||||
},
|
},
|
||||||
|
@ -121,10 +118,14 @@ var TimelinePanel = React.createClass({
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
// XXX: we could track RM per TimelineSet rather than per Room.
|
// XXX: we could track RM per TimelineSet rather than per Room.
|
||||||
// but for now we just do it per room for simplicity.
|
// but for now we just do it per room for simplicity.
|
||||||
|
let initialReadMarker = null;
|
||||||
if (this.props.manageReadMarkers) {
|
if (this.props.manageReadMarkers) {
|
||||||
var initialReadMarker =
|
const readmarker = this.props.timelineSet.room.getAccountData('m.fully_read');
|
||||||
TimelinePanel.roomReadMarkerMap[this.props.timelineSet.room.roomId]
|
if (readmarker){
|
||||||
|| this._getCurrentReadReceipt();
|
initialReadMarker = readmarker.getContent().event_id;
|
||||||
|
} else {
|
||||||
|
initialReadMarker = this._getCurrentReadReceipt();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -173,6 +174,7 @@ var TimelinePanel = React.createClass({
|
||||||
debuglog("TimelinePanel: mounting");
|
debuglog("TimelinePanel: mounting");
|
||||||
|
|
||||||
this.last_rr_sent_event_id = undefined;
|
this.last_rr_sent_event_id = undefined;
|
||||||
|
this.last_rm_sent_event_id = undefined;
|
||||||
|
|
||||||
this.dispatcherRef = dis.register(this.onAction);
|
this.dispatcherRef = dis.register(this.onAction);
|
||||||
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
|
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.redaction", this.onRoomRedaction);
|
||||||
MatrixClientPeg.get().on("Room.receipt", this.onRoomReceipt);
|
MatrixClientPeg.get().on("Room.receipt", this.onRoomReceipt);
|
||||||
MatrixClientPeg.get().on("Room.localEchoUpdated", this.onLocalEchoUpdated);
|
MatrixClientPeg.get().on("Room.localEchoUpdated", this.onLocalEchoUpdated);
|
||||||
|
MatrixClientPeg.get().on("Room.accountData", this.onAccountData);
|
||||||
|
|
||||||
this._initTimeline(this.props);
|
this._initTimeline(this.props);
|
||||||
},
|
},
|
||||||
|
@ -247,6 +250,7 @@ var TimelinePanel = React.createClass({
|
||||||
client.removeListener("Room.redaction", this.onRoomRedaction);
|
client.removeListener("Room.redaction", this.onRoomRedaction);
|
||||||
client.removeListener("Room.receipt", this.onRoomReceipt);
|
client.removeListener("Room.receipt", this.onRoomReceipt);
|
||||||
client.removeListener("Room.localEchoUpdated", this.onLocalEchoUpdated);
|
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) {
|
} else if(lastEv && 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(lastEv.getId(), lastEv.getTs(), true);
|
||||||
updatedState.readMarkerVisible = false;
|
updatedState.readMarkerVisible = false;
|
||||||
updatedState.readMarkerEventId = lastEv.getId();
|
updatedState.readMarkerEventId = lastEv.getId();
|
||||||
|
@ -466,6 +471,21 @@ var TimelinePanel = React.createClass({
|
||||||
this._reloadEvents();
|
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() {
|
sendReadReceipt: function() {
|
||||||
if (!this.refs.messagePanel) return;
|
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
|
// we also remember the last read receipt we sent to avoid spamming the
|
||||||
// same one at the server repeatedly
|
// same one at the server repeatedly
|
||||||
if (lastReadEventIndex > currentReadUpToEventIndex
|
if ((lastReadEventIndex > currentReadUpToEventIndex &&
|
||||||
&& this.last_rr_sent_event_id != lastReadEvent.getId()) {
|
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();
|
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
|
// it failed, so allow retries next time the user is active
|
||||||
this.last_rr_sent_event_id = undefined;
|
this.last_rr_sent_event_id = undefined;
|
||||||
|
this.last_rm_sent_event_id = undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
// do a quick-reset of our unreadNotificationCount to avoid having
|
// 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.
|
// 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.
|
// 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.length > 0) {
|
||||||
if (rmTs < this.state.events[0].getTs()) {
|
if (rmTs < this.state.events[0].getTs()) {
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -957,16 +994,12 @@ var TimelinePanel = React.createClass({
|
||||||
_setReadMarker: function(eventId, eventTs, inhibitSetState) {
|
_setReadMarker: function(eventId, eventTs, inhibitSetState) {
|
||||||
var roomId = this.props.timelineSet.room.roomId;
|
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
|
||||||
// don't update the state (and cause a re-render) if there is
|
// no change to the RM.
|
||||||
// no change to the RM.
|
if (eventId === this.state.readMarkerEventId) {
|
||||||
return;
|
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
|
// in order to later figure out if the read marker is
|
||||||
// above or below the visible timeline, we stash the timestamp.
|
// above or below the visible timeline, we stash the timestamp.
|
||||||
TimelinePanel.roomReadMarkerTsMap[roomId] = eventTs;
|
TimelinePanel.roomReadMarkerTsMap[roomId] = eventTs;
|
||||||
|
@ -975,6 +1008,7 @@ var TimelinePanel = React.createClass({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Do the local echo of the RM
|
||||||
// run the render cycle before calling the callback, so that
|
// run the render cycle before calling the callback, so that
|
||||||
// getReadMarkerPosition() returns the right thing.
|
// getReadMarkerPosition() returns the right thing.
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -1022,7 +1056,6 @@ var TimelinePanel = React.createClass({
|
||||||
// events when viewing historical messages, we get stuck in a loop
|
// events when viewing historical messages, we get stuck in a loop
|
||||||
// of paginating our way through the entire history of the room.
|
// of paginating our way through the entire history of the room.
|
||||||
var stickyBottom = !this._timelineWindow.canPaginate(EventTimeline.FORWARDS);
|
var stickyBottom = !this._timelineWindow.canPaginate(EventTimeline.FORWARDS);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MessagePanel ref="messagePanel"
|
<MessagePanel ref="messagePanel"
|
||||||
hidden={ this.props.hidden }
|
hidden={ this.props.hidden }
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue