From 85ece767a37d6e791ca1b3346d00658e356cb3bd Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sat, 20 May 2023 19:55:38 -0600 Subject: [PATCH 1/7] Expose and pre-populate thread ID in devtools dialog We don't pre-populate state events because state events *shouldn't* have relationships like this. --- src/SlashCommands.tsx | 25 +++- .../views/dialogs/DevtoolsDialog.tsx | 14 +- .../views/dialogs/devtools/BaseTool.tsx | 1 + .../views/dialogs/devtools/Event.tsx | 7 + src/i18n/strings/en_EN.json | 1 + .../views/dialogs/DevtoolsDialog-test.tsx | 22 ++- .../views/dialogs/devtools/Event-test.tsx | 71 ++++++++++ .../__snapshots__/Event-test.tsx.snap | 126 ++++++++++++++++++ 8 files changed, 257 insertions(+), 10 deletions(-) create mode 100644 test/components/views/dialogs/devtools/Event-test.tsx create mode 100644 test/components/views/dialogs/devtools/__snapshots__/Event-test.tsx.snap diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 3615e1a8a3b..a9edc4ddf87 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -111,6 +111,7 @@ export const CommandCategories = { export type RunResult = XOR<{ error: Error }, { promise: Promise }>; +type RunInThreadFn = (this: Command, roomId: string, threadRootId?: string, args?: string) => RunResult; type RunFn = (this: Command, roomId: string, args?: string) => RunResult; interface ICommandOpts { @@ -119,11 +120,18 @@ interface ICommandOpts { args?: string; description: string; analyticsName?: SlashCommandEvent["command"]; - runFn?: RunFn; + runFn?: RunFn | RunInThreadFn; category: string; hideCompletionAfterSpace?: boolean; isEnabled?(): boolean; renderingTypes?: TimelineRenderingType[]; + + /** + * When true, runFn should be a `RunInThreadFn` to receive the actual thread + * ID. If the command is called without the context of a thread, the supplied + * thread ID will be undefined/falsy. + */ + canReceiveThreadId?: boolean; } export class Command { @@ -131,11 +139,12 @@ export class Command { public readonly aliases: string[]; public readonly args?: string; public readonly description: string; - public readonly runFn?: RunFn; + public readonly runFn?: RunFn | RunInThreadFn; public readonly category: string; public readonly hideCompletionAfterSpace: boolean; public readonly renderingTypes?: TimelineRenderingType[]; public readonly analyticsName?: SlashCommandEvent["command"]; + public readonly canReceiveThreadId?: boolean; private readonly _isEnabled?: () => boolean; public constructor(opts: ICommandOpts) { @@ -149,6 +158,7 @@ export class Command { this._isEnabled = opts.isEnabled; this.renderingTypes = opts.renderingTypes; this.analyticsName = opts.analyticsName; + this.canReceiveThreadId = opts.canReceiveThreadId; } public getCommand(): string { @@ -182,7 +192,11 @@ export class Command { }); } - return this.runFn(roomId, args); + if (this.canReceiveThreadId) { + return this.runFn(roomId, threadId, args); + } else { + return this.runFn(roomId, args); + } } public getUsage(): string { @@ -981,8 +995,9 @@ export const Commands = [ new Command({ command: "devtools", description: _td("Opens the Developer Tools dialog"), - runFn: function (roomId) { - Modal.createDialog(DevtoolsDialog, { roomId }, "mx_DevtoolsDialog_wrapper"); + canReceiveThreadId: true, + runFn: function (roomId, threadRootId) { + Modal.createDialog(DevtoolsDialog, { roomId, threadRootId }, "mx_DevtoolsDialog_wrapper"); return success(); }, category: CommandCategories.advanced, diff --git a/src/components/views/dialogs/DevtoolsDialog.tsx b/src/components/views/dialogs/DevtoolsDialog.tsx index 655b175edfb..3605370b234 100644 --- a/src/components/views/dialogs/DevtoolsDialog.tsx +++ b/src/components/views/dialogs/DevtoolsDialog.tsx @@ -65,12 +65,13 @@ const Tools: Record = { interface IProps { roomId: string; + threadRootId?: string; onFinished(finished?: boolean): void; } type ToolInfo = [label: string, tool: Tool]; -const DevtoolsDialog: React.FC = ({ roomId, onFinished }) => { +const DevtoolsDialog: React.FC = ({ roomId, threadRootId, onFinished }) => { const [tool, setTool] = useState(null); let body: JSX.Element; @@ -125,9 +126,18 @@ const DevtoolsDialog: React.FC = ({ roomId, onFinished }) => { roomId} border={false}> {_t("Room ID: %(roomId)s", { roomId })} + {!threadRootId ? null : ( + threadRootId} + border={false} + > + {_t("Thread Root ID: %(threadRootId)s", { threadRootId })} + + )}
{cli.getRoom(roomId) && ( - + {body} )} diff --git a/src/components/views/dialogs/devtools/BaseTool.tsx b/src/components/views/dialogs/devtools/BaseTool.tsx index 473c0255e27..d189a98302b 100644 --- a/src/components/views/dialogs/devtools/BaseTool.tsx +++ b/src/components/views/dialogs/devtools/BaseTool.tsx @@ -79,6 +79,7 @@ export default BaseTool; interface IContext { room: Room; + threadRootId?: string; } export const DevtoolsContext = createContext({} as IContext); diff --git a/src/components/views/dialogs/devtools/Event.tsx b/src/components/views/dialogs/devtools/Event.tsx index c4d8af09a86..f60fb30abcd 100644 --- a/src/components/views/dialogs/devtools/Event.tsx +++ b/src/components/views/dialogs/devtools/Event.tsx @@ -203,6 +203,13 @@ export const TimelineEventEditor: React.FC = ({ mxEvent, onBack }) }; defaultContent = stringify(newContent); + } else if (context.threadRootId) { + defaultContent = stringify({ + "m.relates_to": { + rel_type: "m.thread", + event_id: context.threadRootId, + }, + }); } return ; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index eb1ef2fcb15..422bc0a2ecd 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2834,6 +2834,7 @@ "Toolbox": "Toolbox", "Developer Tools": "Developer Tools", "Room ID: %(roomId)s": "Room ID: %(roomId)s", + "Thread Root ID: %(threadRootId)s": "Thread Root ID: %(threadRootId)s", "The poll has ended. No votes were cast.": "The poll has ended. No votes were cast.", "The poll has ended. Top answer: %(topAnswer)s": "The poll has ended. Top answer: %(topAnswer)s", "Failed to end poll": "Failed to end poll", diff --git a/test/components/views/dialogs/DevtoolsDialog-test.tsx b/test/components/views/dialogs/DevtoolsDialog-test.tsx index bc451255fa6..7d6f8548f0a 100644 --- a/test/components/views/dialogs/DevtoolsDialog-test.tsx +++ b/test/components/views/dialogs/DevtoolsDialog-test.tsx @@ -15,7 +15,7 @@ limitations under the License. */ import React from "react"; -import { getByLabelText, render } from "@testing-library/react"; +import { getByLabelText, getAllByLabelText, render } from "@testing-library/react"; import { Room } from "matrix-js-sdk/src/models/room"; import { MatrixClient } from "matrix-js-sdk/src/client"; import userEvent from "@testing-library/user-event"; @@ -29,10 +29,10 @@ describe("DevtoolsDialog", () => { let cli: MatrixClient; let room: Room; - function getComponent(roomId: string, onFinished = () => true) { + function getComponent(roomId: string, threadRootId?: string, onFinished = () => true) { return render( - + , ); } @@ -68,4 +68,20 @@ describe("DevtoolsDialog", () => { expect(navigator.clipboard.writeText).toHaveBeenCalled(); expect(navigator.clipboard.readText()).resolves.toBe(room.roomId); }); + + it("copies the thread root id when provided", async () => { + const user = userEvent.setup(); + jest.spyOn(navigator.clipboard, "writeText"); + + const threadRootId = "$test_event_id_goes_here"; + const { container } = getComponent(room.roomId, threadRootId); + + const copyBtn = getAllByLabelText(container, "Copy")[1]; + await user.click(copyBtn); + const copiedBtn = getByLabelText(container, "Copied!"); + + expect(copiedBtn).toBeInTheDocument(); + expect(navigator.clipboard.writeText).toHaveBeenCalled(); + expect(navigator.clipboard.readText()).resolves.toBe(threadRootId); + }); }); diff --git a/test/components/views/dialogs/devtools/Event-test.tsx b/test/components/views/dialogs/devtools/Event-test.tsx new file mode 100644 index 00000000000..b135573a0ee --- /dev/null +++ b/test/components/views/dialogs/devtools/Event-test.tsx @@ -0,0 +1,71 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from "react"; +import { render } from "@testing-library/react"; +import { Room } from "matrix-js-sdk/src/models/room"; +import { PendingEventOrdering } from "matrix-js-sdk/src/client"; + +import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext"; +import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg"; +import { stubClient } from "../../../../test-utils"; +import { DevtoolsContext } from "../../../../../src/components/views/dialogs/devtools/BaseTool"; +import { TimelineEventEditor } from "../../../../../src/components/views/dialogs/devtools/Event"; + +describe("", () => { + beforeEach(() => { + stubClient(); + }); + + it("should render", () => { + const cli = MatrixClientPeg.get(); + const { asFragment } = render( + + + {}} /> + + , + ); + expect(asFragment()).toMatchSnapshot(); + }); + + describe("thread context", () => { + it("should pre-populate a thread relationship", () => { + const cli = MatrixClientPeg.get(); + const { asFragment } = render( + + + {}} /> + + , + ); + expect(asFragment()).toMatchSnapshot(); + }); + }); +}); diff --git a/test/components/views/dialogs/devtools/__snapshots__/Event-test.tsx.snap b/test/components/views/dialogs/devtools/__snapshots__/Event-test.tsx.snap new file mode 100644 index 00000000000..d224b791fa5 --- /dev/null +++ b/test/components/views/dialogs/devtools/__snapshots__/Event-test.tsx.snap @@ -0,0 +1,126 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` should render 1`] = ` + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+`; + +exports[` thread context should pre-populate a thread relationship 1`] = ` + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+`; From 49a0db6dfc75b18310909b6cedfdf3e918743c47 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 7 Jul 2023 15:04:52 +0100 Subject: [PATCH 2/7] Simplify --- src/SlashCommands.tsx | 29 +++++++++---------- .../views/dialogs/DevtoolsDialog.tsx | 2 +- .../views/dialogs/devtools/BaseTool.tsx | 2 +- .../views/dialogs/DevtoolsDialog-test.tsx | 2 +- 4 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 01d39bdb4eb..94d3a4b37df 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -113,8 +113,13 @@ export const CommandCategories = { export type RunResult = XOR<{ error: Error }, { promise: Promise }>; -type RunInThreadFn = (this: Command, matrixClient: MatrixClient, roomId: string, threadRootId?: string, args?: string) => RunResult; -type RunFn = (this: Command, matrixClient: MatrixClient, roomId: string, args?: string) => RunResult; +type RunFn = ( + this: Command, + matrixClient: MatrixClient, + roomId: string, + threadId: string | null, + args?: string, +) => RunResult; interface ICommandOpts { command: string; @@ -122,7 +127,7 @@ interface ICommandOpts { args?: string; description: string; analyticsName?: SlashCommandEvent["command"]; - runFn?: RunFn | RunInThreadFn; + runFn?: RunFn; category: string; hideCompletionAfterSpace?: boolean; isEnabled?(matrixClient: MatrixClient | null): boolean; @@ -141,12 +146,11 @@ export class Command { public readonly aliases: string[]; public readonly args?: string; public readonly description: string; - public readonly runFn?: RunFn | RunInThreadFn; + public readonly runFn?: RunFn; public readonly category: string; public readonly hideCompletionAfterSpace: boolean; public readonly renderingTypes?: TimelineRenderingType[]; public readonly analyticsName?: SlashCommandEvent["command"]; - public readonly canReceiveThreadId?: boolean; private readonly _isEnabled?: (matrixClient: MatrixClient | null) => boolean; public constructor(opts: ICommandOpts) { @@ -160,7 +164,6 @@ export class Command { this._isEnabled = opts.isEnabled; this.renderingTypes = opts.renderingTypes; this.analyticsName = opts.analyticsName; - this.canReceiveThreadId = opts.canReceiveThreadId; } public getCommand(): string { @@ -194,11 +197,7 @@ export class Command { }); } - if (this.canReceiveThreadId) { - return this.runFn(matrixClient, roomId, threadId, args); - } else { - return this.runFn(matrixClient, roomId, args); - } + return this.runFn(matrixClient, roomId, threadId, args); } public getUsage(): string { @@ -246,7 +245,7 @@ export const Commands = [ command: "spoiler", args: "", description: _td("Sends the given message as a spoiler"), - runFn: function (cli, roomId, message = "") { + runFn: function (cli, roomId, threadId, message = "") { return successSync(ContentHelpers.makeHtmlMessage(message, `${message}`)); }, category: CommandCategories.messages, @@ -307,7 +306,7 @@ export const Commands = [ command: "plain", args: "", description: _td("Sends a message as plain text, without interpreting it as markdown"), - runFn: function (cli, roomId, messages = "") { + runFn: function (cli, roomId, threadId, messages = "") { return successSync(ContentHelpers.makeTextMessage(messages)); }, category: CommandCategories.messages, @@ -316,7 +315,7 @@ export const Commands = [ command: "html", args: "", description: _td("Sends a message as html, without interpreting it as markdown"), - runFn: function (cli, roomId, messages = "") { + runFn: function (cli, roomId, threadId, messages = "") { return successSync(ContentHelpers.makeHtmlMessage(messages, messages)); }, category: CommandCategories.messages, @@ -1210,7 +1209,7 @@ export const Commands = [ description: _td("Send a bug report with logs"), isEnabled: () => !!SdkConfig.get().bug_report_endpoint_url, args: "", - runFn: function (cli, roomId, args) { + runFn: function (cli, roomId, threadId, args) { return success( Modal.createDialog(BugReportDialog, { initialText: args, diff --git a/src/components/views/dialogs/DevtoolsDialog.tsx b/src/components/views/dialogs/DevtoolsDialog.tsx index 3605370b234..81808c9a8d3 100644 --- a/src/components/views/dialogs/DevtoolsDialog.tsx +++ b/src/components/views/dialogs/DevtoolsDialog.tsx @@ -65,7 +65,7 @@ const Tools: Record = { interface IProps { roomId: string; - threadRootId?: string; + threadRootId: string | null; onFinished(finished?: boolean): void; } diff --git a/src/components/views/dialogs/devtools/BaseTool.tsx b/src/components/views/dialogs/devtools/BaseTool.tsx index f5b0479b100..2334d52eb0a 100644 --- a/src/components/views/dialogs/devtools/BaseTool.tsx +++ b/src/components/views/dialogs/devtools/BaseTool.tsx @@ -88,7 +88,7 @@ export default BaseTool; interface IContext { room: Room; - threadRootId?: string; + threadRootId: string | null; } export const DevtoolsContext = createContext({} as IContext); diff --git a/test/components/views/dialogs/DevtoolsDialog-test.tsx b/test/components/views/dialogs/DevtoolsDialog-test.tsx index 8dc26e89e89..27f1206f922 100644 --- a/test/components/views/dialogs/DevtoolsDialog-test.tsx +++ b/test/components/views/dialogs/DevtoolsDialog-test.tsx @@ -29,7 +29,7 @@ describe("DevtoolsDialog", () => { let cli: MatrixClient; let room: Room; - function getComponent(roomId: string, threadRootId?: string, onFinished = () => true) { + function getComponent(roomId: string, threadRootId: string | null = null, onFinished = () => true) { return render( From 64a5624b4e12a5474911e55745dd05e5761bdb35 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 7 Jul 2023 15:07:00 +0100 Subject: [PATCH 3/7] Remove canReceiveThreadId --- src/SlashCommands.tsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 94d3a4b37df..9573244ad84 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -132,13 +132,6 @@ interface ICommandOpts { hideCompletionAfterSpace?: boolean; isEnabled?(matrixClient: MatrixClient | null): boolean; renderingTypes?: TimelineRenderingType[]; - - /** - * When true, runFn should be a `RunInThreadFn` to receive the actual thread - * ID. If the command is called without the context of a thread, the supplied - * thread ID will be undefined/falsy. - */ - canReceiveThreadId?: boolean; } export class Command { @@ -968,7 +961,6 @@ export const Commands = [ new Command({ command: "devtools", description: _td("Opens the Developer Tools dialog"), - canReceiveThreadId: true, runFn: function (cli, roomId, threadRootId) { Modal.createDialog(DevtoolsDialog, { roomId, threadRootId }, "mx_DevtoolsDialog_wrapper"); return success(); From 79f3e721015ed9e9aad69a5ae6f81c7d7efaeaf9 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 7 Jul 2023 15:07:28 +0100 Subject: [PATCH 4/7] Iterate --- src/SlashCommands.tsx | 70 +++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 9573244ad84..74aaa0340b3 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -247,7 +247,7 @@ export const Commands = [ command: "shrug", args: "", description: _td("Prepends ¯\\_(ツ)_/¯ to a plain-text message"), - runFn: function (cli, roomId, args) { + runFn: function (cli, roomId, threadId, args) { let message = "¯\\_(ツ)_/¯"; if (args) { message = message + " " + args; @@ -260,7 +260,7 @@ export const Commands = [ command: "tableflip", args: "", description: _td("Prepends (╯°□°)╯︵ ┻━┻ to a plain-text message"), - runFn: function (cli, roomId, args) { + runFn: function (cli, roomId, threadId, args) { let message = "(╯°□°)╯︵ ┻━┻"; if (args) { message = message + " " + args; @@ -273,7 +273,7 @@ export const Commands = [ command: "unflip", args: "", description: _td("Prepends ┬──┬ ノ( ゜-゜ノ) to a plain-text message"), - runFn: function (cli, roomId, args) { + runFn: function (cli, roomId, threadId, args) { let message = "┬──┬ ノ( ゜-゜ノ)"; if (args) { message = message + " " + args; @@ -286,7 +286,7 @@ export const Commands = [ command: "lenny", args: "", description: _td("Prepends ( ͡° ͜ʖ ͡°) to a plain-text message"), - runFn: function (cli, roomId, args) { + runFn: function (cli, roomId, threadId, args) { let message = "( ͡° ͜ʖ ͡°)"; if (args) { message = message + " " + args; @@ -318,7 +318,7 @@ export const Commands = [ args: "", description: _td("Upgrades a room to a new version"), isEnabled: (cli) => !isCurrentLocalRoom(cli), - runFn: function (cli, roomId, args) { + runFn: function (cli, roomId, threadId, args) { if (args) { const room = cli.getRoom(roomId); if (!room?.currentState.mayClientSendStateEvent("m.room.tombstone", cli)) { @@ -352,7 +352,7 @@ export const Commands = [ args: "", description: _td("Jump to the given date in the timeline"), isEnabled: () => SettingsStore.getValue("feature_jump_to_date"), - runFn: function (cli, roomId, args) { + runFn: function (cli, roomId, threadId, args) { if (args) { return success( (async (): Promise => { @@ -393,7 +393,7 @@ export const Commands = [ command: "nick", args: "", description: _td("Changes your display nickname"), - runFn: function (cli, roomId, args) { + runFn: function (cli, roomId, threadId, args) { if (args) { return success(cli.setDisplayName(args)); } @@ -408,7 +408,7 @@ export const Commands = [ args: "", description: _td("Changes your display nickname in the current room only"), isEnabled: (cli) => !isCurrentLocalRoom(cli), - runFn: function (cli, roomId, args) { + runFn: function (cli, roomId, threadId, args) { if (args) { const ev = cli.getRoom(roomId)?.currentState.getStateEvents("m.room.member", cli.getSafeUserId()); const content = { @@ -427,7 +427,7 @@ export const Commands = [ args: "[]", description: _td("Changes the avatar of the current room"), isEnabled: (cli) => !isCurrentLocalRoom(cli), - runFn: function (cli, roomId, args) { + runFn: function (cli, roomId, threadId, args) { let promise = Promise.resolve(args ?? null); if (!args) { promise = singleMxcUpload(cli); @@ -448,7 +448,7 @@ export const Commands = [ args: "[]", description: _td("Changes your profile picture in this current room only"), isEnabled: (cli) => !isCurrentLocalRoom(cli), - runFn: function (cli, roomId, args) { + runFn: function (cli, roomId, threadId, args) { const room = cli.getRoom(roomId); const userId = cli.getSafeUserId(); @@ -476,7 +476,7 @@ export const Commands = [ command: "myavatar", args: "[]", description: _td("Changes your profile picture in all rooms"), - runFn: function (cli, roomId, args) { + runFn: function (cli, roomId, threadId, args) { let promise = Promise.resolve(args ?? null); if (!args) { promise = singleMxcUpload(cli); @@ -497,10 +497,10 @@ export const Commands = [ args: "[]", description: _td("Gets or sets the room topic"), isEnabled: (cli) => !isCurrentLocalRoom(cli), - runFn: function (cli, roomId, args) { + runFn: function (cli, roomId, threadId, args) { if (args) { const html = htmlSerializeFromMdIfNeeded(args, { forceHTML: false }); - return success(cli.setRoomTopic(roomId, args, html)); + return success(cli.setRoomTopic(roomId, threadId, args, html)); } const room = cli.getRoom(roomId); if (!room) { @@ -535,9 +535,9 @@ export const Commands = [ args: "", description: _td("Sets the room name"), isEnabled: (cli) => !isCurrentLocalRoom(cli), - runFn: function (cli, roomId, args) { + runFn: function (cli, roomId, threadId, args) { if (args) { - return success(cli.setRoomName(roomId, args)); + return success(cli.setRoomName(roomId, threadId, args)); } return reject(this.getUsage()); }, @@ -550,7 +550,7 @@ export const Commands = [ description: _td("Invites user with given id to current room"), analyticsName: "Invite", isEnabled: (cli) => !isCurrentLocalRoom(cli) && shouldShowComponent(UIComponent.InviteUsers), - runFn: function (cli, roomId, args) { + runFn: function (cli, roomId, threadId, args) { if (args) { const [address, reason] = args.split(/\s+(.+)/); if (address) { @@ -627,7 +627,7 @@ export const Commands = [ aliases: ["j", "goto"], args: "", description: _td("Joins room with given address"), - runFn: function (cli, roomId, args) { + runFn: function (cli, roomId, threadId, args) { if (args) { // Note: we support 2 versions of this command. The first is // the public-facing one for most users and the other is a @@ -740,7 +740,7 @@ export const Commands = [ description: _td("Leave room"), analyticsName: "Part", isEnabled: (cli) => !isCurrentLocalRoom(cli), - runFn: function (cli, roomId, args) { + runFn: function (cli, roomId, threadId, args) { let targetRoomId: string | undefined; if (args) { const matches = args.match(/^(\S+)$/); @@ -780,7 +780,7 @@ export const Commands = [ args: " [reason]", description: _td("Removes user with given id from this room"), isEnabled: (cli) => !isCurrentLocalRoom(cli), - runFn: function (cli, roomId, args) { + runFn: function (cli, roomId, threadId, args) { if (args) { const matches = args.match(/^(\S+?)( +(.*))?$/); if (matches) { @@ -797,7 +797,7 @@ export const Commands = [ args: " [reason]", description: _td("Bans user with given id"), isEnabled: (cli) => !isCurrentLocalRoom(cli), - runFn: function (cli, roomId, args) { + runFn: function (cli, roomId, threadId, args) { if (args) { const matches = args.match(/^(\S+?)( +(.*))?$/); if (matches) { @@ -814,7 +814,7 @@ export const Commands = [ args: "", description: _td("Unbans user with given ID"), isEnabled: (cli) => !isCurrentLocalRoom(cli), - runFn: function (cli, roomId, args) { + runFn: function (cli, roomId, threadId, args) { if (args) { const matches = args.match(/^(\S+)$/); if (matches) { @@ -831,7 +831,7 @@ export const Commands = [ command: "ignore", args: "", description: _td("Ignores a user, hiding their messages from you"), - runFn: function (cli, roomId, args) { + runFn: function (cli, roomId, threadId, args) { if (args) { const matches = args.match(/^(@[^:]+:\S+)$/); if (matches) { @@ -860,7 +860,7 @@ export const Commands = [ command: "unignore", args: "", description: _td("Stops ignoring a user, showing their messages going forward"), - runFn: function (cli, roomId, args) { + runFn: function (cli, roomId, threadId, args) { if (args) { const matches = args.match(/(^@[^:]+:\S+$)/); if (matches) { @@ -891,7 +891,7 @@ export const Commands = [ args: " []", description: _td("Define the power level of a user"), isEnabled: canAffectPowerlevels, - runFn: function (cli, roomId, args) { + runFn: function (cli, roomId, threadId, args) { if (args) { const matches = args.match(/^(\S+?)( +(-?\d+))?$/); let powerLevel = 50; // default power level for op @@ -932,7 +932,7 @@ export const Commands = [ args: "", description: _td("Deops user with given id"), isEnabled: canAffectPowerlevels, - runFn: function (cli, roomId, args) { + runFn: function (cli, roomId, threadId, args) { if (args) { const matches = args.match(/^(\S+)$/); if (matches) { @@ -950,7 +950,7 @@ export const Commands = [ if (!powerLevelEvent?.getContent().users[args]) { return reject(new UserFriendlyError("Could not find user in room")); } - return success(cli.setPowerLevel(roomId, args, undefined, powerLevelEvent)); + return success(cli.setPowerLevel(roomId, threadId, args, undefined, powerLevelEvent)); } } return reject(this.getUsage()); @@ -1028,7 +1028,7 @@ export const Commands = [ command: "verify", args: " ", description: _td("Verifies a user, session, and pubkey tuple"), - runFn: function (cli, roomId, args) { + runFn: function (cli, roomId, threadId, args) { if (args) { const matches = args.match(/^(\S+) +(\S+) +(\S+)$/); if (matches) { @@ -1150,7 +1150,7 @@ export const Commands = [ command: "rainbow", description: _td("Sends the given message coloured as a rainbow"), args: "", - runFn: function (cli, roomId, args) { + runFn: function (cli, roomId, threadId, args) { if (!args) return reject(this.getUsage()); return successSync(ContentHelpers.makeHtmlMessage(args, textToHtmlRainbow(args))); }, @@ -1160,7 +1160,7 @@ export const Commands = [ command: "rainbowme", description: _td("Sends the given emote coloured as a rainbow"), args: "", - runFn: function (cli, roomId, args) { + runFn: function (cli, roomId, threadId, args) { if (!args) return reject(this.getUsage()); return successSync(ContentHelpers.makeHtmlEmote(args, textToHtmlRainbow(args))); }, @@ -1272,7 +1272,7 @@ export const Commands = [ command: "msg", description: _td("Sends a message to the given user"), args: " []", - runFn: function (cli, roomId, args) { + runFn: function (cli, roomId, threadId, args) { if (args) { // matches the first whitespace delimited group and then the rest of the string const matches = args.match(/^(\S+?)(?: +(.*))?$/s); @@ -1308,7 +1308,7 @@ export const Commands = [ description: _td("Places the call in the current room on hold"), category: CommandCategories.other, isEnabled: (cli) => !isCurrentLocalRoom(cli), - runFn: function (cli, roomId, args) { + runFn: function (cli, roomId, threadId, args) { const call = LegacyCallHandler.instance.getCallForRoom(roomId); if (!call) { return reject(new UserFriendlyError("No active call in this room")); @@ -1323,7 +1323,7 @@ export const Commands = [ description: _td("Takes the call in the current room off hold"), category: CommandCategories.other, isEnabled: (cli) => !isCurrentLocalRoom(cli), - runFn: function (cli, roomId, args) { + runFn: function (cli, roomId, threadId, args) { const call = LegacyCallHandler.instance.getCallForRoom(roomId); if (!call) { return reject(new UserFriendlyError("No active call in this room")); @@ -1338,7 +1338,7 @@ export const Commands = [ description: _td("Converts the room to a DM"), category: CommandCategories.other, isEnabled: (cli) => !isCurrentLocalRoom(cli), - runFn: function (cli, roomId, args) { + runFn: function (cli, roomId, threadId, args) { const room = cli.getRoom(roomId); if (!room) return reject(new UserFriendlyError("Could not find room")); return success(guessAndSetDMRoom(room, true)); @@ -1350,7 +1350,7 @@ export const Commands = [ description: _td("Converts the DM to a room"), category: CommandCategories.other, isEnabled: (cli) => !isCurrentLocalRoom(cli), - runFn: function (cli, roomId, args) { + runFn: function (cli, roomId, threadId, args) { const room = cli.getRoom(roomId); if (!room) return reject(new UserFriendlyError("Could not find room")); return success(guessAndSetDMRoom(room, false)); @@ -1373,7 +1373,7 @@ export const Commands = [ command: effect.command, description: effect.description(), args: "", - runFn: function (cli, roomId, args) { + runFn: function (cli, roomId, threadId, args) { let content: IContent; if (!args) { content = ContentHelpers.makeEmoteMessage(effect.fallbackMessage()); From 6a8d1eb3b8ae33b924e5e7669acbc45c33626b71 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 7 Jul 2023 15:08:56 +0100 Subject: [PATCH 5/7] Iterate --- src/SlashCommands.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 74aaa0340b3..324ca3b7ff6 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -975,7 +975,7 @@ export const Commands = [ SettingsStore.getValue(UIFeature.Widgets) && shouldShowComponent(UIComponent.AddIntegrations) && !isCurrentLocalRoom(cli), - runFn: function (cli, roomId, widgetUrl) { + runFn: function (cli, roomId, threadId, widgetUrl) { if (!widgetUrl) { return reject(new UserFriendlyError("Please supply a widget URL or embed code")); } @@ -1180,7 +1180,7 @@ export const Commands = [ description: _td("Displays information about a user"), args: "", isEnabled: (cli) => !isCurrentLocalRoom(cli), - runFn: function (cli, roomId, userId) { + runFn: function (cli, roomId, threadId, userId) { if (!userId || !userId.startsWith("@") || !userId.includes(":")) { return reject(this.getUsage()); } @@ -1236,7 +1236,7 @@ export const Commands = [ command: "query", description: _td("Opens chat with the given user"), args: "", - runFn: function (cli, roomId, userId) { + runFn: function (cli, roomId, threadId, userId) { // easter-egg for now: look up phone numbers through the thirdparty API // (very dumb phone number detection...) const isPhoneNumber = userId && /^\+?[0123456789]+$/.test(userId); From d84ca1a1f7fd1b075947778aa78263899e496a05 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 7 Jul 2023 15:09:21 +0100 Subject: [PATCH 6/7] Iterate --- src/SlashCommands.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 324ca3b7ff6..de2ec6adbc6 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -500,7 +500,7 @@ export const Commands = [ runFn: function (cli, roomId, threadId, args) { if (args) { const html = htmlSerializeFromMdIfNeeded(args, { forceHTML: false }); - return success(cli.setRoomTopic(roomId, threadId, args, html)); + return success(cli.setRoomTopic(roomId, args, html)); } const room = cli.getRoom(roomId); if (!room) { @@ -537,7 +537,7 @@ export const Commands = [ isEnabled: (cli) => !isCurrentLocalRoom(cli), runFn: function (cli, roomId, threadId, args) { if (args) { - return success(cli.setRoomName(roomId, threadId, args)); + return success(cli.setRoomName(roomId, args)); } return reject(this.getUsage()); }, @@ -950,7 +950,7 @@ export const Commands = [ if (!powerLevelEvent?.getContent().users[args]) { return reject(new UserFriendlyError("Could not find user in room")); } - return success(cli.setPowerLevel(roomId, threadId, args, undefined, powerLevelEvent)); + return success(cli.setPowerLevel(roomId, args, undefined, powerLevelEvent)); } } return reject(this.getUsage()); From 31cb6b21b1a0fe09a16ccc28f06306cc01a11d4c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 7 Jul 2023 15:12:41 +0100 Subject: [PATCH 7/7] Iterate --- src/components/views/dialogs/DevtoolsDialog.tsx | 2 +- src/components/views/dialogs/devtools/BaseTool.tsx | 2 +- test/components/views/dialogs/devtools/Event-test.tsx | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/views/dialogs/DevtoolsDialog.tsx b/src/components/views/dialogs/DevtoolsDialog.tsx index 81808c9a8d3..75536664255 100644 --- a/src/components/views/dialogs/DevtoolsDialog.tsx +++ b/src/components/views/dialogs/DevtoolsDialog.tsx @@ -65,7 +65,7 @@ const Tools: Record = { interface IProps { roomId: string; - threadRootId: string | null; + threadRootId?: string | null; onFinished(finished?: boolean): void; } diff --git a/src/components/views/dialogs/devtools/BaseTool.tsx b/src/components/views/dialogs/devtools/BaseTool.tsx index 2334d52eb0a..6aa95e138b4 100644 --- a/src/components/views/dialogs/devtools/BaseTool.tsx +++ b/src/components/views/dialogs/devtools/BaseTool.tsx @@ -88,7 +88,7 @@ export default BaseTool; interface IContext { room: Room; - threadRootId: string | null; + threadRootId?: string | null; } export const DevtoolsContext = createContext({} as IContext); diff --git a/test/components/views/dialogs/devtools/Event-test.tsx b/test/components/views/dialogs/devtools/Event-test.tsx index b135573a0ee..ee9d84f29c3 100644 --- a/test/components/views/dialogs/devtools/Event-test.tsx +++ b/test/components/views/dialogs/devtools/Event-test.tsx @@ -31,7 +31,7 @@ describe("", () => { }); it("should render", () => { - const cli = MatrixClientPeg.get(); + const cli = MatrixClientPeg.safeGet(); const { asFragment } = render( ", () => { describe("thread context", () => { it("should pre-populate a thread relationship", () => { - const cli = MatrixClientPeg.get(); + const cli = MatrixClientPeg.safeGet(); const { asFragment } = render(