Make composer able to reply in thread or in room timeline

This commit is contained in:
Germain Souquet 2021-09-01 12:12:40 +01:00
parent dca268e67a
commit 95d1b06abb
5 changed files with 31 additions and 12 deletions

View file

@ -136,6 +136,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
<MessageComposer <MessageComposer
room={this.props.room} room={this.props.room}
resizeNotifier={this.props.resizeNotifier} resizeNotifier={this.props.resizeNotifier}
replyInThread={true}
replyToEvent={this.state?.thread?.replyToEvent} replyToEvent={this.state?.thread?.replyToEvent}
showReplyPreview={false} showReplyPreview={false}
permalinkCreator={this.props.permalinkCreator} permalinkCreator={this.props.permalinkCreator}

View file

@ -19,6 +19,7 @@ import React from 'react';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import dis from '../../../dispatcher/dispatcher'; import dis from '../../../dispatcher/dispatcher';
import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
import { UNSTABLE_ELEMENT_REPLY_IN_THREAD } from "matrix-js-sdk/src/@types/event";
import { makeUserPermalink, RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"; import { makeUserPermalink, RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import { Layout } from "../../../settings/Layout"; import { Layout } from "../../../settings/Layout";
@ -206,12 +207,13 @@ export default class ReplyThread extends React.Component<IProps, IState> {
return { body, html }; return { body, html };
} }
public static makeReplyMixIn(ev: MatrixEvent) { public static makeReplyMixIn(ev: MatrixEvent, replyInThread: boolean) {
if (!ev) return {}; if (!ev) return {};
return { return {
'm.relates_to': { 'm.relates_to': {
'm.in_reply_to': { 'm.in_reply_to': {
'event_id': ev.getId(), 'event_id': ev.getId(),
[UNSTABLE_ELEMENT_REPLY_IN_THREAD.name]: replyInThread,
}, },
}, },
}; };

View file

@ -183,6 +183,7 @@ interface IProps {
resizeNotifier: ResizeNotifier; resizeNotifier: ResizeNotifier;
permalinkCreator: RoomPermalinkCreator; permalinkCreator: RoomPermalinkCreator;
replyToEvent?: MatrixEvent; replyToEvent?: MatrixEvent;
replyInThread?: boolean;
showReplyPreview?: boolean; showReplyPreview?: boolean;
e2eStatus?: E2EStatus; e2eStatus?: E2EStatus;
compact?: boolean; compact?: boolean;
@ -204,6 +205,7 @@ export default class MessageComposer extends React.Component<IProps, IState> {
private voiceRecordingButton: VoiceRecordComposerTile; private voiceRecordingButton: VoiceRecordComposerTile;
static defaultProps = { static defaultProps = {
replyInThread: false,
showReplyPreview: true, showReplyPreview: true,
compact: false, compact: false,
}; };
@ -383,6 +385,7 @@ export default class MessageComposer extends React.Component<IProps, IState> {
room={this.props.room} room={this.props.room}
placeholder={this.renderPlaceholderText()} placeholder={this.renderPlaceholderText()}
permalinkCreator={this.props.permalinkCreator} permalinkCreator={this.props.permalinkCreator}
replyInThread={this.props.replyInThread}
replyToEvent={this.props.replyToEvent} replyToEvent={this.props.replyToEvent}
onChange={this.onChange} onChange={this.onChange}
disabled={this.state.haveRecording} disabled={this.state.haveRecording}

View file

@ -57,15 +57,16 @@ import { ActionPayload } from "../../../dispatcher/payloads";
function addReplyToMessageContent( function addReplyToMessageContent(
content: IContent, content: IContent,
repliedToEvent: MatrixEvent, replyToEvent: MatrixEvent,
replyInThread: boolean,
permalinkCreator: RoomPermalinkCreator, permalinkCreator: RoomPermalinkCreator,
): void { ): void {
const replyContent = ReplyThread.makeReplyMixIn(repliedToEvent); const replyContent = ReplyThread.makeReplyMixIn(replyToEvent, replyInThread);
Object.assign(content, replyContent); Object.assign(content, replyContent);
// Part of Replies fallback support - prepend the text we're sending // Part of Replies fallback support - prepend the text we're sending
// with the text we're replying to // with the text we're replying to
const nestedReply = ReplyThread.getNestedReplyText(repliedToEvent, permalinkCreator); const nestedReply = ReplyThread.getNestedReplyText(replyToEvent, permalinkCreator);
if (nestedReply) { if (nestedReply) {
if (content.formatted_body) { if (content.formatted_body) {
content.formatted_body = nestedReply.html + content.formatted_body; content.formatted_body = nestedReply.html + content.formatted_body;
@ -77,8 +78,9 @@ function addReplyToMessageContent(
// exported for tests // exported for tests
export function createMessageContent( export function createMessageContent(
model: EditorModel, model: EditorModel,
permalinkCreator: RoomPermalinkCreator,
replyToEvent: MatrixEvent, replyToEvent: MatrixEvent,
replyInThread: boolean,
permalinkCreator: RoomPermalinkCreator,
): IContent { ): IContent {
const isEmote = containsEmote(model); const isEmote = containsEmote(model);
if (isEmote) { if (isEmote) {
@ -101,7 +103,7 @@ export function createMessageContent(
} }
if (replyToEvent) { if (replyToEvent) {
addReplyToMessageContent(content, replyToEvent, permalinkCreator); addReplyToMessageContent(content, replyToEvent, replyInThread, permalinkCreator);
} }
return content; return content;
@ -129,6 +131,7 @@ interface IProps {
room: Room; room: Room;
placeholder?: string; placeholder?: string;
permalinkCreator: RoomPermalinkCreator; permalinkCreator: RoomPermalinkCreator;
replyInThread?: boolean;
replyToEvent?: MatrixEvent; replyToEvent?: MatrixEvent;
disabled?: boolean; disabled?: boolean;
onChange?(model: EditorModel): void; onChange?(model: EditorModel): void;
@ -357,7 +360,12 @@ export default class SendMessageComposer extends React.Component<IProps> {
if (cmd.category === CommandCategories.messages) { if (cmd.category === CommandCategories.messages) {
content = await this.runSlashCommand(cmd, args); content = await this.runSlashCommand(cmd, args);
if (replyToEvent) { if (replyToEvent) {
addReplyToMessageContent(content, replyToEvent, this.props.permalinkCreator); addReplyToMessageContent(
content,
replyToEvent,
this.props.replyInThread,
this.props.permalinkCreator,
);
} }
} else { } else {
this.runSlashCommand(cmd, args); this.runSlashCommand(cmd, args);
@ -400,7 +408,12 @@ export default class SendMessageComposer extends React.Component<IProps> {
const startTime = CountlyAnalytics.getTimestamp(); const startTime = CountlyAnalytics.getTimestamp();
const { roomId } = this.props.room; const { roomId } = this.props.room;
if (!content) { 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 // don't bother sending an empty message
if (!content.body.trim()) return; if (!content.body.trim()) return;

View file

@ -46,7 +46,7 @@ describe('<SendMessageComposer/>', () => {
const model = new EditorModel([], createPartCreator(), createRenderer()); const model = new EditorModel([], createPartCreator(), createRenderer());
model.update("hello world", "insertText", { offset: 11, atNodeEnd: true }); model.update("hello world", "insertText", { offset: 11, atNodeEnd: true });
const content = createMessageContent(model, permalinkCreator); const content = createMessageContent(model, null, false, permalinkCreator);
expect(content).toEqual({ expect(content).toEqual({
body: "hello world", body: "hello world",
@ -58,7 +58,7 @@ describe('<SendMessageComposer/>', () => {
const model = new EditorModel([], createPartCreator(), createRenderer()); const model = new EditorModel([], createPartCreator(), createRenderer());
model.update("hello *world*", "insertText", { offset: 13, atNodeEnd: true }); model.update("hello *world*", "insertText", { offset: 13, atNodeEnd: true });
const content = createMessageContent(model, permalinkCreator); const content = createMessageContent(model, null, false, permalinkCreator);
expect(content).toEqual({ expect(content).toEqual({
body: "hello *world*", body: "hello *world*",
@ -72,7 +72,7 @@ describe('<SendMessageComposer/>', () => {
const model = new EditorModel([], createPartCreator(), createRenderer()); const model = new EditorModel([], createPartCreator(), createRenderer());
model.update("/me blinks __quickly__", "insertText", { offset: 22, atNodeEnd: true }); 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({ expect(content).toEqual({
body: "blinks __quickly__", body: "blinks __quickly__",
@ -86,7 +86,7 @@ describe('<SendMessageComposer/>', () => {
const model = new EditorModel([], createPartCreator(), createRenderer()); const model = new EditorModel([], createPartCreator(), createRenderer());
model.update("//dev/null is my favourite place", "insertText", { offset: 32, atNodeEnd: true }); 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({ expect(content).toEqual({
body: "/dev/null is my favourite place", body: "/dev/null is my favourite place",