1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-08-09 10:22:46 +03:00

Handle empty m.room.topic (#4673)

* Define topic as optional.

* Change isProvided so that types work better.

* allow makeTopicContent and parseTopicContent to handle optional values for plain text

* linting

* Remove usage of optional

* Topic key may only contain legacy key.

* Add tests for other branches.
This commit is contained in:
Will Hunt
2025-02-03 08:13:44 +00:00
committed by GitHub
parent cc238c24ab
commit c93128ed39
6 changed files with 48 additions and 10 deletions

View File

@@ -207,6 +207,17 @@ describe("Topic content helpers", () => {
], ],
}); });
}); });
it("creates an empty event when the topic is falsey", () => {
expect(makeTopicContent(undefined)).toEqual({
topic: undefined,
[M_TOPIC.name]: [],
});
expect(makeTopicContent(null)).toEqual({
topic: null,
[M_TOPIC.name]: [],
});
});
}); });
describe("parseTopicContent()", () => { describe("parseTopicContent()", () => {
@@ -257,5 +268,26 @@ describe("Topic content helpers", () => {
html: "<b>pizza</b>", html: "<b>pizza</b>",
}); });
}); });
it("parses legacy event content", () => {
expect(
parseTopicContent({
topic: "pizza",
}),
).toEqual({
text: "pizza",
});
});
it("uses legacy event content when new topic key is invalid", () => {
expect(
parseTopicContent({
"topic": "pizza",
"m.topic": {} as any,
}),
).toEqual({
text: "pizza",
});
});
}); });
}); });

View File

@@ -90,7 +90,7 @@ export interface RoomNameEventContent {
} }
export interface RoomTopicEventContent { export interface RoomTopicEventContent {
topic: string; topic: string | undefined | null;
} }
export interface RoomAvatarEventContent { export interface RoomAvatarEventContent {

View File

@@ -60,4 +60,4 @@ export type MTopicEvent = EitherAnd<{ [M_TOPIC.name]: MTopicContent }, { [M_TOPI
/** /**
* The event content for an m.room.topic event * The event content for an m.room.topic event
*/ */
export type MRoomTopicEventContent = { topic: string } & MTopicEvent; export type MRoomTopicEventContent = { topic: string | null | undefined } & (MTopicEvent | {});

View File

@@ -4490,11 +4490,13 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
} }
/** /**
* @param roomId - The room to update the topic in.
* @param topic - The plaintext topic. May be empty to remove the topic.
* @param htmlTopic - Optional. * @param htmlTopic - Optional.
* @returns Promise which resolves: TODO * @returns Promise which resolves: TODO
* @returns Rejects: with an error response. * @returns Rejects: with an error response.
*/ */
public setRoomTopic(roomId: string, topic: string, htmlTopic?: string): Promise<ISendEventResponse> { public setRoomTopic(roomId: string, topic?: string, htmlTopic?: string): Promise<ISendEventResponse> {
const content = ContentHelpers.makeTopicContent(topic, htmlTopic); const content = ContentHelpers.makeTopicContent(topic, htmlTopic);
return this.sendStateEvent(roomId, EventType.RoomTopic, content); return this.sendStateEvent(roomId, EventType.RoomTopic, content);
} }

View File

@@ -185,27 +185,31 @@ export const parseLocationEvent = (wireEventContent: LocationEventWireContent):
/** /**
* Topic event helpers * Topic event helpers
*/ */
export type MakeTopicContent = (topic: string, htmlTopic?: string) => MRoomTopicEventContent; export type MakeTopicContent = (topic: string | null | undefined, htmlTopic?: string) => MRoomTopicEventContent;
export const makeTopicContent: MakeTopicContent = (topic, htmlTopic) => { export const makeTopicContent: MakeTopicContent = (topic, htmlTopic) => {
const renderings = [{ body: topic, mimetype: "text/plain" }]; const renderings = [];
if (isProvided(topic)) {
renderings.push({ body: topic, mimetype: "text/plain" });
}
if (isProvided(htmlTopic)) { if (isProvided(htmlTopic)) {
renderings.push({ body: htmlTopic!, mimetype: "text/html" }); renderings.push({ body: htmlTopic, mimetype: "text/html" });
} }
return { topic, [M_TOPIC.name]: renderings }; return { topic, [M_TOPIC.name]: renderings };
}; };
export type TopicState = { export type TopicState = {
text: string; text?: string;
html?: string; html?: string;
}; };
export const parseTopicContent = (content: MRoomTopicEventContent): TopicState => { export const parseTopicContent = (content: MRoomTopicEventContent): TopicState => {
const mtopic = M_TOPIC.findIn<MTopicContent>(content); const mtopic = M_TOPIC.findIn<MTopicContent>(content);
if (!Array.isArray(mtopic)) { if (!Array.isArray(mtopic)) {
return { text: content.topic }; return { text: content.topic ?? undefined };
} }
const text = mtopic?.find((r) => !isProvided(r.mimetype) || r.mimetype === "text/plain")?.body ?? content.topic; const text =
mtopic?.find((r) => !isProvided(r.mimetype) || r.mimetype === "text/plain")?.body ?? content.topic ?? undefined;
const html = mtopic?.find((r) => r.mimetype === "text/html")?.body; const html = mtopic?.find((r) => r.mimetype === "text/html")?.body;
return { text, html }; return { text, html };
}; };

View File

@@ -21,7 +21,7 @@ import { Optional } from "matrix-events-sdk";
* @param s - The optional to test. * @param s - The optional to test.
* @returns True if the value is defined. * @returns True if the value is defined.
*/ */
export function isProvided<T>(s: Optional<T>): boolean { export function isProvided<T>(s: Optional<T>): s is T {
return s !== null && s !== undefined; return s !== null && s !== undefined;
} }