diff --git a/src/components/structures/MessagePanel.tsx b/src/components/structures/MessagePanel.tsx index 0d4af1d06c..a9493da532 100644 --- a/src/components/structures/MessagePanel.tsx +++ b/src/components/structures/MessagePanel.tsx @@ -467,6 +467,16 @@ export default class MessagePanel extends React.Component { // TODO: Implement granular (per-room) hide options public shouldShowEvent(mxEv: MatrixEvent, forceHideEvents = false): boolean { + if (this.props.hideThreadedMessages && SettingsStore.getValue("feature_thread")) { + if (mxEv.isThreadRelation) { + return false; + } + + if (this.shouldLiveInThreadOnly(mxEv)) { + return false; + } + } + if (MatrixClientPeg.get().isUserIgnored(mxEv.getSender())) { return false; // ignored = no show (only happens if the ignore happens after an event was received) } @@ -482,17 +492,25 @@ export default class MessagePanel extends React.Component { // Always show highlighted event if (this.props.highlightedEventId === mxEv.getId()) return true; - // Checking if the message has a "parentEventId" as we do not - // want to hide the root event of the thread - if (mxEv.isThreadRelation && - !mxEv.isThreadRoot && - this.props.hideThreadedMessages && - SettingsStore.getValue("feature_thread") - ) { + return !shouldHideEvent(mxEv, this.context); + } + + private shouldLiveInThreadOnly(event: MatrixEvent): boolean { + const associatedId = event.getAssociatedId(); + + const targetsThreadRoot = event.threadRootId === associatedId; + if (event.isThreadRoot || targetsThreadRoot || !event.isThreadRelation) { return false; } - return !shouldHideEvent(mxEv, this.context); + // If this is a reply, then we use the associated event to decide whether + // this should be thread only or not + const parentEvent = this.props.room.findEventById(associatedId); + if (parentEvent) { + return this.shouldLiveInThreadOnly(parentEvent); + } else { + return true; + } } public readMarkerForEvent(eventId: string, isLastEvent: boolean): ReactNode { diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 1c230eed7a..3213d28688 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -209,6 +209,7 @@ export interface IRoomState { wasContextSwitch?: boolean; editState?: EditorStateTransfer; timelineRenderingType: TimelineRenderingType; + threadId?: string; liveTimeline?: EventTimeline; } diff --git a/src/components/structures/ThreadView.tsx b/src/components/structures/ThreadView.tsx index b10e5a923d..cf8db378be 100644 --- a/src/components/structures/ThreadView.tsx +++ b/src/components/structures/ThreadView.tsx @@ -264,6 +264,7 @@ export default class ThreadView extends React.Component { diff --git a/src/components/structures/TimelinePanel.tsx b/src/components/structures/TimelinePanel.tsx index d5103424ab..9a822ef18d 100644 --- a/src/components/structures/TimelinePanel.tsx +++ b/src/components/structures/TimelinePanel.tsx @@ -1285,11 +1285,30 @@ class TimelinePanel extends React.Component { // should use this list, so that they don't advance into pending events. const liveEvents = [...events]; - const thread = events[0]?.getThread(); - // if we're at the end of the live timeline, append the pending events if (!this.timelineWindow.canPaginate(EventTimeline.FORWARDS)) { - events.push(...this.props.timelineSet.getPendingEvents(thread)); + const pendingEvents = this.props.timelineSet.getPendingEvents(); + if (this.context.timelineRenderingType === TimelineRenderingType.Thread) { + events.push(...pendingEvents.filter(e => e.threadRootId === this.context.threadId)); + } else { + events.push(...pendingEvents.filter(e => { + const hasNoRelation = !e.getRelation(); + if (hasNoRelation) { + return true; + } + + if (e.isThreadRelation) { + return false; + } + + const parentEvent = this.props.timelineSet.findEventById(e.getAssociatedId()); + if (!parentEvent) { + return false; + } else { + return !parentEvent.isThreadRelation; + } + })); + } } return { diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 9a3b1ed86d..565cee99f3 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -649,13 +649,13 @@ export default class EventTile extends React.Component { } private renderThreadPanelSummary(): JSX.Element | null { - if (!this.thread) { + if (!this.state.thread) { return null; } return
- { this.thread.length } + { this.state.thread.length } { this.renderThreadLastMessagePreview() }
; @@ -665,7 +665,7 @@ export default class EventTile extends React.Component { const { threadLastReply } = this.state; const threadMessagePreview = MessagePreviewStore.instance.generatePreviewForEvent(threadLastReply); - const sender = this.thread.roomState.getSentinelMember(threadLastReply.getSender()); + const sender = this.state.thread?.roomState.getSentinelMember(threadLastReply.getSender()); return <> { return (

{ _t("From a thread") }

); - } else if (this.state.threadReplyCount) { + } else if (this.state.threadReplyCount && this.props.mxEvent.isThreadRoot) { return ( { context => @@ -1092,11 +1092,10 @@ export default class EventTile extends React.Component { return this.props.getRelationsForEvent(eventId, "m.annotation", "m.reaction"); }; - private onReactionsCreated = (relationType: string, eventType: string) => { + private onReactionsCreated = (relationType: string, eventType: string): void => { if (relationType !== "m.annotation" || eventType !== "m.reaction") { return; } - this.props.mxEvent.removeListener("Event.relationsCreated", this.onReactionsCreated); this.setState({ reactions: this.getReactions(), }); diff --git a/src/contexts/RoomContext.ts b/src/contexts/RoomContext.ts index 0412355f2f..7e838d1ac9 100644 --- a/src/contexts/RoomContext.ts +++ b/src/contexts/RoomContext.ts @@ -63,6 +63,7 @@ const RoomContext = createContext({ matrixClientIsReady: false, dragCounter: 0, timelineRenderingType: TimelineRenderingType.Room, + threadId: undefined, liveTimeline: undefined, }); RoomContext.displayName = "RoomContext";