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

@ -15,7 +15,7 @@ limitations under the License.
*/
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 { _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();
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) {
console.error("Failed to submit poll response event:", e);
Modal.createDialog(ErrorDialog, {

View file

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

View file

@ -32,6 +32,12 @@ import Field from "../elements/Field";
import Spinner from "../elements/Spinner";
import LabelledCheckbox from "../elements/LabelledCheckbox";
declare module "matrix-js-sdk/src/types" {
interface TimelineEvents {
[ABUSE_EVENT_TYPE]: AbuseEventContent;
}
}
interface IProps {
mxEvent: MatrixEvent;
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.
enum Nature {
@ -250,13 +265,13 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
}
await client.sendEvent(dmRoomId, ABUSE_EVENT_TYPE, {
event_id: ev.getId(),
room_id: ev.getRoomId(),
event_id: ev.getId()!,
room_id: ev.getRoomId()!,
moderated_by_id: this.moderation.moderationRoomId,
nature,
reporter: client.getUserId(),
reporter: client.getUserId()!,
comment: this.state.reason.trim(),
});
} satisfies AbuseEventContent);
} else {
// Report to homeserver admin through the dedicated Matrix API.
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 { 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 Field from "../../elements/Field";
@ -32,7 +32,7 @@ export const stringify = (object: object): string => {
interface IEventEditorProps extends Pick<IDevtoolsProps, "onBack"> {
fieldDefs: IFieldDef[]; // immutable
defaultContent?: string;
onSend(fields: string[], content?: IContent): Promise<unknown>;
onSend(fields: string[], content: IContent): Promise<unknown>;
}
interface IFieldDef {
@ -180,8 +180,8 @@ export const TimelineEventEditor: React.FC<IEditorProps> = ({ mxEvent, onBack })
const fields = useMemo(() => [eventTypeField(mxEvent?.getType())], [mxEvent]);
const onSend = ([eventType]: string[], content?: IContent): Promise<unknown> => {
return cli.sendEvent(context.room.roomId, eventType, content || {});
const onSend = ([eventType]: string[], content: TimelineEvents[keyof TimelineEvents]): Promise<unknown> => {
return cli.sendEvent(context.room.roomId, eventType as keyof TimelineEvents, content);
};
let defaultContent: string | undefined;

View file

@ -23,6 +23,7 @@ import {
M_POLL_KIND_UNDISCLOSED,
M_POLL_START,
IPartialEvent,
TimelineEvents,
} from "matrix-js-sdk/src/matrix";
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(
actualRoomId,
this.props.threadId ?? null,
pollEvent.type,
pollEvent.content,
pollEvent.type as keyof TimelineEvents,
pollEvent.content as TimelineEvents[keyof TimelineEvents],
),
this.matrixClient,
)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -30,6 +30,7 @@ import {
import { DebouncedFunc, throttle } from "lodash";
import { logger } from "matrix-js-sdk/src/logger";
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 EditorModel from "../../../editor/model";
@ -183,7 +184,7 @@ export function createMessageContent(
relation: IEventRelation | undefined,
permalinkCreator?: RoomPermalinkCreator,
includeReplyLegacyFallback = true,
): IContent {
): RoomMessageEventContent {
const isEmote = containsEmote(model);
if (isEmote) {
model = stripEmoteCommand(model);
@ -195,7 +196,7 @@ export function createMessageContent(
const body = textSerialize(model);
const content: IContent = {
const content: RoomMessageEventContent = {
msgtype: isEmote ? MsgType.Emote : MsgType.Text,
body: body,
};
@ -432,7 +433,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
MatrixClientPeg.safeGet().sendEvent(lastMessage.getRoomId()!, EventType.Reaction, {
"m.relates_to": {
rel_type: RelationType.Annotation,
event_id: lastMessage.getId(),
event_id: lastMessage.getId()!,
key: reaction,
},
});
@ -475,7 +476,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
const replyToEvent = this.props.replyToEvent;
let shouldSend = true;
let content: IContent | null = null;
let content: RoomMessageEventContent | null = null;
if (!containsEmote(model) && isSlashCommand(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 { 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 { parsePermalink, RoomPermalinkCreator } from "../../../../../utils/permalinks/Permalinks";
@ -76,7 +77,7 @@ export async function createMessageContent(
includeReplyLegacyFallback = true,
editedEvent,
}: CreateMessageContentParams,
): Promise<IContent> {
): Promise<RoomMessageEventContent> {
const isEditing = isMatrixEvent(editedEvent);
const isReply = isEditing ? Boolean(editedEvent.replyEventId) : isMatrixEvent(replyToEvent);
const isReplyAndEditing = isEditing && isReply;
@ -100,10 +101,10 @@ export async function createMessageContent(
const bodyPrefix = (isReplyAndEditing && getTextReplyFallback(editedEvent)) || "";
const formattedBodyPrefix = (isReplyAndEditing && getHtmlReplyFallback(editedEvent)) || "";
const content: IContent = {
const content = {
msgtype: isEmote ? MsgType.Emote : MsgType.Text,
body: isEditing ? `${bodyPrefix} * ${body}` : body,
};
} as RoomMessageTextEventContent & ReplacementEvent<RoomMessageTextEventContent>;
// TODO markdown support

View file

@ -14,18 +14,22 @@ See the License for the specific language governing permissions and
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";
export function isContentModified(newContent: IContent, editorStateTransfer: EditorStateTransfer): boolean {
export function isContentModified(
newContent: RoomMessageEventContent,
editorStateTransfer: EditorStateTransfer,
): boolean {
// if nothing has changed then bail
const oldContent = editorStateTransfer.getEvent().getContent();
const oldContent = editorStateTransfer.getEvent().getContent<RoomMessageEventContent>();
if (
oldContent["msgtype"] === newContent["msgtype"] &&
oldContent["body"] === newContent["body"] &&
oldContent["format"] === newContent["format"] &&
oldContent["formatted_body"] === newContent["formatted_body"]
(<RoomMessageTextEventContent>oldContent)["format"] === (<RoomMessageTextEventContent>newContent)["format"] &&
(<RoomMessageTextEventContent>oldContent)["formatted_body"] ===
(<RoomMessageTextEventContent>newContent)["formatted_body"]
) {
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 {
IContent,
IEventRelation,
MatrixEvent,
ISendEventResponse,
MatrixClient,
THREAD_RELATION_TYPE,
} from "matrix-js-sdk/src/matrix";
import { RoomMessageEventContent } from "matrix-js-sdk/src/types";
import { PosthogAnalytics } from "../../../../../PosthogAnalytics";
import SettingsStore from "../../../../../settings/SettingsStore";
@ -82,7 +82,7 @@ export async function sendMessage(
}*/
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()
// 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(
roomId,
(actualRoomId: string) => mxClient.sendMessage(actualRoomId, threadId, content as IContent),
(actualRoomId: string) => mxClient.sendMessage(actualRoomId, threadId, content!),
mxClient,
);
@ -218,7 +218,7 @@ export async function editMessage(
this.editorRef.current?.replaceEmoticon(position, REGEX_EMOTICON);
}*/
const editContent = await createMessageContent(html, true, { editedEvent });
const newContent = editContent["m.new_content"];
const newContent = editContent["m.new_content"]!;
const shouldSend = true;