diff --git a/src/models/event-timeline-set.ts b/src/models/event-timeline-set.ts index 5cb04997e8b..9df897b182c 100644 --- a/src/models/event-timeline-set.ts +++ b/src/models/event-timeline-set.ts @@ -756,6 +756,99 @@ export class EventTimelineSet extends TypedEventEmitter event.getTs()) { + // We found an event later than ours, so insert before that. + break; + } + } + // If we got to the end of the loop, insertIndex points at the end of + // the list. + + const eventId = event.getId()!; + timeline.insertEvent(event, insertIndex, roomState); + this._eventIdToTimeline.set(eventId, timeline); + + this.relations.aggregateParentEvent(event); + this.relations.aggregateChildEvent(event, this); + + const data: IRoomTimelineData = { + timeline: timeline, + liveEvent: timeline == this.liveTimeline, + }; + this.emit(RoomEvent.Timeline, event, this.room, false, false, data); + } + /** * Replaces event with ID oldEventId with one with newEventId, if oldEventId is * recognised. Otherwise, add to the live timeline. Used to handle remote echos. diff --git a/src/models/event-timeline.ts b/src/models/event-timeline.ts index d1ba3210365..24f63a99965 100644 --- a/src/models/event-timeline.ts +++ b/src/models/event-timeline.ts @@ -427,6 +427,43 @@ export class EventTimeline { } } + /** + * Insert a new event into the timeline, and update the state. + * + * TEMPORARY: until we have recursive relations, we need this function + * to exist to allow us to insert events in timeline order, which is our + * best guess for Sync Order. + * This is a copy of addEvent above, modified to allow inserting an event at + * a specific index. + */ + public insertEvent(event: MatrixEvent, insertIndex: number, roomState: RoomState): void { + const timelineSet = this.getTimelineSet(); + + if (timelineSet.room) { + EventTimeline.setEventMetadata(event, roomState, false); + + // modify state but only on unfiltered timelineSets + if (event.isState() && timelineSet.room.getUnfilteredTimelineSet() === timelineSet) { + roomState.setStateEvents([event], {}); + // it is possible that the act of setting the state event means we + // can set more metadata (specifically sender/target props), so try + // it again if the prop wasn't previously set. It may also mean that + // the sender/target is updated (if the event set was a room member event) + // so we want to use the *updated* member (new avatar/name) instead. + // + // However, we do NOT want to do this on member events if we're going + // back in time, else we'll set the .sender value for BEFORE the given + // member event, whereas we want to set the .sender value for the ACTUAL + // member event itself. + if (!event.sender || event.getType() === EventType.RoomMember) { + EventTimeline.setEventMetadata(event, roomState!, false); + } + } + } + + this.events.splice(insertIndex, 0, event); // insert element + } + /** * Remove an event from the timeline * diff --git a/src/models/thread.ts b/src/models/thread.ts index c31499b2b34..fb7d84d98c1 100644 --- a/src/models/thread.ts +++ b/src/models/thread.ts @@ -236,6 +236,31 @@ export class Thread extends ReadReceipt { } } + /** + * TEMPORARY. Only call this when MSC3981 is not available, and we have some + * late-arriving events to insert, because we recursively found them as part + * of populating a thread. When we have MSC3981 we won't need it, because + * they will all be supplied by the homeserver in one request, and they will + * already be in the right order in that response. + * This is a copy of addEventToTimeline above, modified to call + * insertEventIntoTimeline so this event is inserted into our best guess of + * the right place based on timestamp. (We should be using Sync Order but we + * don't have it.) + */ + public insertEventIntoTimeline(event: MatrixEvent): void { + const eventId = event.getId(); + if (!eventId) { + return; + } + if (this.findEventById(eventId)) { + return; + } + this.timelineSet.insertEventIntoTimeline(event, this.liveTimeline, this.roomState); + + // As far as we know, timeline should always be the same as events + this.timeline = this.events; + } + public addEvents(events: MatrixEvent[], toStartOfTimeline: boolean): void { events.forEach((ev) => this.addEvent(ev, toStartOfTimeline, false)); this.updateThreadMetadata(); @@ -281,7 +306,8 @@ export class Thread extends ReadReceipt { */ this.replayEvents?.push(event); } else { - this.addEventToTimeline(event, toStartOfTimeline); + // TODO: check with Germain is this right? + this.insertEventIntoTimeline(event); } // Apply annotations and replace relations to the relations of the timeline only this.timelineSet.relations?.aggregateParentEvent(event); @@ -473,6 +499,8 @@ export class Thread extends ReadReceipt { .then((relations) => { if (relations.events.length) { event.makeReplaced(relations.events[0]); + // TODO: check with Germain: is this right? + this.insertEventIntoTimeline(event); } }) .catch((e) => {