From 02939fee0f7815fd653f82b353f5b27465383649 Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Mon, 3 Apr 2023 14:48:32 +0100 Subject: [PATCH 01/31] pass handleCommand prop down and use it in WysiwygAutocomplete --- .../components/WysiwygAutocomplete.tsx | 17 ++++++++++++++++- .../components/WysiwygComposer.tsx | 9 +++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx b/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx index 3afa409c717..0cff876aa1e 100644 --- a/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx +++ b/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx @@ -35,6 +35,12 @@ interface WysiwygAutocompleteProps { * a mention in the autocomplete list or pressing enter on a selected item */ handleMention: FormattingFunctions["mention"]; + + /** + * This handler will be called with the display text for a command on clicking + * a command in the autocomplete list or pressing enter on a selected item + */ + handleCommand: FormattingFunctions["command"]; } /** @@ -45,13 +51,22 @@ interface WysiwygAutocompleteProps { * @param props.ref - the ref will be attached to the rendered `` component */ const WysiwygAutocomplete = forwardRef( - ({ suggestion, handleMention }: WysiwygAutocompleteProps, ref: ForwardedRef): JSX.Element | null => { + ( + { suggestion, handleMention, handleCommand }: WysiwygAutocompleteProps, + ref: ForwardedRef, + ): JSX.Element | null => { const { room } = useRoomContext(); const client = useMatrixClientContext(); function handleConfirm(completion: ICompletion): void { // TODO handle all of the completion types // Using this to pick out the ones we can handle during implementation + if (completion.type === "command") { + // trim the completion text as we add the trailing space in the rust model + // nb there are utils like parseCommandString and the CommandMap in SlashCommands.tsx + // that might be required here, but for now just use the trimmed completion text + handleCommand(completion.completion.trim()); + } if (client && room && completion.href && (completion.type === "room" || completion.type === "user")) { handleMention( completion.href, diff --git a/src/components/views/rooms/wysiwyg_composer/components/WysiwygComposer.tsx b/src/components/views/rooms/wysiwyg_composer/components/WysiwygComposer.tsx index ca42ba22ec4..66c28ff4d5b 100644 --- a/src/components/views/rooms/wysiwyg_composer/components/WysiwygComposer.tsx +++ b/src/components/views/rooms/wysiwyg_composer/components/WysiwygComposer.tsx @@ -91,7 +91,7 @@ export const WysiwygComposer = memo(function WysiwygComposer({ } } - const mentions = ref.current?.querySelectorAll("a[data-mention-type]"); + const mentions: NodeList = ref.current?.querySelectorAll("a[data-mention-type]"); if (mentions) { mentions.forEach((mention) => mention.addEventListener("click", handleClick)); } @@ -108,7 +108,12 @@ export const WysiwygComposer = memo(function WysiwygComposer({ onFocus={onFocus} onBlur={onFocus} > - + Date: Mon, 3 Apr 2023 14:49:17 +0100 Subject: [PATCH 02/31] allow a command to generate a query from buildQuery --- .../views/rooms/wysiwyg_composer/utils/autocomplete.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts b/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts index d1f066a7bc4..86dd2791a71 100644 --- a/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts +++ b/src/components/views/rooms/wysiwyg_composer/utils/autocomplete.ts @@ -29,9 +29,8 @@ import * as Avatar from "../../../../../Avatar"; * with @ for a user query, # for a room or space query */ export function buildQuery(suggestion: MappedSuggestion | null): string { - if (!suggestion || !suggestion.keyChar || suggestion.type === "command") { + if (!suggestion || !suggestion.keyChar) { // if we have an empty key character, we do not build a query - // TODO implement the command functionality return ""; } From 4fe00774794677e8744a43f8a38b635c8c2a7301 Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Mon, 3 Apr 2023 15:13:32 +0100 Subject: [PATCH 03/31] port command functionality into the sendMessage util --- .../rooms/wysiwyg_composer/utils/message.ts | 60 ++++++++++++++++++- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/wysiwyg_composer/utils/message.ts b/src/components/views/rooms/wysiwyg_composer/utils/message.ts index 70b65e392e5..531616b962b 100644 --- a/src/components/views/rooms/wysiwyg_composer/utils/message.ts +++ b/src/components/views/rooms/wysiwyg_composer/utils/message.ts @@ -15,7 +15,7 @@ limitations under the License. */ import { Composer as ComposerEvent } from "@matrix-org/analytics-events/types/typescript/Composer"; -import { IEventRelation, MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { IContent, IEventRelation, MatrixEvent } from "matrix-js-sdk/src/models/event"; import { ISendEventResponse, MatrixClient } from "matrix-js-sdk/src/matrix"; import { THREAD_RELATION_TYPE } from "matrix-js-sdk/src/models/thread"; @@ -33,6 +33,10 @@ import { endEditing, cancelPreviousPendingEdit } from "./editing"; import EditorStateTransfer from "../../../../../utils/EditorStateTransfer"; import { createMessageContent } from "./createMessageContent"; import { isContentModified } from "./isContentModified"; +import { CommandCategories, getCommand } from "../../../../../SlashCommands"; +import { runSlashCommand, shouldSendAnyway } from "../../../../../editor/commands"; +import { attachRelation } from "../../SendMessageComposer"; +import { Action } from "../../../../../dispatcher/actions"; export interface SendMessageParams { mxClient: MatrixClient; @@ -48,6 +52,7 @@ export async function sendMessage( isHTML: boolean, { roomContext, mxClient, ...params }: SendMessageParams, ): Promise { + console.log(`<<< sending ${message}`); const { relation, replyToEvent } = params; const { room } = roomContext; const roomId = room?.roomId; @@ -71,7 +76,58 @@ export async function sendMessage( }*/ PosthogAnalytics.instance.trackEvent(posthogEvent); - const content = await createMessageContent(message, isHTML, params); + let shouldSend = true; + let content: IContent | null = null; + + // TODO slash command - quick and dirty to start + if (message[0] === "/") { + // then we have a slash command, let's use the existing functions as much as possible for processing + const { cmd, args } = getCommand(message); + if (cmd) { + // debugger; // we will need to handle /me separately, see + // /Users/alunturner/code/matrix-react-sdk/src/components/views/rooms/wysiwyg_composer/utils/message.tsx:87 + const threadId = relation?.rel_type === THREAD_RELATION_TYPE.name ? relation?.event_id : null; + let commandSuccessful: boolean; + [content, commandSuccessful] = await runSlashCommand(cmd, args, roomId, threadId ?? null); + + if (!commandSuccessful) { + return; // errored + } + + if (cmd.category === CommandCategories.messages || cmd.category === CommandCategories.effects) { + attachRelation(content, relation); + // if (replyToEvent) { + // addReplyToMessageContent(content, replyToEvent, { + // permalinkCreator: this.props.permalinkCreator, + // // Exclude the legacy fallback for custom event types such as those used by /fireworks + // includeLegacyFallback: content.msgtype?.startsWith("m.") ?? true, + // }); + // } + } else { + shouldSend = false; + } + } else { + const sendAnyway = await shouldSendAnyway(message); + // re-focus the composer after QuestionDialog is closed + dis.dispatch({ + action: Action.FocusAComposer, // TODO this does not refocus the wysiwyg composer + context: roomContext.timelineRenderingType, + }); + // if !sendAnyway bail to let the user edit the composer and try again + if (!sendAnyway) return; + } + } + + // early return to save nesting the whole of the next bit, perhaps this could be handled more neatly + // in the if block above? + if (!shouldSend) { + return; + } + + // we haven't done a slash command + if (!content) { + content = await createMessageContent(message, isHTML, params); + } // TODO slash comment From 6b623d072a9bd138cd3e158774afef4772c49c80 Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Mon, 3 Apr 2023 15:20:27 +0100 Subject: [PATCH 04/31] tidy up comments --- .../views/rooms/wysiwyg_composer/utils/message.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/components/views/rooms/wysiwyg_composer/utils/message.ts b/src/components/views/rooms/wysiwyg_composer/utils/message.ts index 531616b962b..d2a491c931e 100644 --- a/src/components/views/rooms/wysiwyg_composer/utils/message.ts +++ b/src/components/views/rooms/wysiwyg_composer/utils/message.ts @@ -79,13 +79,11 @@ export async function sendMessage( let shouldSend = true; let content: IContent | null = null; - // TODO slash command - quick and dirty to start - if (message[0] === "/") { - // then we have a slash command, let's use the existing functions as much as possible for processing + // Functionality here approximates what can be found in SendMessageComposer.sendMessage() + if (message.startsWith("/") && !message.startsWith("//")) { const { cmd, args } = getCommand(message); if (cmd) { - // debugger; // we will need to handle /me separately, see - // /Users/alunturner/code/matrix-react-sdk/src/components/views/rooms/wysiwyg_composer/utils/message.tsx:87 + // we will need to handle /me separately, see SlashCommands.tsx:1387 const threadId = relation?.rel_type === THREAD_RELATION_TYPE.name ? relation?.event_id : null; let commandSuccessful: boolean; [content, commandSuccessful] = await runSlashCommand(cmd, args, roomId, threadId ?? null); From e056d5dad5d48ab46b1be7581fdbb6b6b4cb5415 Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Mon, 3 Apr 2023 15:23:36 +0100 Subject: [PATCH 05/31] remove use of shouldSend and update comments --- .../views/rooms/wysiwyg_composer/utils/message.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/components/views/rooms/wysiwyg_composer/utils/message.ts b/src/components/views/rooms/wysiwyg_composer/utils/message.ts index d2a491c931e..6a0226c2792 100644 --- a/src/components/views/rooms/wysiwyg_composer/utils/message.ts +++ b/src/components/views/rooms/wysiwyg_composer/utils/message.ts @@ -76,7 +76,6 @@ export async function sendMessage( }*/ PosthogAnalytics.instance.trackEvent(posthogEvent); - let shouldSend = true; let content: IContent | null = null; // Functionality here approximates what can be found in SendMessageComposer.sendMessage() @@ -94,6 +93,7 @@ export async function sendMessage( if (cmd.category === CommandCategories.messages || cmd.category === CommandCategories.effects) { attachRelation(content, relation); + // TODO translate this block for replies // if (replyToEvent) { // addReplyToMessageContent(content, replyToEvent, { // permalinkCreator: this.props.permalinkCreator, @@ -102,7 +102,8 @@ export async function sendMessage( // }); // } } else { - shouldSend = false; + // instead of setting shouldSend to false as in SendMessageComposer, just return + return; } } else { const sendAnyway = await shouldSendAnyway(message); @@ -116,12 +117,6 @@ export async function sendMessage( } } - // early return to save nesting the whole of the next bit, perhaps this could be handled more neatly - // in the if block above? - if (!shouldSend) { - return; - } - // we haven't done a slash command if (!content) { content = await createMessageContent(message, isHTML, params); From c0668f4f254ac0fbfa6c011ae34213ee09c85151 Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Mon, 3 Apr 2023 15:34:26 +0100 Subject: [PATCH 06/31] remove console log --- src/components/views/rooms/wysiwyg_composer/utils/message.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/rooms/wysiwyg_composer/utils/message.ts b/src/components/views/rooms/wysiwyg_composer/utils/message.ts index 6a0226c2792..6b7265e772b 100644 --- a/src/components/views/rooms/wysiwyg_composer/utils/message.ts +++ b/src/components/views/rooms/wysiwyg_composer/utils/message.ts @@ -52,7 +52,6 @@ export async function sendMessage( isHTML: boolean, { roomContext, mxClient, ...params }: SendMessageParams, ): Promise { - console.log(`<<< sending ${message}`); const { relation, replyToEvent } = params; const { room } = roomContext; const roomId = room?.roomId; From 212a9988c6de6351b8792c72d7047556aa926ad8 Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Wed, 5 Apr 2023 10:32:03 +0100 Subject: [PATCH 07/31] make logic more explicit and amend comment --- src/components/views/rooms/wysiwyg_composer/utils/message.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/wysiwyg_composer/utils/message.ts b/src/components/views/rooms/wysiwyg_composer/utils/message.ts index 6b7265e772b..1e6989b3592 100644 --- a/src/components/views/rooms/wysiwyg_composer/utils/message.ts +++ b/src/components/views/rooms/wysiwyg_composer/utils/message.ts @@ -116,8 +116,8 @@ export async function sendMessage( } } - // we haven't done a slash command - if (!content) { + // if content is still null, we haven't done any slash command processing so generate some content + if (content === null) { content = await createMessageContent(message, isHTML, params); } From c65a1b74b40d0c055032a1065f7c35718a8c0ad7 Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Wed, 5 Apr 2023 10:40:38 +0100 Subject: [PATCH 08/31] uncomment replyToEvent block --- .../rooms/wysiwyg_composer/utils/message.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/views/rooms/wysiwyg_composer/utils/message.ts b/src/components/views/rooms/wysiwyg_composer/utils/message.ts index 1e6989b3592..5ad23a99dbc 100644 --- a/src/components/views/rooms/wysiwyg_composer/utils/message.ts +++ b/src/components/views/rooms/wysiwyg_composer/utils/message.ts @@ -37,6 +37,7 @@ import { CommandCategories, getCommand } from "../../../../../SlashCommands"; import { runSlashCommand, shouldSendAnyway } from "../../../../../editor/commands"; import { attachRelation } from "../../SendMessageComposer"; import { Action } from "../../../../../dispatcher/actions"; +import { addReplyToMessageContent } from "../../../../../utils/Reply"; export interface SendMessageParams { mxClient: MatrixClient; @@ -52,7 +53,7 @@ export async function sendMessage( isHTML: boolean, { roomContext, mxClient, ...params }: SendMessageParams, ): Promise { - const { relation, replyToEvent } = params; + const { relation, replyToEvent, permalinkCreator } = params; const { room } = roomContext; const roomId = room?.roomId; @@ -92,14 +93,13 @@ export async function sendMessage( if (cmd.category === CommandCategories.messages || cmd.category === CommandCategories.effects) { attachRelation(content, relation); - // TODO translate this block for replies - // if (replyToEvent) { - // addReplyToMessageContent(content, replyToEvent, { - // permalinkCreator: this.props.permalinkCreator, - // // Exclude the legacy fallback for custom event types such as those used by /fireworks - // includeLegacyFallback: content.msgtype?.startsWith("m.") ?? true, - // }); - // } + if (replyToEvent) { + addReplyToMessageContent(content, replyToEvent, { + permalinkCreator, + // Exclude the legacy fallback for custom event types such as those used by /fireworks + includeLegacyFallback: content.msgtype?.startsWith("m.") ?? true, + }); + } } else { // instead of setting shouldSend to false as in SendMessageComposer, just return return; From f4c272d040427290812e6b7ed6ee9ad80d79d8be Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Wed, 5 Apr 2023 14:30:10 +0100 Subject: [PATCH 09/31] update util test --- .../wysiwyg_composer/utils/autocomplete-test.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts b/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts index 2612f037f2d..32923245acc 100644 --- a/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts +++ b/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts @@ -53,15 +53,18 @@ describe("buildQuery", () => { expect(buildQuery(noKeyCharSuggestion)).toBe(""); }); - it("returns an empty string when suggestion is a command", () => { - // TODO alter this test when commands are implemented - const commandSuggestion = { keyChar: "/" as const, text: "slash", type: "command" as const }; - expect(buildQuery(commandSuggestion)).toBe(""); - }); + // it("returns an empty string when suggestion is a command", () => { + // // TODO alter this test when commands are implemented + // const commandSuggestion = { keyChar: "/" as const, text: "slash", type: "command" as const }; + // expect(buildQuery(commandSuggestion)).toBe(""); + // }); it("combines the keyChar and text of the suggestion in the query", () => { const handledSuggestion = { keyChar: "@" as const, text: "alice", type: "mention" as const }; expect(buildQuery(handledSuggestion)).toBe("@alice"); + + const handledCommand = { keyChar: "/" as const, text: "spoiler", type: "mention" as const }; + expect(buildQuery(handledCommand)).toBe("/spoiler"); }); }); From 6f46bf8349a36276088586a14f99e0b1ad409ccd Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Wed, 5 Apr 2023 14:33:01 +0100 Subject: [PATCH 10/31] remove commented out test --- .../views/rooms/wysiwyg_composer/utils/autocomplete-test.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts b/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts index 32923245acc..dc89caba65e 100644 --- a/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts +++ b/test/components/views/rooms/wysiwyg_composer/utils/autocomplete-test.ts @@ -53,12 +53,6 @@ describe("buildQuery", () => { expect(buildQuery(noKeyCharSuggestion)).toBe(""); }); - // it("returns an empty string when suggestion is a command", () => { - // // TODO alter this test when commands are implemented - // const commandSuggestion = { keyChar: "/" as const, text: "slash", type: "command" as const }; - // expect(buildQuery(commandSuggestion)).toBe(""); - // }); - it("combines the keyChar and text of the suggestion in the query", () => { const handledSuggestion = { keyChar: "@" as const, text: "alice", type: "mention" as const }; expect(buildQuery(handledSuggestion)).toBe("@alice"); From 0db94cb79cc74eafd3ad06754438412b505fbb6d Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Wed, 5 Apr 2023 17:01:21 +0100 Subject: [PATCH 11/31] use local text over import from current composer --- .../views/rooms/wysiwyg_composer/utils/message.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/wysiwyg_composer/utils/message.ts b/src/components/views/rooms/wysiwyg_composer/utils/message.ts index 5ad23a99dbc..a8f27c25fbf 100644 --- a/src/components/views/rooms/wysiwyg_composer/utils/message.ts +++ b/src/components/views/rooms/wysiwyg_composer/utils/message.ts @@ -35,10 +35,18 @@ import { createMessageContent } from "./createMessageContent"; import { isContentModified } from "./isContentModified"; import { CommandCategories, getCommand } from "../../../../../SlashCommands"; import { runSlashCommand, shouldSendAnyway } from "../../../../../editor/commands"; -import { attachRelation } from "../../SendMessageComposer"; import { Action } from "../../../../../dispatcher/actions"; import { addReplyToMessageContent } from "../../../../../utils/Reply"; +// Merges favouring the given relation - taken from SendMessageComposer to avoid another import +function attachRelation(content: IContent, relation?: IEventRelation): void { + if (relation) { + content["m.relates_to"] = { + ...(content["m.relates_to"] || {}), + ...relation, + }; + } +} export interface SendMessageParams { mxClient: MatrixClient; relation?: IEventRelation; From f8bddc06106a216f63384f565ebe0f31ce24a569 Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Wed, 5 Apr 2023 17:01:30 +0100 Subject: [PATCH 12/31] expand tests --- .../wysiwyg_composer/utils/message-test.ts | 95 ++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/test/components/views/rooms/wysiwyg_composer/utils/message-test.ts b/test/components/views/rooms/wysiwyg_composer/utils/message-test.ts index 49442127d05..a2452173246 100644 --- a/test/components/views/rooms/wysiwyg_composer/utils/message-test.ts +++ b/test/components/views/rooms/wysiwyg_composer/utils/message-test.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { EventStatus } from "matrix-js-sdk/src/matrix"; +import { EventStatus, IEventRelation } from "matrix-js-sdk/src/matrix"; import { IRoomState } from "../../../../../../src/components/structures/RoomView"; import { editMessage, sendMessage } from "../../../../../../src/components/views/rooms/wysiwyg_composer/utils/message"; @@ -25,6 +25,8 @@ import { SettingLevel } from "../../../../../../src/settings/SettingLevel"; import { RoomPermalinkCreator } from "../../../../../../src/utils/permalinks/Permalinks"; import EditorStateTransfer from "../../../../../../src/utils/EditorStateTransfer"; import * as ConfirmRedactDialog from "../../../../../../src/components/views/dialogs/ConfirmRedactDialog"; +import * as SlashCommands from "../../../../../../src/SlashCommands"; +import * as Commands from "../../../../../../src/editor/commands"; describe("message", () => { const permalinkCreator = { @@ -226,6 +228,97 @@ describe("message", () => { // Then expect(spyDispatcher).toHaveBeenCalledWith({ action: "effects.confetti" }); }); + + describe.only("slash commands", () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + + it("calls getCommand for a message starting with a valid command", async () => { + // When + const validCommand = "/spoiler"; + const getCommandSpy = jest.spyOn(SlashCommands, "getCommand"); + await sendMessage(validCommand, true, { + roomContext: defaultRoomContext, + mxClient: mockClient, + permalinkCreator, + }); + + // Then + expect(getCommandSpy).toHaveBeenCalledWith(validCommand); + }); + + it("does not call getCommand for valid command with invalid prefix", async () => { + // When + const invalidPrefixCommand = "//spoiler"; + const getCommandSpy = jest.spyOn(SlashCommands, "getCommand"); + await sendMessage(invalidPrefixCommand, true, { + roomContext: defaultRoomContext, + mxClient: mockClient, + permalinkCreator, + }); + + // Then + expect(getCommandSpy).toHaveBeenCalledTimes(0); + }); + + // TODO amend test when TS fixes are made - this currently can't actually return undefined + // according to the TS types + it("returns undefined when the command is not successful", async () => { + // When + const validCommand = "/spoiler"; + jest.spyOn(Commands, "runSlashCommand").mockResolvedValue([{ content: "mock content" }, false]); + + const result = await sendMessage(validCommand, true, { + roomContext: defaultRoomContext, + mxClient: mockClient, + permalinkCreator, + }); + + // Then + expect(result).toBeUndefined(); + }); + + // /spoiler is a .messages category command, /fireworks is an .effect category command + const messagesAndEffectCategoryTestCases = ["/spoiler text", "/fireworks"]; + + it.each(messagesAndEffectCategoryTestCases)( + "does not add relations for a .messages or .effects category command if there is no relation to add", + async (inputText) => { + await sendMessage(inputText, true, { + roomContext: defaultRoomContext, + mxClient: mockClient, + permalinkCreator, + }); + expect(mockClient.sendMessage).toHaveBeenCalledWith( + "myfakeroom", + null, + expect.not.objectContaining({ "m.relates_to": expect.any }), + ); + }, + ); + + it.each(messagesAndEffectCategoryTestCases)( + "adds relations for a .messages or .effects category command if there is a relation", + async (inputText) => { + const mockRelation: IEventRelation = { + rel_type: "mock relation type", + }; + await sendMessage(inputText, true, { + roomContext: defaultRoomContext, + mxClient: mockClient, + permalinkCreator, + relation: mockRelation, + }); + + expect(mockClient.sendMessage).toHaveBeenCalledWith( + "myfakeroom", + null, + expect.objectContaining({ "m.relates_to": expect.objectContaining(mockRelation) }), + ); + }, + ); + }); }); describe("editMessage", () => { From 735e8a8edd20e8fb897f324232c333afc9d4a891 Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Thu, 6 Apr 2023 10:14:17 +0100 Subject: [PATCH 13/31] expand tests --- .../wysiwyg_composer/utils/message-test.ts | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/test/components/views/rooms/wysiwyg_composer/utils/message-test.ts b/test/components/views/rooms/wysiwyg_composer/utils/message-test.ts index a2452173246..7fe16e44a23 100644 --- a/test/components/views/rooms/wysiwyg_composer/utils/message-test.ts +++ b/test/components/views/rooms/wysiwyg_composer/utils/message-test.ts @@ -27,6 +27,8 @@ import EditorStateTransfer from "../../../../../../src/utils/EditorStateTransfer import * as ConfirmRedactDialog from "../../../../../../src/components/views/dialogs/ConfirmRedactDialog"; import * as SlashCommands from "../../../../../../src/SlashCommands"; import * as Commands from "../../../../../../src/editor/commands"; +import * as Reply from "../../../../../../src/utils/Reply"; +import { MatrixClientPeg } from "../../../../../../src/MatrixClientPeg"; describe("message", () => { const permalinkCreator = { @@ -49,6 +51,9 @@ describe("message", () => { }); const mockClient = createTestClient(); + mockClient.setDisplayName = jest.fn().mockResolvedValue({}); + mockClient.setRoomName = jest.fn().mockResolvedValue({}); + const mockRoom = mkStubRoom("myfakeroom", "myfakeroom", mockClient) as any; mockRoom.findEventById = jest.fn((eventId) => { return eventId === mockEvent.getId() ? mockEvent : null; @@ -58,6 +63,9 @@ describe("message", () => { const spyDispatcher = jest.spyOn(defaultDispatcher, "dispatch"); + beforeEach(() => { + jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient); + }); afterEach(() => { jest.resetAllMocks(); }); @@ -281,6 +289,7 @@ describe("message", () => { // /spoiler is a .messages category command, /fireworks is an .effect category command const messagesAndEffectCategoryTestCases = ["/spoiler text", "/fireworks"]; + const otherCategoryTestCases = ["/nick new_nickname", "/roomname new_room_name"]; it.each(messagesAndEffectCategoryTestCases)( "does not add relations for a .messages or .effects category command if there is no relation to add", @@ -318,6 +327,65 @@ describe("message", () => { ); }, ); + + it("calls addReplyToMessageContent when there is an event to reply to", async () => { + const addReplySpy = jest.spyOn(Reply, "addReplyToMessageContent"); + await sendMessage("input", true, { + roomContext: defaultRoomContext, + mxClient: mockClient, + permalinkCreator, + replyToEvent: mockEvent, + }); + + expect(addReplySpy).toHaveBeenCalledTimes(1); + }); + + // TODO - type will change here when I fix the TS errors (maybe) + it.each(otherCategoryTestCases)( + "returns undefined when the command category is not .messages or .effects", + async (input) => { + const result = await sendMessage(input, true, { + roomContext: defaultRoomContext, + mxClient: mockClient, + permalinkCreator, + replyToEvent: mockEvent, + }); + + expect(result).toBeUndefined(); + }, + ); + + it("if user enters invalid command and then sends it anyway, message is sent", async () => { + // mock out returning a true value for `shouldSendAnyway` to avoid rendering the modal + jest.spyOn(Commands, "shouldSendAnyway").mockResolvedValue(true); + const invalidCommandInput = "/badCommand"; + + await sendMessage(invalidCommandInput, true, { + roomContext: defaultRoomContext, + mxClient: mockClient, + permalinkCreator, + }); + + expect(mockClient.sendMessage).toHaveBeenCalledWith( + "myfakeroom", + null, + expect.objectContaining({ body: invalidCommandInput }), + ); + }); + + it("if user enters invalid command and then does not send, return undefined", async () => { + // mock out returning a true value for `shouldSendAnyway` to avoid rendering the modal + jest.spyOn(Commands, "shouldSendAnyway").mockResolvedValue(false); + const invalidCommandInput = "/badCommand"; + + const result = await sendMessage(invalidCommandInput, true, { + roomContext: defaultRoomContext, + mxClient: mockClient, + permalinkCreator, + }); + + expect(result).toBeUndefined(); + }); }); }); From b0c62a3e495c5a4687e5aa005147855350c0377b Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Thu, 6 Apr 2023 10:26:09 +0100 Subject: [PATCH 14/31] handle the FocusAComposer action for the wysiwyg composer --- .../rooms/wysiwyg_composer/hooks/useWysiwygSendActionHandler.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/rooms/wysiwyg_composer/hooks/useWysiwygSendActionHandler.ts b/src/components/views/rooms/wysiwyg_composer/hooks/useWysiwygSendActionHandler.ts index 40b7e8182cb..50a229b6371 100644 --- a/src/components/views/rooms/wysiwyg_composer/hooks/useWysiwygSendActionHandler.ts +++ b/src/components/views/rooms/wysiwyg_composer/hooks/useWysiwygSendActionHandler.ts @@ -46,6 +46,7 @@ export function useWysiwygSendActionHandler( switch (payload.action) { case "reply_to_event": + case Action.FocusAComposer: case Action.FocusSendMessageComposer: focusComposer(composerElement, context, roomContext, timeoutId); break; From 86347a48ceabaa89b38aab53b7dc0ed118688a6a Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Thu, 6 Apr 2023 10:26:16 +0100 Subject: [PATCH 15/31] remove TODO comment --- src/components/views/rooms/wysiwyg_composer/utils/message.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/wysiwyg_composer/utils/message.ts b/src/components/views/rooms/wysiwyg_composer/utils/message.ts index a8f27c25fbf..a8f0ee74c84 100644 --- a/src/components/views/rooms/wysiwyg_composer/utils/message.ts +++ b/src/components/views/rooms/wysiwyg_composer/utils/message.ts @@ -116,7 +116,7 @@ export async function sendMessage( const sendAnyway = await shouldSendAnyway(message); // re-focus the composer after QuestionDialog is closed dis.dispatch({ - action: Action.FocusAComposer, // TODO this does not refocus the wysiwyg composer + action: Action.FocusAComposer, context: roomContext.timelineRenderingType, }); // if !sendAnyway bail to let the user edit the composer and try again From 0282c1416d4a17f6d44fa172a901644db7b02ec8 Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Thu, 6 Apr 2023 10:29:01 +0100 Subject: [PATCH 16/31] remove TODO --- src/components/views/rooms/wysiwyg_composer/utils/message.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/views/rooms/wysiwyg_composer/utils/message.ts b/src/components/views/rooms/wysiwyg_composer/utils/message.ts index a8f0ee74c84..02ac9777a63 100644 --- a/src/components/views/rooms/wysiwyg_composer/utils/message.ts +++ b/src/components/views/rooms/wysiwyg_composer/utils/message.ts @@ -129,8 +129,6 @@ export async function sendMessage( content = await createMessageContent(message, isHTML, params); } - // TODO slash comment - // TODO replace emotion end of message ? // TODO quick reaction From 377e191352d963a6615ebc71bcaba4f0545f20a4 Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Thu, 6 Apr 2023 10:29:17 +0100 Subject: [PATCH 17/31] test for action dispatch --- .../views/rooms/wysiwyg_composer/utils/message-test.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/components/views/rooms/wysiwyg_composer/utils/message-test.ts b/test/components/views/rooms/wysiwyg_composer/utils/message-test.ts index 7fe16e44a23..d37ca8bc1f3 100644 --- a/test/components/views/rooms/wysiwyg_composer/utils/message-test.ts +++ b/test/components/views/rooms/wysiwyg_composer/utils/message-test.ts @@ -29,6 +29,7 @@ import * as SlashCommands from "../../../../../../src/SlashCommands"; import * as Commands from "../../../../../../src/editor/commands"; import * as Reply from "../../../../../../src/utils/Reply"; import { MatrixClientPeg } from "../../../../../../src/MatrixClientPeg"; +import { Action } from "../../../../../../src/dispatcher/actions"; describe("message", () => { const permalinkCreator = { @@ -355,7 +356,7 @@ describe("message", () => { }, ); - it("if user enters invalid command and then sends it anyway, message is sent", async () => { + it("if user enters invalid command and then sends it anyway", async () => { // mock out returning a true value for `shouldSendAnyway` to avoid rendering the modal jest.spyOn(Commands, "shouldSendAnyway").mockResolvedValue(true); const invalidCommandInput = "/badCommand"; @@ -366,11 +367,14 @@ describe("message", () => { permalinkCreator, }); + // we expect the message to have been sent + // and a composer focus action to have been dispatched expect(mockClient.sendMessage).toHaveBeenCalledWith( "myfakeroom", null, expect.objectContaining({ body: invalidCommandInput }), ); + expect(spyDispatcher).toHaveBeenCalledWith(expect.objectContaining({ action: Action.FocusAComposer })); }); it("if user enters invalid command and then does not send, return undefined", async () => { From 66f8380f5d37f81f86e8e4be7c4f9615d9242cd7 Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Thu, 6 Apr 2023 11:33:05 +0100 Subject: [PATCH 18/31] fix failing tests --- .../wysiwyg_composer/utils/message-test.ts | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/test/components/views/rooms/wysiwyg_composer/utils/message-test.ts b/test/components/views/rooms/wysiwyg_composer/utils/message-test.ts index d37ca8bc1f3..eb7127ad198 100644 --- a/test/components/views/rooms/wysiwyg_composer/utils/message-test.ts +++ b/test/components/views/rooms/wysiwyg_composer/utils/message-test.ts @@ -66,9 +66,11 @@ describe("message", () => { beforeEach(() => { jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient); + jest.clearAllMocks(); }); - afterEach(() => { - jest.resetAllMocks(); + + afterAll(() => { + jest.restoreAllMocks(); }); describe("sendMessage", () => { @@ -238,15 +240,12 @@ describe("message", () => { expect(spyDispatcher).toHaveBeenCalledWith({ action: "effects.confetti" }); }); - describe.only("slash commands", () => { - afterEach(() => { - jest.restoreAllMocks(); - }); + describe("slash commands", () => { + const getCommandSpy = jest.spyOn(SlashCommands, "getCommand"); it("calls getCommand for a message starting with a valid command", async () => { // When const validCommand = "/spoiler"; - const getCommandSpy = jest.spyOn(SlashCommands, "getCommand"); await sendMessage(validCommand, true, { roomContext: defaultRoomContext, mxClient: mockClient, @@ -260,7 +259,6 @@ describe("message", () => { it("does not call getCommand for valid command with invalid prefix", async () => { // When const invalidPrefixCommand = "//spoiler"; - const getCommandSpy = jest.spyOn(SlashCommands, "getCommand"); await sendMessage(invalidPrefixCommand, true, { roomContext: defaultRoomContext, mxClient: mockClient, @@ -276,7 +274,7 @@ describe("message", () => { it("returns undefined when the command is not successful", async () => { // When const validCommand = "/spoiler"; - jest.spyOn(Commands, "runSlashCommand").mockResolvedValue([{ content: "mock content" }, false]); + jest.spyOn(Commands, "runSlashCommand").mockResolvedValueOnce([{ content: "mock content" }, false]); const result = await sendMessage(validCommand, true, { roomContext: defaultRoomContext, @@ -358,7 +356,7 @@ describe("message", () => { it("if user enters invalid command and then sends it anyway", async () => { // mock out returning a true value for `shouldSendAnyway` to avoid rendering the modal - jest.spyOn(Commands, "shouldSendAnyway").mockResolvedValue(true); + jest.spyOn(Commands, "shouldSendAnyway").mockResolvedValueOnce(true); const invalidCommandInput = "/badCommand"; await sendMessage(invalidCommandInput, true, { @@ -379,7 +377,7 @@ describe("message", () => { it("if user enters invalid command and then does not send, return undefined", async () => { // mock out returning a true value for `shouldSendAnyway` to avoid rendering the modal - jest.spyOn(Commands, "shouldSendAnyway").mockResolvedValue(false); + jest.spyOn(Commands, "shouldSendAnyway").mockResolvedValueOnce(false); const invalidCommandInput = "/badCommand"; const result = await sendMessage(invalidCommandInput, true, { From 8cddb2518efcdc2ddb023edc4f3e7f6f053eefe5 Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Thu, 6 Apr 2023 11:38:38 +0100 Subject: [PATCH 19/31] tidy up tests --- .../views/rooms/wysiwyg_composer/utils/message-test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/components/views/rooms/wysiwyg_composer/utils/message-test.ts b/test/components/views/rooms/wysiwyg_composer/utils/message-test.ts index eb7127ad198..8a9dd1617d0 100644 --- a/test/components/views/rooms/wysiwyg_composer/utils/message-test.ts +++ b/test/components/views/rooms/wysiwyg_composer/utils/message-test.ts @@ -288,8 +288,6 @@ describe("message", () => { // /spoiler is a .messages category command, /fireworks is an .effect category command const messagesAndEffectCategoryTestCases = ["/spoiler text", "/fireworks"]; - const otherCategoryTestCases = ["/nick new_nickname", "/roomname new_room_name"]; - it.each(messagesAndEffectCategoryTestCases)( "does not add relations for a .messages or .effects category command if there is no relation to add", async (inputText) => { @@ -340,6 +338,9 @@ describe("message", () => { }); // TODO - type will change here when I fix the TS errors (maybe) + + // these test cases are .action and .admin categories + const otherCategoryTestCases = ["/nick new_nickname", "/roomname new_room_name"]; it.each(otherCategoryTestCases)( "returns undefined when the command category is not .messages or .effects", async (input) => { From 000d74480d68f7422f71aef619d453f7e57401e9 Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Thu, 6 Apr 2023 11:48:42 +0100 Subject: [PATCH 20/31] fix TS error and improve typing --- .../components/WysiwygAutocomplete-test.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/test/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete-test.tsx b/test/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete-test.tsx index e4de34c2691..b666ff1ff9f 100644 --- a/test/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete-test.tsx +++ b/test/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete-test.tsx @@ -69,8 +69,9 @@ describe("WysiwygAutocomplete", () => { }, ]); const mockHandleMention = jest.fn(); + const mockHandleCommand = jest.fn(); - const renderComponent = (props = {}) => { + const renderComponent = (props: Partial> = {}) => { const mockClient = stubClient(); const mockRoom = mkStubRoom("test_room", "test_room", mockClient); const mockRoomContext = getRoomContext(mockRoom, {}); @@ -82,6 +83,7 @@ describe("WysiwygAutocomplete", () => { ref={autocompleteRef} suggestion={null} handleMention={mockHandleMention} + handleCommand={mockHandleCommand} {...props} /> @@ -90,7 +92,14 @@ describe("WysiwygAutocomplete", () => { }; it("does not show the autocomplete when room is undefined", () => { - render(); + render( + , + ); expect(screen.queryByTestId("autocomplete-wrapper")).not.toBeInTheDocument(); }); From b1ca9999649bbb20eb2189fa3b9ee90ee65c89e8 Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Thu, 6 Apr 2023 12:14:16 +0100 Subject: [PATCH 21/31] fix TS error --- .../views/rooms/wysiwyg_composer/components/WysiwygComposer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/wysiwyg_composer/components/WysiwygComposer.tsx b/src/components/views/rooms/wysiwyg_composer/components/WysiwygComposer.tsx index 66c28ff4d5b..56f79c94a87 100644 --- a/src/components/views/rooms/wysiwyg_composer/components/WysiwygComposer.tsx +++ b/src/components/views/rooms/wysiwyg_composer/components/WysiwygComposer.tsx @@ -91,7 +91,7 @@ export const WysiwygComposer = memo(function WysiwygComposer({ } } - const mentions: NodeList = ref.current?.querySelectorAll("a[data-mention-type]"); + const mentions: NodeList | undefined = ref.current?.querySelectorAll("a[data-mention-type]"); if (mentions) { mentions.forEach((mention) => mention.addEventListener("click", handleClick)); } From 4abd905aad2b5a83d25884a31f1c8ad269bbda39 Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Thu, 6 Apr 2023 12:18:02 +0100 Subject: [PATCH 22/31] amend return types for sendMessage, editMessage --- src/components/views/rooms/wysiwyg_composer/utils/message.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/wysiwyg_composer/utils/message.ts b/src/components/views/rooms/wysiwyg_composer/utils/message.ts index 02ac9777a63..b075a97e084 100644 --- a/src/components/views/rooms/wysiwyg_composer/utils/message.ts +++ b/src/components/views/rooms/wysiwyg_composer/utils/message.ts @@ -60,7 +60,7 @@ export async function sendMessage( message: string, isHTML: boolean, { roomContext, mxClient, ...params }: SendMessageParams, -): Promise { +): Promise { const { relation, replyToEvent, permalinkCreator } = params; const { room } = roomContext; const roomId = room?.roomId; @@ -200,7 +200,7 @@ interface EditMessageParams { export async function editMessage( html: string, { roomContext, mxClient, editorStateTransfer }: EditMessageParams, -): Promise { +): Promise { const editedEvent = editorStateTransfer.getEvent(); PosthogAnalytics.instance.trackEvent({ From fa13702a54e481b8eb68fcd1b1759228ef81ec43 Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Thu, 6 Apr 2023 12:20:54 +0100 Subject: [PATCH 23/31] fix null content TS error --- src/components/views/rooms/wysiwyg_composer/utils/message.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/wysiwyg_composer/utils/message.ts b/src/components/views/rooms/wysiwyg_composer/utils/message.ts index b075a97e084..a0b80395b46 100644 --- a/src/components/views/rooms/wysiwyg_composer/utils/message.ts +++ b/src/components/views/rooms/wysiwyg_composer/utils/message.ts @@ -99,7 +99,10 @@ export async function sendMessage( return; // errored } - if (cmd.category === CommandCategories.messages || cmd.category === CommandCategories.effects) { + if ( + content && + (cmd.category === CommandCategories.messages || cmd.category === CommandCategories.effects) + ) { attachRelation(content, relation); if (replyToEvent) { addReplyToMessageContent(content, replyToEvent, { From 01fa6a7ef8f5174fba51dadf0d1303619f4f4090 Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Thu, 6 Apr 2023 12:22:01 +0100 Subject: [PATCH 24/31] fix another null content TS error --- src/components/views/rooms/wysiwyg_composer/utils/message.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/wysiwyg_composer/utils/message.ts b/src/components/views/rooms/wysiwyg_composer/utils/message.ts index a0b80395b46..a0563c7d517 100644 --- a/src/components/views/rooms/wysiwyg_composer/utils/message.ts +++ b/src/components/views/rooms/wysiwyg_composer/utils/message.ts @@ -165,7 +165,7 @@ export async function sendMessage( dis.dispatch({ action: "message_sent" }); CHAT_EFFECTS.forEach((effect) => { - if (containsEmoji(content, effect.emojis)) { + if (content && containsEmoji(content, effect.emojis)) { // For initial threads launch, chat effects are disabled // see #19731 const isNotThread = relation?.rel_type !== THREAD_RELATION_TYPE.name; From cd4ce1bc3b3f924939745050b60104ef60bec55d Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Thu, 6 Apr 2023 13:09:45 +0100 Subject: [PATCH 25/31] use as to correct final TS error --- src/components/views/rooms/wysiwyg_composer/utils/message.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/wysiwyg_composer/utils/message.ts b/src/components/views/rooms/wysiwyg_composer/utils/message.ts index a0563c7d517..e3a6bfb6489 100644 --- a/src/components/views/rooms/wysiwyg_composer/utils/message.ts +++ b/src/components/views/rooms/wysiwyg_composer/utils/message.ts @@ -149,7 +149,9 @@ export async function sendMessage( const prom = doMaybeLocalRoomAction( roomId, - (actualRoomId: string) => mxClient.sendMessage(actualRoomId, threadId, content), + (actualRoomId: string) => { + return mxClient.sendMessage(actualRoomId, threadId, content as IContent, undefined); + }, mxClient, ); From 45cc670a19c5b842500afa1725257c20d3865a88 Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Thu, 6 Apr 2023 13:31:35 +0100 Subject: [PATCH 26/31] remove undefined argument --- src/components/views/rooms/wysiwyg_composer/utils/message.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/wysiwyg_composer/utils/message.ts b/src/components/views/rooms/wysiwyg_composer/utils/message.ts index e3a6bfb6489..587ce38f3fa 100644 --- a/src/components/views/rooms/wysiwyg_composer/utils/message.ts +++ b/src/components/views/rooms/wysiwyg_composer/utils/message.ts @@ -150,7 +150,7 @@ export async function sendMessage( const prom = doMaybeLocalRoomAction( roomId, (actualRoomId: string) => { - return mxClient.sendMessage(actualRoomId, threadId, content as IContent, undefined); + return mxClient.sendMessage(actualRoomId, threadId, content as IContent); }, mxClient, ); From b77025b8195acc334a955b889899cd4d536ac4cb Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Thu, 6 Apr 2023 13:32:23 +0100 Subject: [PATCH 27/31] try to fix TS errors for editMessage function usage --- .../DynamicImportWysiwygComposer.tsx | 2 +- .../rooms/wysiwyg_composer/hooks/useEditing.ts | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/components/views/rooms/wysiwyg_composer/DynamicImportWysiwygComposer.tsx b/src/components/views/rooms/wysiwyg_composer/DynamicImportWysiwygComposer.tsx index 9df04f90f34..afe8396bbd0 100644 --- a/src/components/views/rooms/wysiwyg_composer/DynamicImportWysiwygComposer.tsx +++ b/src/components/views/rooms/wysiwyg_composer/DynamicImportWysiwygComposer.tsx @@ -28,7 +28,7 @@ export const dynamicImportSendMessage = async ( message: string, isHTML: boolean, params: SendMessageParams, -): Promise => { +): Promise => { const { sendMessage } = await import("./utils/message"); return sendMessage(message, isHTML, params); diff --git a/src/components/views/rooms/wysiwyg_composer/hooks/useEditing.ts b/src/components/views/rooms/wysiwyg_composer/hooks/useEditing.ts index 862b7495026..ffb354dda7c 100644 --- a/src/components/views/rooms/wysiwyg_composer/hooks/useEditing.ts +++ b/src/components/views/rooms/wysiwyg_composer/hooks/useEditing.ts @@ -29,7 +29,7 @@ export function useEditing( ): { isSaveDisabled: boolean; onChange(content: string): void; - editMessage(): Promise; + editMessage(): Promise; endEditing(): void; } { const roomContext = useRoomContext(); @@ -45,11 +45,12 @@ export function useEditing( [initialContent], ); - const editMessageMemoized = useCallback( - () => - !!mxClient && content !== undefined && editMessage(content, { roomContext, mxClient, editorStateTransfer }), - [content, roomContext, mxClient, editorStateTransfer], - ); + const editMessageMemoized = useCallback(() => { + if (mxClient === undefined || content === undefined) { + return Promise.resolve(undefined); + } + return editMessage(content, { roomContext, mxClient, editorStateTransfer }); + }, [content, roomContext, mxClient, editorStateTransfer]); const endEditingMemoized = useCallback(() => endEditing(roomContext), [roomContext]); return { onChange, editMessage: editMessageMemoized, endEditing: endEditingMemoized, isSaveDisabled }; From 2863b3ddc9e320394e2a74e06361acf3db671b1e Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Thu, 6 Apr 2023 13:58:26 +0100 Subject: [PATCH 28/31] tidy up --- .../components/WysiwygAutocomplete.tsx | 7 ++++--- .../wysiwyg_composer/hooks/useEditing.ts | 4 ++-- .../rooms/wysiwyg_composer/utils/message.ts | 20 ++++--------------- 3 files changed, 10 insertions(+), 21 deletions(-) diff --git a/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx b/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx index 0cff876aa1e..20e41fe0b12 100644 --- a/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx +++ b/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx @@ -62,9 +62,10 @@ const WysiwygAutocomplete = forwardRef( // TODO handle all of the completion types // Using this to pick out the ones we can handle during implementation if (completion.type === "command") { - // trim the completion text as we add the trailing space in the rust model - // nb there are utils like parseCommandString and the CommandMap in SlashCommands.tsx - // that might be required here, but for now just use the trimmed completion text + // TODO determine if utils in SlashCommands.tsx are required + + // trim the completion as some include trailing spaces, but we always insert a + // trailing space in the rust model anyway handleCommand(completion.completion.trim()); } if (client && room && completion.href && (completion.type === "room" || completion.type === "user")) { diff --git a/src/components/views/rooms/wysiwyg_composer/hooks/useEditing.ts b/src/components/views/rooms/wysiwyg_composer/hooks/useEditing.ts index ffb354dda7c..e646046e599 100644 --- a/src/components/views/rooms/wysiwyg_composer/hooks/useEditing.ts +++ b/src/components/views/rooms/wysiwyg_composer/hooks/useEditing.ts @@ -45,9 +45,9 @@ export function useEditing( [initialContent], ); - const editMessageMemoized = useCallback(() => { + const editMessageMemoized = useCallback(async () => { if (mxClient === undefined || content === undefined) { - return Promise.resolve(undefined); + return; } return editMessage(content, { roomContext, mxClient, editorStateTransfer }); }, [content, roomContext, mxClient, editorStateTransfer]); diff --git a/src/components/views/rooms/wysiwyg_composer/utils/message.ts b/src/components/views/rooms/wysiwyg_composer/utils/message.ts index 587ce38f3fa..0d69cb53c42 100644 --- a/src/components/views/rooms/wysiwyg_composer/utils/message.ts +++ b/src/components/views/rooms/wysiwyg_composer/utils/message.ts @@ -37,16 +37,8 @@ import { CommandCategories, getCommand } from "../../../../../SlashCommands"; import { runSlashCommand, shouldSendAnyway } from "../../../../../editor/commands"; import { Action } from "../../../../../dispatcher/actions"; import { addReplyToMessageContent } from "../../../../../utils/Reply"; +import { attachRelation } from "../../SendMessageComposer"; -// Merges favouring the given relation - taken from SendMessageComposer to avoid another import -function attachRelation(content: IContent, relation?: IEventRelation): void { - if (relation) { - content["m.relates_to"] = { - ...(content["m.relates_to"] || {}), - ...relation, - }; - } -} export interface SendMessageParams { mxClient: MatrixClient; relation?: IEventRelation; @@ -127,10 +119,8 @@ export async function sendMessage( } } - // if content is still null, we haven't done any slash command processing so generate some content - if (content === null) { - content = await createMessageContent(message, isHTML, params); - } + // if content is null, we haven't done any slash command processing, so generate some content + content ??= await createMessageContent(message, isHTML, params); // TODO replace emotion end of message ? @@ -149,9 +139,7 @@ export async function sendMessage( const prom = doMaybeLocalRoomAction( roomId, - (actualRoomId: string) => { - return mxClient.sendMessage(actualRoomId, threadId, content as IContent); - }, + (actualRoomId: string) => mxClient.sendMessage(actualRoomId, threadId, content as IContent), mxClient, ); From 61946a1f4acf37dada07d6a83cec1bbe64fb2a08 Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Thu, 6 Apr 2023 14:16:33 +0100 Subject: [PATCH 29/31] add TODO --- .../rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx b/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx index 20e41fe0b12..709de5fbbc1 100644 --- a/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx +++ b/src/components/views/rooms/wysiwyg_composer/components/WysiwygAutocomplete.tsx @@ -77,6 +77,8 @@ const WysiwygAutocomplete = forwardRef( } } + // TODO - determine if we show all of the /command suggestions, there are some options in the + // list which don't seem to make sense in this context, specifically /html and /plain return room ? (
Date: Thu, 6 Apr 2023 14:37:21 +0100 Subject: [PATCH 30/31] improve comments --- .../views/rooms/wysiwyg_composer/utils/message-test.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/test/components/views/rooms/wysiwyg_composer/utils/message-test.ts b/test/components/views/rooms/wysiwyg_composer/utils/message-test.ts index 8a9dd1617d0..58fc6b7184c 100644 --- a/test/components/views/rooms/wysiwyg_composer/utils/message-test.ts +++ b/test/components/views/rooms/wysiwyg_composer/utils/message-test.ts @@ -269,8 +269,6 @@ describe("message", () => { expect(getCommandSpy).toHaveBeenCalledTimes(0); }); - // TODO amend test when TS fixes are made - this currently can't actually return undefined - // according to the TS types it("returns undefined when the command is not successful", async () => { // When const validCommand = "/spoiler"; @@ -337,8 +335,6 @@ describe("message", () => { expect(addReplySpy).toHaveBeenCalledTimes(1); }); - // TODO - type will change here when I fix the TS errors (maybe) - // these test cases are .action and .admin categories const otherCategoryTestCases = ["/nick new_nickname", "/roomname new_room_name"]; it.each(otherCategoryTestCases)( @@ -377,7 +373,7 @@ describe("message", () => { }); it("if user enters invalid command and then does not send, return undefined", async () => { - // mock out returning a true value for `shouldSendAnyway` to avoid rendering the modal + // mock out returning a false value for `shouldSendAnyway` to avoid rendering the modal jest.spyOn(Commands, "shouldSendAnyway").mockResolvedValueOnce(false); const invalidCommandInput = "/badCommand"; From eb4e1a8b9e1a78579b1190f07bf2a9a1f26252f4 Mon Sep 17 00:00:00 2001 From: Alun Turner Date: Mon, 10 Apr 2023 08:32:06 +0100 Subject: [PATCH 31/31] update comment --- src/components/views/rooms/wysiwyg_composer/utils/message.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/wysiwyg_composer/utils/message.ts b/src/components/views/rooms/wysiwyg_composer/utils/message.ts index 0d69cb53c42..9753ebae494 100644 --- a/src/components/views/rooms/wysiwyg_composer/utils/message.ts +++ b/src/components/views/rooms/wysiwyg_composer/utils/message.ts @@ -82,7 +82,7 @@ export async function sendMessage( if (message.startsWith("/") && !message.startsWith("//")) { const { cmd, args } = getCommand(message); if (cmd) { - // we will need to handle /me separately, see SlashCommands.tsx:1387 + // TODO handle /me special case separately, see end of SlashCommands.Commands const threadId = relation?.rel_type === THREAD_RELATION_TYPE.name ? relation?.event_id : null; let commandSuccessful: boolean; [content, commandSuccessful] = await runSlashCommand(cmd, args, roomId, threadId ?? null);