diff --git a/src/app/hooks/timeline/useProcessedTimeline.ts b/src/app/hooks/timeline/useProcessedTimeline.ts index 59e99cd58..45545867e 100644 --- a/src/app/hooks/timeline/useProcessedTimeline.ts +++ b/src/app/hooks/timeline/useProcessedTimeline.ts @@ -148,22 +148,28 @@ const computeCollapseAndDividers = ( const mergeDraftsAndExtras = ( result: ProcessedEvent[], - extras: { mEvent: MatrixEvent; timelineSet: EventTimelineSet; parentTs: number }[] + extras: { + mEvent: MatrixEvent; + timelineSet: EventTimelineSet; + parentId: string; + itemIndex?: number; + }[] ): ProcessedEventDraft[] => { const resultDrafts = result.map( ({ collapsed: _c, willRenderNewDivider: _n, willRenderDayDivider: _d, ...draft }) => draft ); const extraDrafts = extras - .map(({ mEvent, timelineSet, parentTs }) => ({ + .map(({ mEvent, timelineSet, parentId, itemIndex = -1 }) => ({ draft: { id: mEvent.getId()!, - itemIndex: -1, + itemIndex, mEvent, timelineSet, eventSender: mEvent.getSender() ?? null, }, - effectiveTs: Math.max(mEvent.getTs(), parentTs), + effectiveTs: mEvent.getTs(), + parentId, })) .toSorted((a, b) => a.effectiveTs - b.effectiveTs); @@ -174,13 +180,21 @@ const mergeDraftsAndExtras = ( for (const extra of extraDrafts) { const extraTs = extra.effectiveTs; - let insertIdx = 0; - for (let i = resultDrafts.length - 1; i >= 0; i -= 1) { - if (resultDrafts[i]!.mEvent.getTs() <= extraTs) { - insertIdx = i + 1; + let parentIdx = -1; + for (let i = 0; i < resultDrafts.length; i += 1) { + if (resultDrafts[i]!.id === extra.parentId) { + parentIdx = i; break; } } + + let insertIdx = parentIdx + 1; + for (let i = parentIdx + 1; i < resultDrafts.length; i += 1) { + if (resultDrafts[i]!.mEvent.getTs() > extraTs) { + break; + } + insertIdx = i + 1; + } buckets[insertIdx]!.push(extra.draft); } @@ -207,6 +221,31 @@ const mergeRelationReactions = ( if (hideMemberInReadOnly && isReadOnly) return result; const existingIds = new Set(result.map((event) => event.id)); + const baseDrafts: ProcessedEvent[] = []; + const inlineExtras: { + mEvent: MatrixEvent; + timelineSet: EventTimelineSet; + parentId: string; + itemIndex: number; + }[] = []; + + for (const draft of result) { + if (isReactionEvent(draft.mEvent)) { + const relation = draft.mEvent.getRelation(); + const parentId = relation?.event_id; + if (parentId) { + inlineExtras.push({ + mEvent: draft.mEvent, + timelineSet: draft.timelineSet, + parentId, + itemIndex: draft.itemIndex, + }); + continue; + } + } + baseDrafts.push(draft); + } + const extras = collectRelationReactionEvents( linkedTimelines, existingIds, @@ -215,9 +254,10 @@ const mergeRelationReactions = ( hiddenEventReactionTombstone ); - if (extras.length === 0) return result; + const allExtras = [...inlineExtras, ...extras]; + if (allExtras.length === 0) return baseDrafts; - const mergedDrafts = mergeDraftsAndExtras(result, extras); + const mergedDrafts = mergeDraftsAndExtras(baseDrafts, allExtras); return computeCollapseAndDividers(mergedDrafts, mxUserId, readUptoEventId); }; @@ -231,6 +271,31 @@ const mergeRelationEdits = ( readUptoEventId: string | undefined ): ProcessedEvent[] => { const existingIds = new Set(result.map((event) => event.id)); + const baseDrafts: ProcessedEvent[] = []; + const inlineExtras: { + mEvent: MatrixEvent; + timelineSet: EventTimelineSet; + parentId: string; + itemIndex: number; + }[] = []; + + for (const draft of result) { + if (isEditEvent(draft.mEvent)) { + const relation = draft.mEvent.getRelation(); + const parentId = relation?.event_id; + if (parentId) { + inlineExtras.push({ + mEvent: draft.mEvent, + timelineSet: draft.timelineSet, + parentId, + itemIndex: draft.itemIndex, + }); + continue; + } + } + baseDrafts.push(draft); + } + const extras = collectRelationEditEvents( linkedTimelines, existingIds, @@ -238,9 +303,10 @@ const mergeRelationEdits = ( hiddenEventEdits ); - if (extras.length === 0) return result; + const allExtras = [...inlineExtras, ...extras]; + if (allExtras.length === 0) return baseDrafts; - const mergedDrafts = mergeDraftsAndExtras(result, extras); + const mergedDrafts = mergeDraftsAndExtras(baseDrafts, allExtras); return computeCollapseAndDividers(mergedDrafts, mxUserId, readUptoEventId); }; diff --git a/src/app/utils/room.ts b/src/app/utils/room.ts index 4e79900c1..6d201f34a 100644 --- a/src/app/utils/room.ts +++ b/src/app/utils/room.ts @@ -758,10 +758,10 @@ export const collectRelationReactionEvents = ( ignoredUsersSet: Set, showReactions: boolean, showReactionTombstones: boolean -): { mEvent: MatrixEvent; timelineSet: EventTimelineSet; parentTs: number }[] => { +): { mEvent: MatrixEvent; timelineSet: EventTimelineSet; parentId: string }[] => { if (!showReactions && !showReactionTombstones) return []; - const extras: { mEvent: MatrixEvent; timelineSet: EventTimelineSet; parentTs: number }[] = []; + const extras: { mEvent: MatrixEvent; timelineSet: EventTimelineSet; parentId: string }[] = []; const seen = new Set(existingIds); for (const timeline of linkedTimelines) { @@ -785,7 +785,7 @@ export const collectRelationReactionEvents = ( if (redacted && !showReactionTombstones) continue; if (!redacted && !showReactions) continue; - extras.push({ mEvent: reaction, timelineSet, parentTs: parent.getTs() }); + extras.push({ mEvent: reaction, timelineSet, parentId }); } } } @@ -798,10 +798,10 @@ export const collectRelationEditEvents = ( existingIds: ReadonlySet, ignoredUsersSet: Set, showEdits: boolean -): { mEvent: MatrixEvent; timelineSet: EventTimelineSet; parentTs: number }[] => { +): { mEvent: MatrixEvent; timelineSet: EventTimelineSet; parentId: string }[] => { if (!showEdits) return []; - const extras: { mEvent: MatrixEvent; timelineSet: EventTimelineSet; parentTs: number }[] = []; + const extras: { mEvent: MatrixEvent; timelineSet: EventTimelineSet; parentId: string }[] = []; const seen = new Set(existingIds); for (const timeline of linkedTimelines) { @@ -821,7 +821,7 @@ export const collectRelationEditEvents = ( const sender = editEvent.getSender(); if (sender && ignoredUsersSet.has(sender)) continue; - extras.push({ mEvent: editEvent, timelineSet, parentTs: parent.getTs() }); + extras.push({ mEvent: editEvent, timelineSet, parentId }); } } }