diff --git a/src/components/structures/ThreadView.tsx b/src/components/structures/ThreadView.tsx index 71efae0c6a..12ed56f84b 100644 --- a/src/components/structures/ThreadView.tsx +++ b/src/components/structures/ThreadView.tsx @@ -55,6 +55,7 @@ interface IProps { } interface IState { thread?: Thread; + lastThreadReply?: MatrixEvent; layout: Layout; editState?: EditorStateTransfer; replyToEvent?: MatrixEvent; @@ -142,14 +143,14 @@ export default class ThreadView extends React.Component { if (!thread) { thread = this.props.room.createThread([mxEv]); } - thread.on(ThreadEvent.Update, this.updateThread); + thread.on(ThreadEvent.Update, this.updateLastThreadReply); thread.once(ThreadEvent.Ready, this.updateThread); this.updateThread(thread); }; private teardownThread = () => { if (this.state.thread) { - this.state.thread.removeListener(ThreadEvent.Update, this.updateThread); + this.state.thread.removeListener(ThreadEvent.Update, this.updateLastThreadReply); this.state.thread.removeListener(ThreadEvent.Ready, this.updateThread); } }; @@ -165,6 +166,7 @@ export default class ThreadView extends React.Component { if (thread && this.state.thread !== thread) { this.setState({ thread, + lastThreadReply: thread.lastReply, }, () => { thread.emit(ThreadEvent.ViewThread); this.timelinePanelRef.current?.refreshTimeline(); @@ -172,6 +174,14 @@ export default class ThreadView extends React.Component { } }; + private updateLastThreadReply = () => { + if (this.state.thread) { + this.setState({ + lastThreadReply: this.state.thread.lastReply, + }); + } + }; + private onScroll = (): void => { if (this.props.initialEvent && this.props.isInitialEventHighlighted) { dis.dispatch({ @@ -199,8 +209,11 @@ export default class ThreadView extends React.Component { : null; const threadRelation: IEventRelation = { - rel_type: RelationType.Thread, - event_id: this.state.thread?.id, + "rel_type": RelationType.Thread, + "event_id": this.state.thread?.id, + "m.in_reply_to": { + "event_id": this.state.lastThreadReply?.getId(), + }, }; const messagePanelClassNames = classNames( diff --git a/src/components/views/elements/ReplyChain.tsx b/src/components/views/elements/ReplyChain.tsx index 00221f4585..680407dc0e 100644 --- a/src/components/views/elements/ReplyChain.tsx +++ b/src/components/views/elements/ReplyChain.tsx @@ -17,7 +17,7 @@ limitations under the License. import React from 'react'; import classNames from 'classnames'; -import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; +import { IEventRelation, MatrixEvent } from 'matrix-js-sdk/src/models/event'; import escapeHtml from "escape-html"; import sanitizeHtml from "sanitize-html"; import { Room } from 'matrix-js-sdk/src/models/room'; @@ -99,22 +99,8 @@ export default class ReplyChain extends React.Component { public static getParentEventId(ev: MatrixEvent): string | undefined { if (!ev || ev.isRedacted()) return; - - // XXX: For newer relations (annotations, replacements, etc.), we now - // have a `getRelation` helper on the event, and you might assume it - // could be used here for replies as well... However, the helper - // currently assumes the relation has a `rel_type`, which older replies - // do not, so this block is left as-is for now. - // - // We're prefer ev.getContent() over ev.getWireContent() to make sure - // we grab the latest edit with potentially new relations. But we also - // can't just rely on ev.getContent() by itself because historically we - // still show the reply from the original message even though the edit - // event does not include the relation reply. - const mRelatesTo = ev.getContent()['m.relates_to'] || ev.getWireContent()['m.relates_to']; - if (mRelatesTo && mRelatesTo['m.in_reply_to']) { - const mInReplyTo = mRelatesTo['m.in_reply_to']; - if (mInReplyTo && mInReplyTo['event_id']) return mInReplyTo['event_id']; + if (ev.replyEventId) { + return ev.replyEventId; } else if (!SettingsStore.getValue("feature_thread") && ev.isThreadRelation) { return ev.threadRootId; } @@ -232,7 +218,7 @@ export default class ReplyChain extends React.Component { return { body, html }; } - public static makeReplyMixIn(ev: MatrixEvent) { + public static makeReplyMixIn(ev: MatrixEvent, renderIn?: string[]) { if (!ev) return {}; const mixin: any = { @@ -243,6 +229,10 @@ export default class ReplyChain extends React.Component { }, }; + if (renderIn) { + mixin['m.relates_to']['m.in_reply_to']['m.render_in'] = renderIn; + } + /** * If the event replied is part of a thread * Add the `m.thread` relation so that clients @@ -260,8 +250,21 @@ export default class ReplyChain extends React.Component { return mixin; } - public static hasReply(event: MatrixEvent) { - return Boolean(ReplyChain.getParentEventId(event)); + public static shouldDisplayReply(event: MatrixEvent, renderTarget?: string): boolean { + const parentExist = Boolean(ReplyChain.getParentEventId(event)); + + const relations = event.getRelation(); + const renderIn = relations?.["m.in_reply_to"]?.["m.render_in"] ?? []; + + const shouldRenderInTarget = !renderTarget || (renderIn.includes(renderTarget)); + + return parentExist && shouldRenderInTarget; + } + + public static getRenderInMixin(relation?: IEventRelation): string[] | undefined { + if (relation?.rel_type === RelationType.Thread) { + return [RelationType.Thread]; + } } componentDidMount() { diff --git a/src/components/views/messages/MessageActionBar.tsx b/src/components/views/messages/MessageActionBar.tsx index 6213e3b050..7387e0cc7b 100644 --- a/src/components/views/messages/MessageActionBar.tsx +++ b/src/components/views/messages/MessageActionBar.tsx @@ -382,7 +382,7 @@ export default class MessageActionBar extends React.PureComponent { msgOption = readAvatars; } - const replyChain = haveTileForEvent(this.props.mxEvent) && ReplyChain.hasReply(this.props.mxEvent) + const renderTarget = this.props.tileShape === TileShape.Thread + ? RelationType.Thread + : undefined; + + const replyChain = haveTileForEvent(this.props.mxEvent) + && ReplyChain.shouldDisplayReply(this.props.mxEvent, renderTarget) ? ) { super(props); if (this.props.mxClient.isCryptoEnabled() && this.props.mxClient.isRoomEncrypted(this.props.room.roomId)) { @@ -350,10 +370,14 @@ export class SendMessageComposer extends React.Component', () => { rel_type: RelationType.Thread, event_id: "myFakeThreadId", }} + includeReplyLegacyFallback={false} /> );