diff --git a/apps/web/src/shouldHideEvent.ts b/apps/web/src/shouldHideEvent.ts index 64a0e1f0f91..f11e1bb1334 100644 --- a/apps/web/src/shouldHideEvent.ts +++ b/apps/web/src/shouldHideEvent.ts @@ -4,12 +4,20 @@ Copyright 2017 Michael Telatynski <7t3chguy@gmail.com> SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE files in the repository root for full details. - */ - -import { type MatrixEvent, EventType, RelationType, M_POLL_END } from "matrix-js-sdk/src/matrix"; +*/ + +import { + type MatrixEvent, + type MatrixClient, + EventType, + RelationType, + JoinRule, + M_POLL_END, +} from "matrix-js-sdk/src/matrix"; import { KnownMembership } from "matrix-js-sdk/src/types"; import SettingsStore from "./settings/SettingsStore"; +import { MatrixClientPeg } from "./MatrixClientPeg"; import { type IRoomState } from "./components/structures/RoomView"; import { type SettingKey } from "./settings/Settings.tsx"; @@ -43,16 +51,26 @@ function memberEventDiff(ev: MatrixEvent): IDiff { return diff; } +function isPublicRoom(ev: MatrixEvent, client?: MatrixClient): boolean { + const mxClient = client ?? MatrixClientPeg.get(); + const room = mxClient?.getRoom(ev.getRoomId()); + return room?.getJoinRule() === JoinRule.Public; +} + /** * Determines whether the given event should be hidden from timelines. * @param ev The event * @param ctx An optional RoomContext to pull cached settings values from to avoid * hitting the settings store + * @param client An optional MatrixClient to use instead of MatrixClientPeg + * @returns True if the event should be hidden */ -export default function shouldHideEvent(ev: MatrixEvent, ctx?: IRoomState): boolean { +export default function shouldHideEvent(ev: MatrixEvent, ctx?: IRoomState, client?: MatrixClient): boolean { // Hide all poll end events if (M_POLL_END.matches(ev.getType())) return true; + if (ev.getType() === EventType.RoomTopic && isPublicRoom(ev, client)) return true; + // Accessing the settings store directly can be expensive if done frequently, // so we should prefer using cached values if a RoomContext is available const isEnabled = ctx @@ -70,6 +88,8 @@ export default function shouldHideEvent(ev: MatrixEvent, ctx?: IRoomState): bool const eventDiff = memberEventDiff(ev); if (eventDiff.isMemberEvent) { + if (isPublicRoom(ev, client)) return true; + if ((eventDiff.isJoin || eventDiff.isPart) && !isEnabled("showJoinLeaves")) return true; if (eventDiff.isAvatarChange && !isEnabled("showAvatarChanges")) return true; if (eventDiff.isDisplaynameChange && !isEnabled("showDisplaynameChanges")) return true; diff --git a/apps/web/test/unit-tests/shouldHideEvent-test.ts b/apps/web/test/unit-tests/shouldHideEvent-test.ts new file mode 100644 index 00000000000..86bc97325f1 --- /dev/null +++ b/apps/web/test/unit-tests/shouldHideEvent-test.ts @@ -0,0 +1,109 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE files in the repository root for full details. +*/ + +import { EventType, JoinRule, type MatrixClient, type Room, Room as SDKRoom } from "matrix-js-sdk/src/matrix"; +import { KnownMembership } from "matrix-js-sdk/src/types"; + +import shouldHideEvent from "../../src/shouldHideEvent"; +import { mkEvent } from "../test-utils/test-utils"; + +describe("shouldHideEvent", () => { + let client: MatrixClient; + let publicRoom: Room; + let privateRoom: Room; + + beforeEach(() => { + client = { + getRoom: jest.fn().mockImplementation((roomId: string) => { + if (roomId === "!public:server") return publicRoom; + if (roomId === "!private:server") return privateRoom; + return null; + }), + } as unknown as MatrixClient; + + publicRoom = new SDKRoom("!public:server", client, "@user:server"); + privateRoom = new SDKRoom("!private:server", client, "@user:server"); + + (publicRoom as unknown as { getJoinRule: jest.Mock }).getJoinRule = jest.fn().mockReturnValue(JoinRule.Public); + (privateRoom as unknown as { getJoinRule: jest.Mock }).getJoinRule = jest + .fn() + .mockReturnValue(JoinRule.Private); + }); + + function makeMemberEvent( + roomId: string, + membership: KnownMembership, + prevMembership?: KnownMembership, + ): ReturnType { + return mkEvent({ + type: EventType.RoomMember, + room: roomId, + user: "@user:server", + content: { + membership, + displayname: "User", + avatar_url: "mxc://avatar", + }, + prev_content: + prevMembership !== undefined + ? { + membership: prevMembership, + displayname: "User", + avatar_url: "mxc://avatar", + } + : undefined, + event: true, + }); + } + + function makeTopicEvent(roomId: string): ReturnType { + return mkEvent({ + type: EventType.RoomTopic, + room: roomId, + user: "@user:server", + skey: "", + content: { + topic: "Test Topic", + }, + event: true, + }); + } + + describe("public room events", () => { + it("should hide member join events in public rooms", () => { + const event = makeMemberEvent("!public:server", KnownMembership.Join, KnownMembership.Invite); + expect(shouldHideEvent(event, undefined, client)).toBe(true); + }); + + it("should hide member leave events in public rooms", () => { + const event = makeMemberEvent("!public:server", KnownMembership.Leave, KnownMembership.Join); + expect(shouldHideEvent(event, undefined, client)).toBe(true); + }); + + it("should hide topic events in public rooms", () => { + const event = makeTopicEvent("!public:server"); + expect(shouldHideEvent(event, undefined, client)).toBe(true); + }); + }); + + describe("non-public room events", () => { + it("should show member join events in private rooms", () => { + const event = makeMemberEvent("!private:server", KnownMembership.Join, KnownMembership.Invite); + expect(shouldHideEvent(event, undefined, client)).toBe(false); + }); + + it("should show member leave events in private rooms", () => { + const event = makeMemberEvent("!private:server", KnownMembership.Leave, KnownMembership.Join); + expect(shouldHideEvent(event, undefined, client)).toBe(false); + }); + + it("should show topic events in private rooms", () => { + const event = makeTopicEvent("!private:server"); + expect(shouldHideEvent(event, undefined, client)).toBe(false); + }); + }); +});