Improve types for sendEvent (#12335)

This commit is contained in:
Michael Telatynski 2024-03-25 12:48:48 +00:00 committed by GitHub
parent 4941327c78
commit ef2bd7ae04
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 209 additions and 99 deletions

View file

@ -32,7 +32,9 @@ import type {
UploadOpts, UploadOpts,
Upload, Upload,
StateEvents, StateEvents,
TimelineEvents,
} from "matrix-js-sdk/src/matrix"; } from "matrix-js-sdk/src/matrix";
import type { RoomMessageEventContent } from "matrix-js-sdk/src/types";
import { Credentials } from "../plugins/homeserver"; import { Credentials } from "../plugins/homeserver";
export class Client { export class Client {
@ -98,7 +100,12 @@ export class Client {
const client = await this.prepareClient(); const client = await this.prepareClient();
return client.evaluate( return client.evaluate(
async (client, { roomId, threadId, eventType, content }) => { async (client, { roomId, threadId, eventType, content }) => {
return client.sendEvent(roomId, threadId, eventType, content); return client.sendEvent(
roomId,
threadId,
eventType as keyof TimelineEvents,
content as TimelineEvents[keyof TimelineEvents],
);
}, },
{ roomId, threadId, eventType, content }, { roomId, threadId, eventType, content },
); );
@ -125,7 +132,7 @@ export class Client {
const client = await this.prepareClient(); const client = await this.prepareClient();
return client.evaluate( return client.evaluate(
(client, { roomId, content, threadId }) => { (client, { roomId, content, threadId }) => {
return client.sendMessage(roomId, threadId, content); return client.sendMessage(roomId, threadId, content as RoomMessageEventContent);
}, },
{ {
roomId, roomId,

View file

@ -19,8 +19,9 @@ import type { BLURHASH_FIELD } from "../utils/image-media";
import type { JitsiCallMemberEventType, JitsiCallMemberContent } from "../call-types"; import type { JitsiCallMemberEventType, JitsiCallMemberContent } from "../call-types";
import type { ILayoutStateEvent, WIDGET_LAYOUT_EVENT_TYPE } from "../stores/widgets/types"; import type { ILayoutStateEvent, WIDGET_LAYOUT_EVENT_TYPE } from "../stores/widgets/types";
import type { VoiceBroadcastInfoEventContent, VoiceBroadcastInfoEventType } from "../voice-broadcast/types"; import type { VoiceBroadcastInfoEventContent, VoiceBroadcastInfoEventType } from "../voice-broadcast/types";
import type { EncryptedFile } from "matrix-js-sdk/src/types";
// Matrix JS SDK extensions // Extend Matrix JS SDK types via Typescript declaration merging to support unspecced event fields and types
declare module "matrix-js-sdk/src/types" { declare module "matrix-js-sdk/src/types" {
export interface FileInfo { export interface FileInfo {
/** /**
@ -50,4 +51,35 @@ declare module "matrix-js-sdk/src/types" {
}; };
"m.room.bot.options": unknown; "m.room.bot.options": unknown;
} }
export interface TimelineEvents {
"io.element.performance_metric": {
"io.element.performance_metrics": {
forEventId: string;
responseTs: number;
kind: "send_time";
};
};
}
export interface AudioContent {
// MSC1767 + Ideals of MSC2516 as MSC3245
// https://github.com/matrix-org/matrix-doc/pull/3245
"org.matrix.msc1767.text"?: string;
"org.matrix.msc1767.file"?: {
url?: string;
file?: EncryptedFile;
name: string;
mimetype: string;
size: number;
};
"org.matrix.msc1767.audio"?: {
duration: number;
// https://github.com/matrix-org/matrix-doc/pull/3246
waveform?: number[];
};
"org.matrix.msc3245.voice"?: {};
"io.element.voice_broadcast_chunk"?: { sequence: number };
}
} }

View file

@ -622,7 +622,7 @@ export default class ContentMessages {
if (upload.cancelled) throw new UploadCanceledError(); if (upload.cancelled) throw new UploadCanceledError();
const threadId = relation?.rel_type === THREAD_RELATION_TYPE.name ? relation.event_id : null; const threadId = relation?.rel_type === THREAD_RELATION_TYPE.name ? relation.event_id : null;
const response = await matrixClient.sendMessage(roomId, threadId ?? null, content); const response = await matrixClient.sendMessage(roomId, threadId ?? null, content as MediaEventContent);
if (SettingsStore.getValue("Performance.addSendMessageTimingMetadata")) { if (SettingsStore.getValue("Performance.addSendMessageTimingMetadata")) {
sendRoundTripMetric(matrixClient, roomId, response.event_id); sendRoundTripMetric(matrixClient, roomId, response.event_id);

View file

@ -26,6 +26,7 @@ import {
RoomType, RoomType,
SyncStateData, SyncStateData,
SyncState, SyncState,
TimelineEvents,
} from "matrix-js-sdk/src/matrix"; } from "matrix-js-sdk/src/matrix";
import { defer, IDeferred, QueryDict } from "matrix-js-sdk/src/utils"; import { defer, IDeferred, QueryDict } from "matrix-js-sdk/src/utils";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
@ -1930,7 +1931,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
if (!cli) return; if (!cli) return;
cli.sendEvent(roomId, event.getType(), event.getContent()).then(() => { cli.sendEvent(roomId, event.getType() as keyof TimelineEvents, event.getContent()).then(() => {
dis.dispatch({ action: "message_sent" }); dis.dispatch({ action: "message_sent" });
}); });
} }

View file

@ -15,7 +15,7 @@ limitations under the License.
*/ */
import React from "react"; import React from "react";
import { MatrixEvent, MatrixClient } from "matrix-js-sdk/src/matrix"; import { MatrixEvent, MatrixClient, TimelineEvents } from "matrix-js-sdk/src/matrix";
import { PollEndEvent } from "matrix-js-sdk/src/extensible_events_v1/PollEndEvent"; import { PollEndEvent } from "matrix-js-sdk/src/extensible_events_v1/PollEndEvent";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
@ -51,7 +51,11 @@ export default class EndPollDialog extends React.Component<IProps> {
const endEvent = PollEndEvent.from(this.props.event.getId()!, message).serialize(); const endEvent = PollEndEvent.from(this.props.event.getId()!, message).serialize();
await this.props.matrixClient.sendEvent(this.props.event.getRoomId()!, endEvent.type, endEvent.content); await this.props.matrixClient.sendEvent(
this.props.event.getRoomId()!,
endEvent.type as keyof TimelineEvents,
endEvent.content as TimelineEvents[keyof TimelineEvents],
);
} catch (e) { } catch (e) {
console.error("Failed to submit poll response event:", e); console.error("Failed to submit poll response event:", e);
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {

View file

@ -28,6 +28,7 @@ import {
LocationAssetType, LocationAssetType,
M_TIMESTAMP, M_TIMESTAMP,
M_BEACON, M_BEACON,
TimelineEvents,
} from "matrix-js-sdk/src/matrix"; } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types"; import { KnownMembership } from "matrix-js-sdk/src/types";
@ -80,10 +81,10 @@ interface IProps {
onFinished(): void; onFinished(): void;
} }
interface IEntryProps { interface IEntryProps<K extends keyof TimelineEvents> {
room: Room; room: Room;
type: EventType | string; type: K;
content: IContent; content: TimelineEvents[K];
matrixClient: MatrixClient; matrixClient: MatrixClient;
onFinished(success: boolean): void; onFinished(success: boolean): void;
} }
@ -95,7 +96,7 @@ enum SendState {
Failed, Failed,
} }
const Entry: React.FC<IEntryProps> = ({ room, type, content, matrixClient: cli, onFinished }) => { const Entry: React.FC<IEntryProps<any>> = ({ room, type, content, matrixClient: cli, onFinished }) => {
const [sendState, setSendState] = useState<SendState>(SendState.CanSend); const [sendState, setSendState] = useState<SendState>(SendState.CanSend);
const [onFocus, isActive, ref] = useRovingTabIndex<HTMLDivElement>(); const [onFocus, isActive, ref] = useRovingTabIndex<HTMLDivElement>();

View file

@ -32,6 +32,12 @@ import Field from "../elements/Field";
import Spinner from "../elements/Spinner"; import Spinner from "../elements/Spinner";
import LabelledCheckbox from "../elements/LabelledCheckbox"; import LabelledCheckbox from "../elements/LabelledCheckbox";
declare module "matrix-js-sdk/src/types" {
interface TimelineEvents {
[ABUSE_EVENT_TYPE]: AbuseEventContent;
}
}
interface IProps { interface IProps {
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
onFinished(report?: boolean): void; onFinished(report?: boolean): void;
@ -56,7 +62,16 @@ const MODERATED_BY_STATE_EVENT_TYPE = [
*/ */
]; ];
const ABUSE_EVENT_TYPE = "org.matrix.msc3215.abuse.report"; export const ABUSE_EVENT_TYPE = "org.matrix.msc3215.abuse.report";
interface AbuseEventContent {
event_id: string;
room_id: string;
moderated_by_id: string;
nature?: ExtendedNature;
reporter: string;
comment: string;
}
// Standard abuse natures. // Standard abuse natures.
enum Nature { enum Nature {
@ -250,13 +265,13 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
} }
await client.sendEvent(dmRoomId, ABUSE_EVENT_TYPE, { await client.sendEvent(dmRoomId, ABUSE_EVENT_TYPE, {
event_id: ev.getId(), event_id: ev.getId()!,
room_id: ev.getRoomId(), room_id: ev.getRoomId()!,
moderated_by_id: this.moderation.moderationRoomId, moderated_by_id: this.moderation.moderationRoomId,
nature, nature,
reporter: client.getUserId(), reporter: client.getUserId()!,
comment: this.state.reason.trim(), comment: this.state.reason.trim(),
}); } satisfies AbuseEventContent);
} else { } else {
// Report to homeserver admin through the dedicated Matrix API. // Report to homeserver admin through the dedicated Matrix API.
await client.reportEvent(ev.getRoomId()!, ev.getId()!, -100, this.state.reason.trim()); await client.reportEvent(ev.getRoomId()!, ev.getId()!, -100, this.state.reason.trim());

View file

@ -16,7 +16,7 @@ limitations under the License.
*/ */
import React, { ChangeEvent, ReactNode, useContext, useMemo, useRef, useState } from "react"; import React, { ChangeEvent, ReactNode, useContext, useMemo, useRef, useState } from "react";
import { IContent, MatrixEvent } from "matrix-js-sdk/src/matrix"; import { IContent, MatrixEvent, TimelineEvents } from "matrix-js-sdk/src/matrix";
import { _t, _td, TranslationKey } from "../../../../languageHandler"; import { _t, _td, TranslationKey } from "../../../../languageHandler";
import Field from "../../elements/Field"; import Field from "../../elements/Field";
@ -32,7 +32,7 @@ export const stringify = (object: object): string => {
interface IEventEditorProps extends Pick<IDevtoolsProps, "onBack"> { interface IEventEditorProps extends Pick<IDevtoolsProps, "onBack"> {
fieldDefs: IFieldDef[]; // immutable fieldDefs: IFieldDef[]; // immutable
defaultContent?: string; defaultContent?: string;
onSend(fields: string[], content?: IContent): Promise<unknown>; onSend(fields: string[], content: IContent): Promise<unknown>;
} }
interface IFieldDef { interface IFieldDef {
@ -180,8 +180,8 @@ export const TimelineEventEditor: React.FC<IEditorProps> = ({ mxEvent, onBack })
const fields = useMemo(() => [eventTypeField(mxEvent?.getType())], [mxEvent]); const fields = useMemo(() => [eventTypeField(mxEvent?.getType())], [mxEvent]);
const onSend = ([eventType]: string[], content?: IContent): Promise<unknown> => { const onSend = ([eventType]: string[], content: TimelineEvents[keyof TimelineEvents]): Promise<unknown> => {
return cli.sendEvent(context.room.roomId, eventType, content || {}); return cli.sendEvent(context.room.roomId, eventType as keyof TimelineEvents, content);
}; };
let defaultContent: string | undefined; let defaultContent: string | undefined;

View file

@ -23,6 +23,7 @@ import {
M_POLL_KIND_UNDISCLOSED, M_POLL_KIND_UNDISCLOSED,
M_POLL_START, M_POLL_START,
IPartialEvent, IPartialEvent,
TimelineEvents,
} from "matrix-js-sdk/src/matrix"; } from "matrix-js-sdk/src/matrix";
import { PollStartEvent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent"; import { PollStartEvent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
@ -166,8 +167,8 @@ export default class PollCreateDialog extends ScrollableBaseModal<IProps, IState
this.matrixClient.sendEvent( this.matrixClient.sendEvent(
actualRoomId, actualRoomId,
this.props.threadId ?? null, this.props.threadId ?? null,
pollEvent.type, pollEvent.type as keyof TimelineEvents,
pollEvent.content, pollEvent.content as TimelineEvents[keyof TimelineEvents],
), ),
this.matrixClient, this.matrixClient,
) )

View file

@ -108,7 +108,7 @@ class ReactionPicker extends React.Component<IProps, IState> {
MatrixClientPeg.safeGet().sendEvent(this.props.mxEvent.getRoomId()!, EventType.Reaction, { MatrixClientPeg.safeGet().sendEvent(this.props.mxEvent.getRoomId()!, EventType.Reaction, {
"m.relates_to": { "m.relates_to": {
rel_type: RelationType.Annotation, rel_type: RelationType.Annotation,
event_id: this.props.mxEvent.getId(), event_id: this.props.mxEvent.getId()!,
key: reaction, key: reaction,
}, },
}); });

View file

@ -16,13 +16,13 @@ limitations under the License.
import { import {
MatrixClient, MatrixClient,
IContent,
IEventRelation, IEventRelation,
MatrixError, MatrixError,
THREAD_RELATION_TYPE, THREAD_RELATION_TYPE,
ContentHelpers, ContentHelpers,
LocationAssetType, LocationAssetType,
} from "matrix-js-sdk/src/matrix"; } from "matrix-js-sdk/src/matrix";
import { RoomMessageEventContent } from "matrix-js-sdk/src/types";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
@ -146,7 +146,7 @@ export const shareLocation =
timestamp, timestamp,
undefined, undefined,
assetType, assetType,
) as IContent; ) as RoomMessageEventContent;
await doMaybeLocalRoomAction( await doMaybeLocalRoomAction(
roomId, roomId,
(actualRoomId: string) => client.sendMessage(actualRoomId, threadId, content), (actualRoomId: string) => client.sendMessage(actualRoomId, threadId, content),

View file

@ -25,6 +25,7 @@ import {
M_POLL_KIND_DISCLOSED, M_POLL_KIND_DISCLOSED,
M_POLL_RESPONSE, M_POLL_RESPONSE,
M_POLL_START, M_POLL_START,
TimelineEvents,
} from "matrix-js-sdk/src/matrix"; } from "matrix-js-sdk/src/matrix";
import { RelatedRelations } from "matrix-js-sdk/src/models/related-relations"; import { RelatedRelations } from "matrix-js-sdk/src/models/related-relations";
import { PollStartEvent, PollAnswerSubevent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent"; import { PollStartEvent, PollAnswerSubevent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
@ -225,7 +226,13 @@ export default class MPollBody extends React.Component<IBodyProps, IState> {
const response = PollResponseEvent.from([answerId], this.props.mxEvent.getId()!).serialize(); const response = PollResponseEvent.from([answerId], this.props.mxEvent.getId()!).serialize();
this.context.sendEvent(this.props.mxEvent.getRoomId()!, response.type, response.content).catch((e: any) => { this.context
.sendEvent(
this.props.mxEvent.getRoomId()!,
response.type as keyof TimelineEvents,
response.content as TimelineEvents[keyof TimelineEvents],
)
.catch((e: any) => {
console.error("Failed to submit poll response event:", e); console.error("Failed to submit poll response event:", e);
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {

View file

@ -16,7 +16,7 @@ limitations under the License.
import React from "react"; import React from "react";
import classNames from "classnames"; import classNames from "classnames";
import { MatrixEvent } from "matrix-js-sdk/src/matrix"; import { EventType, MatrixEvent, RelationType } from "matrix-js-sdk/src/matrix";
import { mediaFromMxc } from "../../../customisations/Media"; import { mediaFromMxc } from "../../../customisations/Media";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
@ -26,6 +26,7 @@ import ReactionsRowButtonTooltip from "./ReactionsRowButtonTooltip";
import AccessibleButton from "../elements/AccessibleButton"; import AccessibleButton from "../elements/AccessibleButton";
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { REACTION_SHORTCODE_KEY } from "./ReactionsRow"; import { REACTION_SHORTCODE_KEY } from "./ReactionsRow";
export interface IProps { export interface IProps {
// The event we're displaying reactions for // The event we're displaying reactions for
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
@ -62,10 +63,10 @@ export default class ReactionsRowButton extends React.PureComponent<IProps, ISta
if (myReactionEvent) { if (myReactionEvent) {
this.context.redactEvent(mxEvent.getRoomId()!, myReactionEvent.getId()!); this.context.redactEvent(mxEvent.getRoomId()!, myReactionEvent.getId()!);
} else { } else {
this.context.sendEvent(mxEvent.getRoomId()!, "m.reaction", { this.context.sendEvent(mxEvent.getRoomId()!, EventType.Reaction, {
"m.relates_to": { "m.relates_to": {
rel_type: "m.annotation", rel_type: RelationType.Annotation,
event_id: mxEvent.getId(), event_id: mxEvent.getId()!,
key: content, key: content,
}, },
}); });

View file

@ -16,9 +16,10 @@ limitations under the License.
import React, { createRef, KeyboardEvent } from "react"; import React, { createRef, KeyboardEvent } from "react";
import classNames from "classnames"; import classNames from "classnames";
import { EventStatus, IContent, MatrixEvent, Room, MsgType } from "matrix-js-sdk/src/matrix"; import { EventStatus, MatrixEvent, Room, MsgType } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { Composer as ComposerEvent } from "@matrix-org/analytics-events/types/typescript/Composer"; import { Composer as ComposerEvent } from "@matrix-org/analytics-events/types/typescript/Composer";
import { ReplacementEvent, RoomMessageEventContent, RoomMessageTextEventContent } from "matrix-js-sdk/src/types";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import dis from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher";
@ -70,7 +71,11 @@ function getTextReplyFallback(mxEvent: MatrixEvent): string {
} }
// exported for tests // exported for tests
export function createEditContent(model: EditorModel, editedEvent: MatrixEvent, replyToEvent?: MatrixEvent): IContent { export function createEditContent(
model: EditorModel,
editedEvent: MatrixEvent,
replyToEvent?: MatrixEvent,
): RoomMessageEventContent {
const isEmote = containsEmote(model); const isEmote = containsEmote(model);
if (isEmote) { if (isEmote) {
model = stripEmoteCommand(model); model = stripEmoteCommand(model);
@ -86,11 +91,11 @@ export function createEditContent(model: EditorModel, editedEvent: MatrixEvent,
const body = textSerialize(model); const body = textSerialize(model);
const newContent: IContent = { const newContent: RoomMessageEventContent = {
msgtype: isEmote ? MsgType.Emote : MsgType.Text, msgtype: isEmote ? MsgType.Emote : MsgType.Text,
body: body, body: body,
}; };
const contentBody: IContent = { const contentBody: RoomMessageTextEventContent & Omit<ReplacementEvent<RoomMessageEventContent>, "m.relates_to"> = {
"msgtype": newContent.msgtype, "msgtype": newContent.msgtype,
"body": `${plainPrefix} * ${body}`, "body": `${plainPrefix} * ${body}`,
"m.new_content": newContent, "m.new_content": newContent,
@ -111,7 +116,7 @@ export function createEditContent(model: EditorModel, editedEvent: MatrixEvent,
attachMentions(editedEvent.sender!.userId, contentBody, model, replyToEvent, editedEvent.getContent()); attachMentions(editedEvent.sender!.userId, contentBody, model, replyToEvent, editedEvent.getContent());
attachRelation(contentBody, { rel_type: "m.replace", event_id: editedEvent.getId() }); attachRelation(contentBody, { rel_type: "m.replace", event_id: editedEvent.getId() });
return contentBody; return contentBody as RoomMessageEventContent;
} }
interface IEditMessageComposerProps extends MatrixClientProps { interface IEditMessageComposerProps extends MatrixClientProps {
@ -142,7 +147,7 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
const editContent = createEditContent(this.model, ev, this.replyToEvent); const editContent = createEditContent(this.model, ev, this.replyToEvent);
this.state = { this.state = {
saveDisabled: !isRestored || !this.isContentModified(editContent["m.new_content"]), saveDisabled: !isRestored || !this.isContentModified(editContent["m.new_content"]!),
}; };
window.addEventListener("beforeunload", this.saveStoredEditorState); window.addEventListener("beforeunload", this.saveStoredEditorState);
@ -284,14 +289,16 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
localStorage.setItem(this.editorStateKey, JSON.stringify(item)); localStorage.setItem(this.editorStateKey, JSON.stringify(item));
}; };
private isContentModified(newContent: IContent): boolean { private isContentModified(newContent: RoomMessageEventContent): boolean {
// if nothing has changed then bail // if nothing has changed then bail
const oldContent = this.props.editState.getEvent().getContent(); const oldContent = this.props.editState.getEvent().getContent<RoomMessageEventContent>();
if ( if (
oldContent["msgtype"] === newContent["msgtype"] && oldContent["msgtype"] === newContent["msgtype"] &&
oldContent["body"] === newContent["body"] && oldContent["body"] === newContent["body"] &&
oldContent["format"] === newContent["format"] && (oldContent as RoomMessageTextEventContent)["format"] ===
oldContent["formatted_body"] === newContent["formatted_body"] (newContent as RoomMessageTextEventContent)["format"] &&
(oldContent as RoomMessageTextEventContent)["formatted_body"] ===
(newContent as RoomMessageTextEventContent)["formatted_body"]
) { ) {
return false; return false;
} }
@ -318,7 +325,7 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
this.editorRef.current.replaceEmoticon(position, REGEX_EMOTICON); this.editorRef.current.replaceEmoticon(position, REGEX_EMOTICON);
} }
const editContent = createEditContent(this.model, editedEvent, this.replyToEvent); const editContent = createEditContent(this.model, editedEvent, this.replyToEvent);
const newContent = editContent["m.new_content"]; const newContent = editContent["m.new_content"]!;
let shouldSend = true; let shouldSend = true;
@ -352,7 +359,7 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
} }
if (cmd.category === CommandCategories.messages || cmd.category === CommandCategories.effects) { if (cmd.category === CommandCategories.messages || cmd.category === CommandCategories.effects) {
editContent["m.new_content"] = content; editContent["m.new_content"] = content!;
} else { } else {
shouldSend = false; shouldSend = false;
} }

View file

@ -30,6 +30,7 @@ import {
import { DebouncedFunc, throttle } from "lodash"; import { DebouncedFunc, throttle } from "lodash";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { Composer as ComposerEvent } from "@matrix-org/analytics-events/types/typescript/Composer"; import { Composer as ComposerEvent } from "@matrix-org/analytics-events/types/typescript/Composer";
import { RoomMessageEventContent } from "matrix-js-sdk/src/types";
import dis from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher";
import EditorModel from "../../../editor/model"; import EditorModel from "../../../editor/model";
@ -183,7 +184,7 @@ export function createMessageContent(
relation: IEventRelation | undefined, relation: IEventRelation | undefined,
permalinkCreator?: RoomPermalinkCreator, permalinkCreator?: RoomPermalinkCreator,
includeReplyLegacyFallback = true, includeReplyLegacyFallback = true,
): IContent { ): RoomMessageEventContent {
const isEmote = containsEmote(model); const isEmote = containsEmote(model);
if (isEmote) { if (isEmote) {
model = stripEmoteCommand(model); model = stripEmoteCommand(model);
@ -195,7 +196,7 @@ export function createMessageContent(
const body = textSerialize(model); const body = textSerialize(model);
const content: IContent = { const content: RoomMessageEventContent = {
msgtype: isEmote ? MsgType.Emote : MsgType.Text, msgtype: isEmote ? MsgType.Emote : MsgType.Text,
body: body, body: body,
}; };
@ -432,7 +433,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
MatrixClientPeg.safeGet().sendEvent(lastMessage.getRoomId()!, EventType.Reaction, { MatrixClientPeg.safeGet().sendEvent(lastMessage.getRoomId()!, EventType.Reaction, {
"m.relates_to": { "m.relates_to": {
rel_type: RelationType.Annotation, rel_type: RelationType.Annotation,
event_id: lastMessage.getId(), event_id: lastMessage.getId()!,
key: reaction, key: reaction,
}, },
}); });
@ -475,7 +476,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
const replyToEvent = this.props.replyToEvent; const replyToEvent = this.props.replyToEvent;
let shouldSend = true; let shouldSend = true;
let content: IContent | null = null; let content: RoomMessageEventContent | null = null;
if (!containsEmote(model) && isSlashCommand(this.model)) { if (!containsEmote(model) && isSlashCommand(this.model)) {
const [cmd, args, commandText] = getSlashCommand(this.model); const [cmd, args, commandText] = getSlashCommand(this.model);

View file

@ -16,6 +16,7 @@ limitations under the License.
import { richToPlain, plainToRich } from "@matrix-org/matrix-wysiwyg"; import { richToPlain, plainToRich } from "@matrix-org/matrix-wysiwyg";
import { IContent, IEventRelation, MatrixEvent, MsgType } from "matrix-js-sdk/src/matrix"; import { IContent, IEventRelation, MatrixEvent, MsgType } from "matrix-js-sdk/src/matrix";
import { ReplacementEvent, RoomMessageEventContent, RoomMessageTextEventContent } from "matrix-js-sdk/src/types";
import SettingsStore from "../../../../../settings/SettingsStore"; import SettingsStore from "../../../../../settings/SettingsStore";
import { parsePermalink, RoomPermalinkCreator } from "../../../../../utils/permalinks/Permalinks"; import { parsePermalink, RoomPermalinkCreator } from "../../../../../utils/permalinks/Permalinks";
@ -76,7 +77,7 @@ export async function createMessageContent(
includeReplyLegacyFallback = true, includeReplyLegacyFallback = true,
editedEvent, editedEvent,
}: CreateMessageContentParams, }: CreateMessageContentParams,
): Promise<IContent> { ): Promise<RoomMessageEventContent> {
const isEditing = isMatrixEvent(editedEvent); const isEditing = isMatrixEvent(editedEvent);
const isReply = isEditing ? Boolean(editedEvent.replyEventId) : isMatrixEvent(replyToEvent); const isReply = isEditing ? Boolean(editedEvent.replyEventId) : isMatrixEvent(replyToEvent);
const isReplyAndEditing = isEditing && isReply; const isReplyAndEditing = isEditing && isReply;
@ -100,10 +101,10 @@ export async function createMessageContent(
const bodyPrefix = (isReplyAndEditing && getTextReplyFallback(editedEvent)) || ""; const bodyPrefix = (isReplyAndEditing && getTextReplyFallback(editedEvent)) || "";
const formattedBodyPrefix = (isReplyAndEditing && getHtmlReplyFallback(editedEvent)) || ""; const formattedBodyPrefix = (isReplyAndEditing && getHtmlReplyFallback(editedEvent)) || "";
const content: IContent = { const content = {
msgtype: isEmote ? MsgType.Emote : MsgType.Text, msgtype: isEmote ? MsgType.Emote : MsgType.Text,
body: isEditing ? `${bodyPrefix} * ${body}` : body, body: isEditing ? `${bodyPrefix} * ${body}` : body,
}; } as RoomMessageTextEventContent & ReplacementEvent<RoomMessageTextEventContent>;
// TODO markdown support // TODO markdown support

View file

@ -14,18 +14,22 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { IContent } from "matrix-js-sdk/src/matrix"; import { RoomMessageEventContent, RoomMessageTextEventContent } from "matrix-js-sdk/src/types";
import EditorStateTransfer from "../../../../../utils/EditorStateTransfer"; import EditorStateTransfer from "../../../../../utils/EditorStateTransfer";
export function isContentModified(newContent: IContent, editorStateTransfer: EditorStateTransfer): boolean { export function isContentModified(
newContent: RoomMessageEventContent,
editorStateTransfer: EditorStateTransfer,
): boolean {
// if nothing has changed then bail // if nothing has changed then bail
const oldContent = editorStateTransfer.getEvent().getContent(); const oldContent = editorStateTransfer.getEvent().getContent<RoomMessageEventContent>();
if ( if (
oldContent["msgtype"] === newContent["msgtype"] && oldContent["msgtype"] === newContent["msgtype"] &&
oldContent["body"] === newContent["body"] && oldContent["body"] === newContent["body"] &&
oldContent["format"] === newContent["format"] && (<RoomMessageTextEventContent>oldContent)["format"] === (<RoomMessageTextEventContent>newContent)["format"] &&
oldContent["formatted_body"] === newContent["formatted_body"] (<RoomMessageTextEventContent>oldContent)["formatted_body"] ===
(<RoomMessageTextEventContent>newContent)["formatted_body"]
) { ) {
return false; return false;
} }

View file

@ -16,13 +16,13 @@ limitations under the License.
import { Composer as ComposerEvent } from "@matrix-org/analytics-events/types/typescript/Composer"; import { Composer as ComposerEvent } from "@matrix-org/analytics-events/types/typescript/Composer";
import { import {
IContent,
IEventRelation, IEventRelation,
MatrixEvent, MatrixEvent,
ISendEventResponse, ISendEventResponse,
MatrixClient, MatrixClient,
THREAD_RELATION_TYPE, THREAD_RELATION_TYPE,
} from "matrix-js-sdk/src/matrix"; } from "matrix-js-sdk/src/matrix";
import { RoomMessageEventContent } from "matrix-js-sdk/src/types";
import { PosthogAnalytics } from "../../../../../PosthogAnalytics"; import { PosthogAnalytics } from "../../../../../PosthogAnalytics";
import SettingsStore from "../../../../../settings/SettingsStore"; import SettingsStore from "../../../../../settings/SettingsStore";
@ -82,7 +82,7 @@ export async function sendMessage(
}*/ }*/
PosthogAnalytics.instance.trackEvent<ComposerEvent>(posthogEvent); PosthogAnalytics.instance.trackEvent<ComposerEvent>(posthogEvent);
let content: IContent | null = null; let content: RoomMessageEventContent | null = null;
// Slash command handling here approximates what can be found in SendMessageComposer.sendMessage() // Slash command handling here approximates what can be found in SendMessageComposer.sendMessage()
// but note that the /me and // special cases are handled by the call to createMessageContent // but note that the /me and // special cases are handled by the call to createMessageContent
@ -145,7 +145,7 @@ export async function sendMessage(
const prom = doMaybeLocalRoomAction( const prom = doMaybeLocalRoomAction(
roomId, roomId,
(actualRoomId: string) => mxClient.sendMessage(actualRoomId, threadId, content as IContent), (actualRoomId: string) => mxClient.sendMessage(actualRoomId, threadId, content!),
mxClient, mxClient,
); );
@ -218,7 +218,7 @@ export async function editMessage(
this.editorRef.current?.replaceEmoticon(position, REGEX_EMOTICON); this.editorRef.current?.replaceEmoticon(position, REGEX_EMOTICON);
}*/ }*/
const editContent = await createMessageContent(html, true, { editedEvent }); const editContent = await createMessageContent(html, true, { editedEvent });
const newContent = editContent["m.new_content"]; const newContent = editContent["m.new_content"]!;
const shouldSend = true; const shouldSend = true;

View file

@ -16,7 +16,8 @@ limitations under the License.
import React from "react"; import React from "react";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { IContent, MatrixClient } from "matrix-js-sdk/src/matrix"; import { MatrixClient } from "matrix-js-sdk/src/matrix";
import { RoomMessageEventContent } from "matrix-js-sdk/src/types";
import EditorModel from "./model"; import EditorModel from "./model";
import { Type } from "./parts"; import { Type } from "./parts";
@ -63,9 +64,9 @@ export async function runSlashCommand(
args: string | undefined, args: string | undefined,
roomId: string, roomId: string,
threadId: string | null, threadId: string | null,
): Promise<[content: IContent | null, success: boolean]> { ): Promise<[content: RoomMessageEventContent | null, success: boolean]> {
const result = cmd.run(matrixClient, roomId, threadId, args); const result = cmd.run(matrixClient, roomId, threadId, args);
let messageContent: IContent | null = null; let messageContent: RoomMessageEventContent | null = null;
let error: any = result.error; let error: any = result.error;
if (result.promise) { if (result.promise) {
try { try {

View file

@ -17,7 +17,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { IContent } from "matrix-js-sdk/src/matrix"; import { RoomMessageEventContent } from "matrix-js-sdk/src/types";
import { _td } from "../languageHandler"; import { _td } from "../languageHandler";
import { XOR } from "../@types/common"; import { XOR } from "../@types/common";
@ -31,4 +31,4 @@ export const CommandCategories = {
other: _td("slash_command|category_other"), other: _td("slash_command|category_other"),
}; };
export type RunResult = XOR<{ error: Error }, { promise: Promise<IContent | undefined> }>; export type RunResult = XOR<{ error: Error }, { promise: Promise<RoomMessageEventContent | undefined> }>;

View file

@ -44,6 +44,7 @@ import {
Direction, Direction,
THREAD_RELATION_TYPE, THREAD_RELATION_TYPE,
StateEvents, StateEvents,
TimelineEvents,
} from "matrix-js-sdk/src/matrix"; } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { import {
@ -248,13 +249,13 @@ export class StopGapWidgetDriver extends WidgetDriver {
stateKey?: string, stateKey?: string,
targetRoomId?: string, targetRoomId?: string,
): Promise<ISendEventDetails>; ): Promise<ISendEventDetails>;
public async sendEvent( public async sendEvent<K extends keyof TimelineEvents>(
eventType: Exclude<EventType, keyof StateEvents>, eventType: K,
content: IContent, content: TimelineEvents[K],
stateKey: null, stateKey: null,
targetRoomId?: string, targetRoomId?: string,
): Promise<ISendEventDetails>; ): Promise<ISendEventDetails>;
public async sendEvent<K extends keyof StateEvents>( public async sendEvent(
eventType: string, eventType: string,
content: IContent, content: IContent,
stateKey?: string | null, stateKey?: string | null,
@ -268,13 +269,22 @@ export class StopGapWidgetDriver extends WidgetDriver {
let r: { event_id: string } | null; let r: { event_id: string } | null;
if (stateKey !== null) { if (stateKey !== null) {
// state event // state event
r = await client.sendStateEvent(roomId, eventType as K, content as StateEvents[K], stateKey); r = await client.sendStateEvent(
roomId,
eventType as keyof StateEvents,
content as StateEvents[keyof StateEvents],
stateKey,
);
} else if (eventType === EventType.RoomRedaction) { } else if (eventType === EventType.RoomRedaction) {
// special case: extract the `redacts` property and call redact // special case: extract the `redacts` property and call redact
r = await client.redactEvent(roomId, content["redacts"]); r = await client.redactEvent(roomId, content["redacts"]);
} else { } else {
// message event // message event
r = await client.sendEvent(roomId, eventType, content); r = await client.sendEvent(
roomId,
eventType as keyof TimelineEvents,
content as TimelineEvents[keyof TimelineEvents],
);
if (eventType === EventType.RoomMessage) { if (eventType === EventType.RoomMessage) {
CHAT_EFFECTS.forEach((effect) => { CHAT_EFFECTS.forEach((effect) => {

View file

@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { IContent, IEncryptedFile, MsgType } from "matrix-js-sdk/src/matrix"; import { IEncryptedFile, MsgType } from "matrix-js-sdk/src/matrix";
import { RoomMessageEventContent } from "matrix-js-sdk/src/types";
/** /**
* @param {string} mxc MXC URL of the file * @param {string} mxc MXC URL of the file
@ -31,7 +32,7 @@ export const createVoiceMessageContent = (
size: number, size: number,
file?: IEncryptedFile, file?: IEncryptedFile,
waveform?: number[], waveform?: number[],
): IContent => { ): RoomMessageEventContent => {
return { return {
"body": "Voice message", "body": "Voice message",
//"msgtype": "org.matrix.msc2516.voice", //"msgtype": "org.matrix.msc2516.voice",

View file

@ -26,7 +26,7 @@ import {
RelationType, RelationType,
TypedEventEmitter, TypedEventEmitter,
} from "matrix-js-sdk/src/matrix"; } from "matrix-js-sdk/src/matrix";
import { EncryptedFile } from "matrix-js-sdk/src/types"; import { AudioContent, EncryptedFile } from "matrix-js-sdk/src/types";
import { import {
ChunkRecordedPayload, ChunkRecordedPayload,
@ -387,7 +387,7 @@ export class VoiceBroadcastRecording
rel_type: RelationType.Reference, rel_type: RelationType.Reference,
event_id: this.infoEventId, event_id: this.infoEventId,
}; };
content["io.element.voice_broadcast_chunk"] = { (<AudioContent>content)["io.element.voice_broadcast_chunk"] = {
sequence, sequence,
}; };

View file

@ -25,6 +25,7 @@ import {
M_TEXT, M_TEXT,
} from "matrix-js-sdk/src/matrix"; } from "matrix-js-sdk/src/matrix";
import { PollStartEvent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent"; import { PollStartEvent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent";
import { ReplacementEvent } from "matrix-js-sdk/src/types";
import { getMockClientWithEventEmitter } from "../../../test-utils"; import { getMockClientWithEventEmitter } from "../../../test-utils";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
@ -273,7 +274,9 @@ describe("PollCreateDialog", () => {
const [, , eventType, sentEventContent] = mockClient.sendEvent.mock.calls[0]; const [, , eventType, sentEventContent] = mockClient.sendEvent.mock.calls[0];
expect(M_POLL_START.matches(eventType)).toBeTruthy(); expect(M_POLL_START.matches(eventType)).toBeTruthy();
// didnt change // didnt change
expect(sentEventContent["m.new_content"][M_POLL_START.name].kind).toEqual(M_POLL_KIND_DISCLOSED.name); expect((sentEventContent as ReplacementEvent<any>)["m.new_content"][M_POLL_START.name].kind).toEqual(
M_POLL_KIND_DISCLOSED.name,
);
}); });
}); });

View file

@ -18,6 +18,7 @@ import React from "react";
import { fireEvent, render, screen } from "@testing-library/react"; import { fireEvent, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event"; import userEvent from "@testing-library/user-event";
import { Room } from "matrix-js-sdk/src/matrix"; import { Room } from "matrix-js-sdk/src/matrix";
import { ReplacementEvent, RoomMessageEventContent } from "matrix-js-sdk/src/types";
import EditMessageComposerWithMatrixClient, { import EditMessageComposerWithMatrixClient, {
createEditContent, createEditContent,
@ -296,11 +297,12 @@ describe("<EditMessageComposer/>", () => {
fireEvent.click(screen.getByText("Save")); fireEvent.click(screen.getByText("Save"));
const messageContent = mockClient.sendMessage.mock.calls[0][2]; const messageContent = mockClient.sendMessage.mock.calls[0][2] as RoomMessageEventContent &
ReplacementEvent<RoomMessageEventContent>;
// both content.mentions and new_content.mentions are empty // both content.mentions and new_content.mentions are empty
expect(messageContent["m.mentions"]).toEqual({}); expect(messageContent["m.mentions"]).toEqual({});
expect(messageContent["m.new_content"]["m.mentions"]).toEqual({}); expect(messageContent["m.new_content"]!["m.mentions"]).toEqual({});
}); });
it("should retain mentions in the original message that are not removed by the edit", async () => { it("should retain mentions in the original message that are not removed by the edit", async () => {
@ -312,12 +314,13 @@ describe("<EditMessageComposer/>", () => {
fireEvent.click(screen.getByText("Save")); fireEvent.click(screen.getByText("Save"));
const messageContent = mockClient.sendMessage.mock.calls[0][2]; const messageContent = mockClient.sendMessage.mock.calls[0][2] as RoomMessageEventContent &
ReplacementEvent<RoomMessageEventContent>;
// no new mentions were added, so nothing in top level mentions // no new mentions were added, so nothing in top level mentions
expect(messageContent["m.mentions"]).toEqual({}); expect(messageContent["m.mentions"]).toEqual({});
// bob is still mentioned, charlie removed // bob is still mentioned, charlie removed
expect(messageContent["m.new_content"]["m.mentions"]).toEqual({ expect(messageContent["m.new_content"]!["m.mentions"]).toEqual({
user_ids: ["@bob:server.org"], user_ids: ["@bob:server.org"],
}); });
}); });
@ -331,12 +334,13 @@ describe("<EditMessageComposer/>", () => {
fireEvent.click(screen.getByText("Save")); fireEvent.click(screen.getByText("Save"));
const messageContent = mockClient.sendMessage.mock.calls[0][2]; const messageContent = mockClient.sendMessage.mock.calls[0][2] as RoomMessageEventContent &
ReplacementEvent<RoomMessageEventContent>;
// no new mentions were added, so nothing in top level mentions // no new mentions were added, so nothing in top level mentions
expect(messageContent["m.mentions"]).toEqual({}); expect(messageContent["m.mentions"]).toEqual({});
// bob is not longer mentioned in the edited message, so empty mentions in new_content // bob is not longer mentioned in the edited message, so empty mentions in new_content
expect(messageContent["m.new_content"]["m.mentions"]).toEqual({}); expect(messageContent["m.new_content"]!["m.mentions"]).toEqual({});
}); });
it("should add mentions that were added in the edit", async () => { it("should add mentions that were added in the edit", async () => {
@ -352,13 +356,14 @@ describe("<EditMessageComposer/>", () => {
fireEvent.click(screen.getByText("Save")); fireEvent.click(screen.getByText("Save"));
const messageContent = mockClient.sendMessage.mock.calls[0][2]; const messageContent = mockClient.sendMessage.mock.calls[0][2] as RoomMessageEventContent &
ReplacementEvent<RoomMessageEventContent>;
// new mention in the edit // new mention in the edit
expect(messageContent["m.mentions"]).toEqual({ expect(messageContent["m.mentions"]).toEqual({
user_ids: ["@dan:server.org"], user_ids: ["@dan:server.org"],
}); });
expect(messageContent["m.new_content"]["m.mentions"]).toEqual({ expect(messageContent["m.new_content"]!["m.mentions"]).toEqual({
user_ids: ["@dan:server.org"], user_ids: ["@dan:server.org"],
}); });
}); });
@ -377,14 +382,15 @@ describe("<EditMessageComposer/>", () => {
fireEvent.click(screen.getByText("Save")); fireEvent.click(screen.getByText("Save"));
const messageContent = mockClient.sendMessage.mock.calls[0][2]; const messageContent = mockClient.sendMessage.mock.calls[0][2] as RoomMessageEventContent &
ReplacementEvent<RoomMessageEventContent>;
// new mention in the edit // new mention in the edit
expect(messageContent["m.mentions"]).toEqual({ expect(messageContent["m.mentions"]).toEqual({
user_ids: ["@dan:server.org"], user_ids: ["@dan:server.org"],
}); });
// all mentions in the edited version of the event // all mentions in the edited version of the event
expect(messageContent["m.new_content"]["m.mentions"]).toEqual({ expect(messageContent["m.new_content"]!["m.mentions"]).toEqual({
user_ids: ["@bob:server.org", "@dan:server.org"], user_ids: ["@bob:server.org", "@dan:server.org"],
}); });
}); });
@ -454,12 +460,13 @@ describe("<EditMessageComposer/>", () => {
fireEvent.click(screen.getByText("Save")); fireEvent.click(screen.getByText("Save"));
const messageContent = mockClient.sendMessage.mock.calls[0][2]; const messageContent = mockClient.sendMessage.mock.calls[0][2] as RoomMessageEventContent &
ReplacementEvent<RoomMessageEventContent>;
// no new mentions from edit // no new mentions from edit
expect(messageContent["m.mentions"]).toEqual({}); expect(messageContent["m.mentions"]).toEqual({});
// edited reply still mentions the parent event sender // edited reply still mentions the parent event sender
expect(messageContent["m.new_content"]["m.mentions"]).toEqual({ expect(messageContent["m.new_content"]!["m.mentions"]).toEqual({
user_ids: [originalEvent.getSender()], user_ids: [originalEvent.getSender()],
}); });
}); });
@ -475,7 +482,8 @@ describe("<EditMessageComposer/>", () => {
fireEvent.click(screen.getByText("Save")); fireEvent.click(screen.getByText("Save"));
const messageContent = mockClient.sendMessage.mock.calls[0][2]; const messageContent = mockClient.sendMessage.mock.calls[0][2] as RoomMessageEventContent &
ReplacementEvent<RoomMessageEventContent>;
// new mention in edit // new mention in edit
expect(messageContent["m.mentions"]).toEqual({ expect(messageContent["m.mentions"]).toEqual({
@ -483,7 +491,7 @@ describe("<EditMessageComposer/>", () => {
}); });
// edited reply still mentions the parent event sender // edited reply still mentions the parent event sender
// plus new mention @dan // plus new mention @dan
expect(messageContent["m.new_content"]["m.mentions"]).toEqual({ expect(messageContent["m.new_content"]!["m.mentions"]).toEqual({
user_ids: [originalEvent.getSender(), "@dan:server.org"], user_ids: [originalEvent.getSender(), "@dan:server.org"],
}); });
}); });
@ -496,13 +504,14 @@ describe("<EditMessageComposer/>", () => {
fireEvent.click(screen.getByText("Save")); fireEvent.click(screen.getByText("Save"));
const messageContent = mockClient.sendMessage.mock.calls[0][2]; const messageContent = mockClient.sendMessage.mock.calls[0][2] as RoomMessageEventContent &
ReplacementEvent<RoomMessageEventContent>;
// no mentions in edit // no mentions in edit
expect(messageContent["m.mentions"]).toEqual({}); expect(messageContent["m.mentions"]).toEqual({});
// edited reply still mentions the parent event sender // edited reply still mentions the parent event sender
// existing @bob mention removed // existing @bob mention removed
expect(messageContent["m.new_content"]["m.mentions"]).toEqual({ expect(messageContent["m.new_content"]!["m.mentions"]).toEqual({
user_ids: [originalEvent.getSender()], user_ids: [originalEvent.getSender()],
}); });
}); });
@ -536,12 +545,13 @@ describe("<EditMessageComposer/>", () => {
fireEvent.click(screen.getByText("Save")); fireEvent.click(screen.getByText("Save"));
const messageContent = mockClient.sendMessage.mock.calls[0][2]; const messageContent = mockClient.sendMessage.mock.calls[0][2] as RoomMessageEventContent &
ReplacementEvent<RoomMessageEventContent>;
// no mentions in edit // no mentions in edit
expect(messageContent["m.mentions"]).toEqual({}); expect(messageContent["m.mentions"]).toEqual({});
// edited reply still mentions the parent event sender // edited reply still mentions the parent event sender
expect(messageContent["m.new_content"]["m.mentions"]).toEqual({ expect(messageContent["m.new_content"]!["m.mentions"]).toEqual({
user_ids: [originalEvent.getSender()], user_ids: [originalEvent.getSender()],
}); });
}); });

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { EventStatus, IEventRelation } from "matrix-js-sdk/src/matrix"; import { EventStatus, IEventRelation, MsgType } from "matrix-js-sdk/src/matrix";
import { IRoomState } from "../../../../../../src/components/structures/RoomView"; import { IRoomState } from "../../../../../../src/components/structures/RoomView";
import { editMessage, sendMessage } from "../../../../../../src/components/views/rooms/wysiwyg_composer/utils/message"; import { editMessage, sendMessage } from "../../../../../../src/components/views/rooms/wysiwyg_composer/utils/message";
@ -272,7 +272,10 @@ describe("message", () => {
it("returns undefined when the command is not successful", async () => { it("returns undefined when the command is not successful", async () => {
// When // When
const validCommand = "/spoiler"; const validCommand = "/spoiler";
jest.spyOn(Commands, "runSlashCommand").mockResolvedValueOnce([{ content: "mock content" }, false]); jest.spyOn(Commands, "runSlashCommand").mockResolvedValueOnce([
{ body: "mock content", msgtype: MsgType.Text },
false,
]);
const result = await sendMessage(validCommand, true, { const result = await sendMessage(validCommand, true, {
roomContext: defaultRoomContext, roomContext: defaultRoomContext,