Skip to content

Commit 54731b3

Browse files
committed
Apply edits discovered from sync after thread is initialised
1 parent b83c372 commit 54731b3

File tree

2 files changed

+95
-6
lines changed

2 files changed

+95
-6
lines changed

spec/unit/event-timeline-set.spec.ts

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@ import {
2424
MatrixClient,
2525
MatrixEvent,
2626
MatrixEventEvent,
27+
RelationType,
2728
Room,
29+
RoomEvent,
2830
} from "../../src";
29-
import { Thread } from "../../src/models/thread";
31+
import { FeatureSupport, Thread } from "../../src/models/thread";
3032
import { ReEmitter } from "../../src/ReEmitter";
3133

3234
describe("EventTimelineSet", () => {
@@ -202,6 +204,70 @@ describe("EventTimelineSet", () => {
202204
expect(liveTimeline.getEvents().length).toStrictEqual(0);
203205
});
204206

207+
it("should allow edits to be added to thread timeline", async () => {
208+
jest.spyOn(client, "supportsExperimentalThreads").mockReturnValue(true);
209+
Thread.hasServerSideSupport = FeatureSupport.Stable;
210+
211+
const sender = "@alice:matrix.org";
212+
213+
const root = utils.mkEvent({
214+
event: true,
215+
content: {
216+
body: "Thread root",
217+
},
218+
type: EventType.RoomMessage,
219+
sender,
220+
});
221+
room.addLiveEvents([root]);
222+
223+
const threadReply = utils.mkEvent({
224+
event: true,
225+
content: {
226+
"body": "Thread reply",
227+
"m.relates_to": {
228+
event_id: root.getId()!,
229+
rel_type: RelationType.Thread,
230+
},
231+
},
232+
type: EventType.RoomMessage,
233+
sender,
234+
});
235+
236+
const editToThreadReply = utils.mkEvent({
237+
event: true,
238+
content: {
239+
"body": " * edit",
240+
"m.new_content": {
241+
"body": "edit",
242+
"msgtype": "m.text",
243+
"org.matrix.msc1767.text": "edit",
244+
},
245+
"m.relates_to": {
246+
event_id: threadReply.getId()!,
247+
rel_type: RelationType.Replace,
248+
},
249+
},
250+
type: EventType.RoomMessage,
251+
sender,
252+
});
253+
254+
const thread = room.createThread(root.getId()!, root, [threadReply, editToThreadReply], false);
255+
256+
jest.spyOn(thread, "processEvent").mockResolvedValue();
257+
jest.spyOn(client, "paginateEventTimeline").mockImplementation(async () => {
258+
thread.timelineSet.getLiveTimeline().addEvent(threadReply, { toStartOfTimeline: true });
259+
return true;
260+
});
261+
jest.spyOn(client, "relations").mockResolvedValue({
262+
events: [],
263+
});
264+
265+
thread.once(RoomEvent.TimelineReset, () => {
266+
const lastEvent = thread.timeline.at(-1)!;
267+
expect(lastEvent.getContent().body).toBe(" * edit");
268+
});
269+
});
270+
205271
describe("non-room timeline", () => {
206272
it("Adds event to timeline", () => {
207273
const nonRoomEventTimelineSet = new EventTimelineSet(

src/models/thread.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
9797
private readonly pendingEventOrdering: PendingEventOrdering;
9898

9999
public initialEventsFetched = !Thread.hasServerSideSupport;
100+
/**
101+
* An array of events to add to the timeline once the thread has been initialised
102+
* with server suppport.
103+
*/
104+
public replayEvents: MatrixEvent[] | null = [];
100105

101106
public constructor(public readonly id: string, public rootEvent: MatrixEvent | undefined, opts: IThreadOpts) {
102107
super();
@@ -136,7 +141,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
136141
this.setEventMetadata(this.rootEvent);
137142
}
138143

139-
private async fetchRootEvent(): Promise<void> {
144+
public async fetchRootEvent(): Promise<void> {
140145
this.rootEvent = this.room.findEventById(this.id);
141146
// If the rootEvent does not exist in the local stores, then fetch it from the server.
142147
try {
@@ -266,9 +271,23 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
266271
this.addEventToTimeline(event, false);
267272
this.fetchEditsWhereNeeded(event);
268273
} else if (event.isRelation(RelationType.Annotation) || event.isRelation(RelationType.Replace)) {
269-
// Apply annotations and replace relations to the relations of the timeline only
270-
this.timelineSet.relations?.aggregateParentEvent(event);
271-
this.timelineSet.relations?.aggregateChildEvent(event, this.timelineSet);
274+
if (!this.initialEventsFetched) {
275+
/**
276+
* A thread can be fully discovered via a single sync response
277+
* And when that's the case we still ask the server to do an initialisation
278+
* as it's the safest to ensure we have everything.
279+
* However when we are in that scenario we might loose annotation or edits
280+
*
281+
* This fix keeps a reference to those events and replay them once the thread
282+
* has been initialised properly.
283+
*/
284+
this.replayEvents?.push(event);
285+
} else {
286+
this.addEventToTimeline(event, toStartOfTimeline);
287+
// Apply annotations and replace relations to the relations of the timeline only
288+
this.timelineSet.relations?.aggregateParentEvent(event);
289+
this.timelineSet.relations?.aggregateChildEvent(event, this.timelineSet);
290+
}
272291
return;
273292
}
274293

@@ -316,7 +335,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
316335
return rootEvent?.getServerAggregatedRelation<IThreadBundledRelationship>(THREAD_RELATION_TYPE.name);
317336
}
318337

319-
private async processRootEvent(): Promise<void> {
338+
public async processRootEvent(): Promise<void> {
320339
const bundledRelationship = this.getRootEventBundledRelationship();
321340
if (Thread.hasServerSideSupport && bundledRelationship) {
322341
this.replyCount = bundledRelationship.count;
@@ -375,6 +394,10 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
375394
limit: Math.max(1, this.length),
376395
});
377396
}
397+
for (const event of this.replayEvents!) {
398+
this.addEvent(event, false);
399+
}
400+
this.replayEvents = null;
378401
// just to make sure that, if we've created a timeline window for this thread before the thread itself
379402
// existed (e.g. when creating a new thread), we'll make sure the panel is force refreshed correctly.
380403
this.emit(RoomEvent.TimelineReset, this.room, this.timelineSet, true);

0 commit comments

Comments
 (0)