diff --git a/src/components/structures/ThreadView.tsx b/src/components/structures/ThreadView.tsx index a2595debc8..94f3f26261 100644 --- a/src/components/structures/ThreadView.tsx +++ b/src/components/structures/ThreadView.tsx @@ -136,6 +136,7 @@ export default class ThreadView extends React.Component { { return { body, html }; } - public static makeReplyMixIn(ev: MatrixEvent) { + public static makeReplyMixIn(ev: MatrixEvent, replyInThread: boolean) { if (!ev) return {}; - return { + + const replyMixin = { 'm.relates_to': { 'm.in_reply_to': { 'event_id': ev.getId(), }, }, }; + + /** + * @experimental + * Rendering hint for threads, only attached if true to make + * sure that Element does not start sending that property for all events + */ + if (replyInThread) { + const inReplyTo = replyMixin['m.relates_to']['m.in_reply_to']; + inReplyTo[UNSTABLE_ELEMENT_REPLY_IN_THREAD.name] = replyInThread; + } + + return replyMixin; } public static makeThread( diff --git a/src/components/views/rooms/EditMessageComposer.tsx b/src/components/views/rooms/EditMessageComposer.tsx index b7e067ee93..7a3767deb7 100644 --- a/src/components/views/rooms/EditMessageComposer.tsx +++ b/src/components/views/rooms/EditMessageComposer.tsx @@ -43,11 +43,6 @@ import QuestionDialog from "../dialogs/QuestionDialog"; import { ActionPayload } from "../../../dispatcher/payloads"; import AccessibleButton from '../elements/AccessibleButton'; -function eventIsReply(mxEvent: MatrixEvent): boolean { - const relatesTo = mxEvent.getContent()["m.relates_to"]; - return !!(relatesTo && relatesTo["m.in_reply_to"]); -} - function getHtmlReplyFallback(mxEvent: MatrixEvent): string { const html = mxEvent.getContent().formatted_body; if (!html) { @@ -72,7 +67,7 @@ function createEditContent(model: EditorModel, editedEvent: MatrixEvent): IConte if (isEmote) { model = stripEmoteCommand(model); } - const isReply = eventIsReply(editedEvent); + const isReply = !!editedEvent.replyEventId; let plainPrefix = ""; let htmlPrefix = ""; diff --git a/src/components/views/rooms/MessageComposer.tsx b/src/components/views/rooms/MessageComposer.tsx index fbf3b58570..466675ac64 100644 --- a/src/components/views/rooms/MessageComposer.tsx +++ b/src/components/views/rooms/MessageComposer.tsx @@ -183,6 +183,7 @@ interface IProps { resizeNotifier: ResizeNotifier; permalinkCreator: RoomPermalinkCreator; replyToEvent?: MatrixEvent; + replyInThread?: boolean; showReplyPreview?: boolean; e2eStatus?: E2EStatus; compact?: boolean; @@ -204,6 +205,7 @@ export default class MessageComposer extends React.Component { private voiceRecordingButton: VoiceRecordComposerTile; static defaultProps = { + replyInThread: false, showReplyPreview: true, compact: false, }; @@ -383,6 +385,7 @@ export default class MessageComposer extends React.Component { room={this.props.room} placeholder={this.renderPlaceholderText()} permalinkCreator={this.props.permalinkCreator} + replyInThread={this.props.replyInThread} replyToEvent={this.props.replyToEvent} onChange={this.onChange} disabled={this.state.haveRecording} diff --git a/src/components/views/rooms/SendMessageComposer.tsx b/src/components/views/rooms/SendMessageComposer.tsx index 205320fb68..aca397b6b2 100644 --- a/src/components/views/rooms/SendMessageComposer.tsx +++ b/src/components/views/rooms/SendMessageComposer.tsx @@ -57,15 +57,16 @@ import { ActionPayload } from "../../../dispatcher/payloads"; function addReplyToMessageContent( content: IContent, - repliedToEvent: MatrixEvent, + replyToEvent: MatrixEvent, + replyInThread: boolean, permalinkCreator: RoomPermalinkCreator, ): void { - const replyContent = ReplyThread.makeReplyMixIn(repliedToEvent); + const replyContent = ReplyThread.makeReplyMixIn(replyToEvent, replyInThread); Object.assign(content, replyContent); // Part of Replies fallback support - prepend the text we're sending // with the text we're replying to - const nestedReply = ReplyThread.getNestedReplyText(repliedToEvent, permalinkCreator); + const nestedReply = ReplyThread.getNestedReplyText(replyToEvent, permalinkCreator); if (nestedReply) { if (content.formatted_body) { content.formatted_body = nestedReply.html + content.formatted_body; @@ -77,8 +78,9 @@ function addReplyToMessageContent( // exported for tests export function createMessageContent( model: EditorModel, - permalinkCreator: RoomPermalinkCreator, replyToEvent: MatrixEvent, + replyInThread: boolean, + permalinkCreator: RoomPermalinkCreator, ): IContent { const isEmote = containsEmote(model); if (isEmote) { @@ -101,7 +103,7 @@ export function createMessageContent( } if (replyToEvent) { - addReplyToMessageContent(content, replyToEvent, permalinkCreator); + addReplyToMessageContent(content, replyToEvent, replyInThread, permalinkCreator); } return content; @@ -129,6 +131,7 @@ interface IProps { room: Room; placeholder?: string; permalinkCreator: RoomPermalinkCreator; + replyInThread?: boolean; replyToEvent?: MatrixEvent; disabled?: boolean; onChange?(model: EditorModel): void; @@ -357,7 +360,12 @@ export default class SendMessageComposer extends React.Component { if (cmd.category === CommandCategories.messages) { content = await this.runSlashCommand(cmd, args); if (replyToEvent) { - addReplyToMessageContent(content, replyToEvent, this.props.permalinkCreator); + addReplyToMessageContent( + content, + replyToEvent, + this.props.replyInThread, + this.props.permalinkCreator, + ); } } else { this.runSlashCommand(cmd, args); @@ -400,7 +408,12 @@ export default class SendMessageComposer extends React.Component { const startTime = CountlyAnalytics.getTimestamp(); const { roomId } = this.props.room; if (!content) { - content = createMessageContent(this.model, this.props.permalinkCreator, replyToEvent); + content = createMessageContent( + this.model, + replyToEvent, + this.props.replyInThread, + this.props.permalinkCreator, + ); } // don't bother sending an empty message if (!content.body.trim()) return; diff --git a/test/components/views/rooms/SendMessageComposer-test.js b/test/components/views/rooms/SendMessageComposer-test.js index 0c4bde76a8..db5b55df90 100644 --- a/test/components/views/rooms/SendMessageComposer-test.js +++ b/test/components/views/rooms/SendMessageComposer-test.js @@ -46,7 +46,7 @@ describe('', () => { const model = new EditorModel([], createPartCreator(), createRenderer()); model.update("hello world", "insertText", { offset: 11, atNodeEnd: true }); - const content = createMessageContent(model, permalinkCreator); + const content = createMessageContent(model, null, false, permalinkCreator); expect(content).toEqual({ body: "hello world", @@ -58,7 +58,7 @@ describe('', () => { const model = new EditorModel([], createPartCreator(), createRenderer()); model.update("hello *world*", "insertText", { offset: 13, atNodeEnd: true }); - const content = createMessageContent(model, permalinkCreator); + const content = createMessageContent(model, null, false, permalinkCreator); expect(content).toEqual({ body: "hello *world*", @@ -72,7 +72,7 @@ describe('', () => { const model = new EditorModel([], createPartCreator(), createRenderer()); model.update("/me blinks __quickly__", "insertText", { offset: 22, atNodeEnd: true }); - const content = createMessageContent(model, permalinkCreator); + const content = createMessageContent(model, null, false, permalinkCreator); expect(content).toEqual({ body: "blinks __quickly__", @@ -86,7 +86,7 @@ describe('', () => { const model = new EditorModel([], createPartCreator(), createRenderer()); model.update("//dev/null is my favourite place", "insertText", { offset: 32, atNodeEnd: true }); - const content = createMessageContent(model, permalinkCreator); + const content = createMessageContent(model, null, false, permalinkCreator); expect(content).toEqual({ body: "/dev/null is my favourite place",