Skip to content

Global configuration flag for media previews #29582

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 63 commits into from
Apr 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
2f4272f
Modify useMediaVisible to take a room.
Half-Shot Mar 24, 2025
31c53f8
Add initial support for a account data level key.
Half-Shot Mar 24, 2025
a51b7d0
Merge remote-tracking branch 'origin/develop' into hs/media-previews-…
Half-Shot Apr 4, 2025
084823a
Update controls.
Half-Shot Apr 4, 2025
b12a13e
Update settings
Half-Shot Apr 4, 2025
3550303
Lint and fixes
Half-Shot Apr 4, 2025
2b6151f
make some tests go happy
Half-Shot Apr 8, 2025
8407a97
lint
Half-Shot Apr 8, 2025
ddadcc6
Merge branch 'develop' into hs/media-previews-server-config
Half-Shot Apr 8, 2025
c291165
Merge branch 'develop' into hs/media-previews-server-config
Half-Shot Apr 8, 2025
8487a8f
i18n
Half-Shot Apr 8, 2025
5bc0b3c
update preferences
Half-Shot Apr 10, 2025
1ff42d3
prettier
Half-Shot Apr 10, 2025
bd8ed66
Update settings tab.
Half-Shot Apr 10, 2025
6c9638d
Merge remote-tracking branch 'origin/develop' into hs/media-previews-…
Half-Shot Apr 10, 2025
a217cb1
update screenshot
Half-Shot Apr 10, 2025
b37b560
Update docs
Half-Shot Apr 10, 2025
b66e024
Rewrite controller
Half-Shot Apr 10, 2025
806f5be
Rewrite tons of tests
Half-Shot Apr 10, 2025
ef5f044
Rewrite RoomAvatar to be a functional component
Half-Shot Apr 10, 2025
ee4581e
lint
Half-Shot Apr 10, 2025
d1213b7
lint
Half-Shot Apr 10, 2025
3bdd0f4
Tidy up comments
Half-Shot Apr 10, 2025
81b400a
Apply media visible hook to inline images.
Half-Shot Apr 10, 2025
b1c056a
Move conditionals.
Half-Shot Apr 10, 2025
a51eab0
copyright all the things
Half-Shot Apr 10, 2025
9c278b9
Merge branch 'develop' into hs/media-previews-server-config
Half-Shot Apr 10, 2025
0202ef7
Review changes
Half-Shot Apr 11, 2025
7014484
Update html utils to properly discard media.
Half-Shot Apr 11, 2025
31253a4
Merge remote-tracking branch 'origin/develop' into hs/media-previews-…
Half-Shot Apr 11, 2025
5c5484c
Types fix
Half-Shot Apr 11, 2025
3d49a61
Fixing tests that break settings getValue expectations
Half-Shot Apr 14, 2025
17459e4
Merge remote-tracking branch 'origin/develop' into hs/media-previews-…
Half-Shot Apr 14, 2025
dc1dda9
Fix logic around media preview calculation
Half-Shot Apr 14, 2025
dbd533a
Fix room header tests
Half-Shot Apr 14, 2025
0330933
Fixup tests for timelinePanel
Half-Shot Apr 14, 2025
4a11e0e
Clear settings in matrixchat
Half-Shot Apr 14, 2025
2c95cf1
Update tests to use SettingsStore where possible.
Half-Shot Apr 14, 2025
f9d13a8
fix bug
Half-Shot Apr 14, 2025
c7a7ca0
revert changes to client.ts
Half-Shot Apr 14, 2025
4796ba9
Merge remote-tracking branch 'origin/develop' into hs/media-previews-…
Half-Shot Apr 14, 2025
cd579bc
copyright years
Half-Shot Apr 14, 2025
1042706
Add header
Half-Shot Apr 14, 2025
bda2f22
Merge remote-tracking branch 'origin/develop' into hs/media-previews-…
Half-Shot Apr 14, 2025
60d6e5c
Add a test for MediaPreviewAccountSettingsTab
Half-Shot Apr 14, 2025
a06a1c7
Mark initMatrixClient as optional
Half-Shot Apr 14, 2025
b9437bb
Improve on types
Half-Shot Apr 14, 2025
1dba7e4
Ensure we do not set the account data twice.
Half-Shot Apr 14, 2025
7809f44
Merge remote-tracking branch 'origin/develop' into hs/media-previews-…
Half-Shot Apr 14, 2025
a4143ca
lint
Half-Shot Apr 14, 2025
3df67ae
Merge branch 'develop' into hs/media-previews-server-config
Half-Shot Apr 14, 2025
6eb75db
Merge remote-tracking branch 'origin/develop' into hs/media-previews-…
Half-Shot Apr 15, 2025
a0b6613
Review changes
Half-Shot Apr 15, 2025
f415233
Ensure we include the client on rendered messages.
Half-Shot Apr 15, 2025
50857de
Fix test
Half-Shot Apr 15, 2025
dcc24ec
Merge branch 'develop' into hs/media-previews-server-config
Half-Shot Apr 17, 2025
38190ab
update labels
Half-Shot Apr 17, 2025
7ff6ac9
clean designs
Half-Shot Apr 17, 2025
a7da5af
update settings tab
Half-Shot Apr 17, 2025
f165dae
update snapshot
Half-Shot Apr 17, 2025
4bba73c
Merge branch 'develop' into hs/media-previews-server-config
Half-Shot Apr 22, 2025
12e337a
copyright
Half-Shot Apr 22, 2025
1efe4b5
prevent mutation
Half-Shot Apr 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions playwright/e2e/timeline/media-preview-settings.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
Copyright 2025 New Vector 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 * as fs from "node:fs";
import { type EventType, type MsgType, type RoomJoinRulesEventContent } from "matrix-js-sdk/src/types";

import { test, expect } from "../../element-web-test";

const MEDIA_FILE = fs.readFileSync("playwright/sample-files/riot.png");

test.describe("Media preview settings", () => {
test.use({
displayName: "Alan",
room: async ({ app, page, homeserver, bot, user }, use) => {
const mxc = (await bot.uploadContent(MEDIA_FILE, { name: "image.png", type: "image/png" })).content_uri;
const roomId = await bot.createRoom({
name: "Test room",
invite: [user.userId],
initial_state: [{ type: "m.room.avatar", content: { url: mxc }, state_key: "" }],
});
await bot.sendEvent(roomId, null, "m.room.message" as EventType, {
msgtype: "m.image" as MsgType,
body: "image.png",
url: mxc,
});

await use({ roomId });
},
});

test("should be able to hide avatars of inviters", { tag: "@screenshot" }, async ({ page, app, room, user }) => {
let settings = await app.settings.openUserSettings("Preferences");
await settings.getByLabel("Hide avatars of room and inviter").click();
await app.closeDialog();
await app.viewRoomById(room.roomId);
await expect(
page.getByRole("complementary").filter({ hasText: "Do you want to join Test room" }),
).toMatchScreenshot("invite-no-avatar.png");

Check failure on line 42 in playwright/e2e/timeline/media-preview-settings.spec.ts

View workflow job for this annotation

GitHub Actions / Run Tests [Chrome] 5/6

[Chrome] › playwright/e2e/timeline/media-preview-settings.spec.ts:35:9 › Media preview settings › should be able to hide avatars of inviters @screenshot

1) [Chrome] › playwright/e2e/timeline/media-preview-settings.spec.ts:35:9 › Media preview settings › should be able to hide avatars of inviters @screenshot Error: expect(locator).toHaveScreenshot(expected) 1286 pixels (ratio 0.01 of all image pixels) are different. Expected: /home/runner/work/element-web/element-web/playwright/snapshots/timeline/media-preview-settings.spec.ts/invite-no-avatar-linux.png Received: /home/runner/work/element-web/element-web/playwright/test-results/timeline-media-preview-set-66157-to-hide-avatars-of-inviters-Chrome/invite-no-avatar-actual.png Diff: /home/runner/work/element-web/element-web/playwright/test-results/timeline-media-preview-set-66157-to-hide-avatars-of-inviters-Chrome/invite-no-avatar-diff.png Call log: - expect.toHaveScreenshot(invite-no-avatar.png) with timeout 5000ms - verifying given screenshot expectation - waiting for getByRole('complementary').filter({ hasText: 'Do you want to join Test room' }) - locator resolved to <div role="complementary" class="mx_RoomPreviewBar mx_RoomPreviewBar_Invite mx_RoomPreviewBar_dialog">…</div> - taking element screenshot - disabled all CSS animations - waiting for fonts to load... - fonts loaded - attempting scroll into view action - waiting for element to be stable - 1286 pixels (ratio 0.01 of all image pixels) are different. - waiting 100ms before taking screenshot - waiting for getByRole('complementary').filter({ hasText: 'Do you want to join Test room' }) - locator resolved to <div role="complementary" class="mx_RoomPreviewBar mx_RoomPreviewBar_Invite mx_RoomPreviewBar_dialog">…</div> - taking element screenshot - disabled all CSS animations - waiting for fonts to load... - fonts loaded - attempting scroll into view action - waiting for element to be stable - captured a stable screenshot - 1286 pixels (ratio 0.01 of all image pixels) are different. 40 | await expect( 41 | page.getByRole("complementary").filter({ hasText: "Do you want to join Test room" }), > 42 | ).toMatchScreenshot("invite-no-avatar.png"); | ^ 43 | await expect( 44 | page.getByRole("tree", { name: "Rooms" }).getByRole("treeitem", { name: "Test room" }), 45 | ).toMatchScreenshot("invite-room-tree-no-avatar.png"); at /home/runner/work/element-web/element-web/playwright/e2e/timeline/media-preview-settings.spec.ts:42:11
await expect(
page.getByRole("tree", { name: "Rooms" }).getByRole("treeitem", { name: "Test room" }),
).toMatchScreenshot("invite-room-tree-no-avatar.png");

// And then go back to being visible
settings = await app.settings.openUserSettings("Preferences");
await settings.getByLabel("Hide avatars of room and inviter").click();
await app.closeDialog();
await page.goto("#/home");
await app.viewRoomById(room.roomId);
await expect(
page.getByRole("complementary").filter({ hasText: "Do you want to join Test room" }),
).toMatchScreenshot("invite-with-avatar.png");
await expect(
page.getByRole("tree", { name: "Rooms" }).getByRole("treeitem", { name: "Test room" }),
).toMatchScreenshot("invite-room-tree-with-avatar.png");
});

test("should be able to hide media in rooms globally", async ({ page, app, room, user }) => {
const settings = await app.settings.openUserSettings("Preferences");
await settings.getByLabel("Show media in timeline").getByRole("radio", { name: "Always hide" }).click();
await app.closeDialog();
await app.viewRoomById(room.roomId);
await page.getByRole("button", { name: "Accept" }).click();
await expect(page.getByText("Show image")).toBeVisible();
});
test("should be able to hide media in non-private rooms globally", async ({ page, app, room, user, bot }) => {
await bot.sendStateEvent(room.roomId, "m.room.join_rules", {
join_rule: "public",
});
const settings = await app.settings.openUserSettings("Preferences");
await settings.getByLabel("Show media in timeline").getByLabel("In private rooms").click();
await app.closeDialog();
await app.viewRoomById(room.roomId);
await page.getByRole("button", { name: "Accept" }).click();
await expect(page.getByText("Show image")).toBeVisible();
for (const joinRule of ["invite", "knock", "restricted"] as RoomJoinRulesEventContent["join_rule"][]) {
await bot.sendStateEvent(room.roomId, "m.room.join_rules", {
join_rule: joinRule,
} satisfies RoomJoinRulesEventContent);
await expect(page.getByText("Show image")).not.toBeVisible();
}
});
test("should be able to show media in rooms globally", async ({ page, app, room, user }) => {
const settings = await app.settings.openUserSettings("Preferences");
await settings.getByLabel("Show media in timeline").getByRole("radio", { name: "Always show" }).click();
await app.closeDialog();
await app.viewRoomById(room.roomId);
await page.getByRole("button", { name: "Accept" }).click();
await expect(page.getByText("Show image")).not.toBeVisible();
});
test("should be able to hide media in an individual room", async ({ page, app, room, user }) => {
const settings = await app.settings.openUserSettings("Preferences");
await settings.getByLabel("Show media in timeline").getByRole("radio", { name: "Always show" }).click();
await app.closeDialog();

await app.viewRoomById(room.roomId);
await page.getByRole("button", { name: "Accept" }).click();

const roomSettings = await app.settings.openRoomSettings("General");
await roomSettings.getByLabel("Show media in timeline").getByRole("radio", { name: "Always hide" }).click();
await app.closeDialog();

await expect(page.getByText("Show image")).toBeVisible();
});
test("should be able to show media in an individual room", async ({ page, app, room, user }) => {
const settings = await app.settings.openUserSettings("Preferences");
await settings.getByLabel("Show media in timeline").getByRole("radio", { name: "Always hide" }).click();
await app.closeDialog();

await app.viewRoomById(room.roomId);
await page.getByRole("button", { name: "Accept" }).click();

const roomSettings = await app.settings.openRoomSettings("General");
await roomSettings.getByLabel("Show media in timeline").getByRole("radio", { name: "Always show" }).click();
await app.closeDialog();

await expect(page.getByText("Show image")).not.toBeVisible();
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions res/css/_components.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@
@import "./views/settings/tabs/user/_AppearanceUserSettingsTab.pcss";
@import "./views/settings/tabs/user/_HelpUserSettingsTab.pcss";
@import "./views/settings/tabs/user/_KeyboardUserSettingsTab.pcss";
@import "./views/settings/tabs/user/_MediaPreviewAccountSettings.pcss";
@import "./views/settings/tabs/user/_MjolnirUserSettingsTab.pcss";
@import "./views/settings/tabs/user/_PreferencesUserSettingsTab.pcss";
@import "./views/settings/tabs/user/_SecurityUserSettingsTab.pcss";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
Copyright 2025 New Vector 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.
*/

.mx_MediaPreviewAccountSetting_Radio {
margin: var(--cpd-space-1x) 0;
}

.mx_MediaPreviewAccountSetting {
margin-top: var(--cpd-space-1x);
}

.mx_MediaPreviewAccountSetting_RadioHelp {
margin-top: 0;
margin-bottom: var(--cpd-space-1x);
}

.mx_MediaPreviewAccountSetting_Form {
width: 100%;
}

.mx_MediaPreviewAccountSetting_ToggleSwitch {
font: var(--cpd-font-body-md-medium);
letter-spacing: var(--cpd-font-letter-spacing-body-md);
}
5 changes: 4 additions & 1 deletion src/@types/matrix-js-sdk.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024, 2025 New Vector Ltd.
Copyright 2024 The Matrix.org Foundation C.I.C.

SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Expand All @@ -14,6 +14,7 @@ import type { EncryptedFile } from "matrix-js-sdk/src/types";
import type { EmptyObject } from "matrix-js-sdk/src/matrix";
import type { DeviceClientInformation } from "../utils/device/types.ts";
import type { UserWidget } from "../utils/WidgetUtils-types.ts";
import { type MediaPreviewConfig } from "./media_preview.ts";

// Extend Matrix JS SDK types via Typescript declaration merging to support unspecced event fields and types
declare module "matrix-js-sdk/src/types" {
Expand Down Expand Up @@ -87,6 +88,8 @@ declare module "matrix-js-sdk/src/types" {
"m.accepted_terms": {
accepted: string[];
};

"io.element.msc4278.media_preview_config": MediaPreviewConfig;
}

export interface AudioContent {
Expand Down
33 changes: 33 additions & 0 deletions src/@types/media_preview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
Copyright 2025 New Vector 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.
*/

export enum MediaPreviewValue {
/**
* Media previews should be enabled.
*/
On = "on",
/**
* Media previews should only be enabled for rooms with non-public join rules.
*/
Private = "private",
/**
* Media previews should be disabled.
*/
Off = "off",
}

export const MEDIA_PREVIEW_ACCOUNT_DATA_TYPE = "io.element.msc4278.media_preview_config";
export interface MediaPreviewConfig extends Record<string, unknown> {
/**
* Media preview setting for thumbnails of media in rooms.
*/
media_previews: MediaPreviewValue;
/**
* Media preview settings for avatars of rooms we have been invited to.
*/
invite_avatars: MediaPreviewValue.On | MediaPreviewValue.Off;
}
20 changes: 19 additions & 1 deletion src/HtmlUtils.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024, 2025 New Vector Ltd.
Copyright 2019 Michael Telatynski <[email protected]>
Copyright 2019 The Matrix.org Foundation C.I.C.
Copyright 2017, 2018 New Vector Ltd
Expand Down Expand Up @@ -294,6 +294,10 @@ export interface EventRenderOpts {
disableBigEmoji?: boolean;
stripReplyFallback?: boolean;
forComposerQuote?: boolean;
/**
* Should inline media be rendered?
*/
mediaIsVisible?: boolean;
}

function analyseEvent(content: IContent, highlights: Optional<string[]>, opts: EventRenderOpts = {}): EventAnalysis {
Expand All @@ -302,6 +306,20 @@ function analyseEvent(content: IContent, highlights: Optional<string[]>, opts: E
sanitizeParams = composerSanitizeHtmlParams;
}

if (opts.mediaIsVisible === false && sanitizeParams.transformTags?.["img"]) {
// Prevent mutating the source of sanitizeParams.
sanitizeParams = {
...sanitizeParams,
transformTags: {
...sanitizeParams.transformTags,
img: (tagName) => {
// Remove element
return { tagName, attribs: {} };
},
},
};
}

try {
const isFormattedBody =
content.format === "org.matrix.custom.html" && typeof content.formatted_body === "string";
Expand Down
9 changes: 2 additions & 7 deletions src/Linkify.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024, 2025 New Vector Ltd.
Copyright 2024 The Matrix.org Foundation C.I.C.

SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Expand All @@ -12,7 +12,6 @@ import { merge } from "lodash";
import _Linkify from "linkify-react";

import { _linkifyString, ELEMENT_URL_PATTERN, options as linkifyMatrixOptions } from "./linkify-matrix";
import SettingsStore from "./settings/SettingsStore";
import { tryTransformPermalinkToLocalHref } from "./utils/permalinks/Permalinks";
import { mediaFromMxc } from "./customisations/Media";
import { PERMITTED_URL_SCHEMES } from "./utils/UrlUtils";
Expand Down Expand Up @@ -47,10 +46,7 @@ export const transformTags: NonNullable<IOptions["transformTags"]> = {
// Strip out imgs that aren't `mxc` here instead of using allowedSchemesByTag
// because transformTags is used _before_ we filter by allowedSchemesByTag and
// we don't want to allow images with `https?` `src`s.
// We also drop inline images (as if they were not present at all) when the "show
// images" preference is disabled. Future work might expose some UI to reveal them
// like standalone image events have.
if (!src || !SettingsStore.getValue("showImages")) {
if (!src) {
return { tagName, attribs: {} };
}

Expand Down Expand Up @@ -78,7 +74,6 @@ export const transformTags: NonNullable<IOptions["transformTags"]> = {
if (requestedHeight) {
attribs.style += "height: 100%;";
}

attribs.src = mediaFromMxc(src).getThumbnailOfSourceHttp(width, height)!;
return { tagName, attribs };
},
Expand Down
5 changes: 3 additions & 2 deletions src/components/views/avatars/RoomAvatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { filterBoolean } from "../../../utils/arrays";
import { useSettingValue } from "../../../hooks/useSettings";
import { useRoomState } from "../../../hooks/useRoomState";
import { useRoomIdName } from "../../../hooks/room/useRoomIdName";
import { MediaPreviewValue } from "../../../@types/media_preview";

interface IProps extends Omit<ComponentProps<typeof BaseAvatar>, "name" | "idName" | "url" | "onClick" | "size"> {
// Room may be left unset here, but if it is,
Expand All @@ -40,7 +41,8 @@ const RoomAvatar: React.FC<IProps> = ({ room, viewAvatarOnClick, onClick, oobDat
const avatarEvent = useRoomState(room, (state) => state.getStateEvents(EventType.RoomAvatar, ""));
const roomIdName = useRoomIdName(room, oobData);

const showAvatarsOnInvites = useSettingValue("showAvatarsOnInvites", room?.roomId);
const showAvatarsOnInvites =
useSettingValue("mediaPreviewConfig", room?.roomId).invite_avatars === MediaPreviewValue.On;

const onRoomAvatarClick = useCallback(() => {
const avatarUrl = Avatar.avatarUrlForRoom(room ?? null);
Expand All @@ -63,7 +65,6 @@ const RoomAvatar: React.FC<IProps> = ({ room, viewAvatarOnClick, onClick, oobDat
// parseInt ignores suffixes.
const sizeInt = parseInt(size, 10);
let oobAvatar: string | null = null;

if (oobData?.avatarUrl) {
oobAvatar = mediaFromMxc(oobData?.avatarUrl).getThumbnailOfSourceHttp(sizeInt, sizeInt, "crop");
}
Expand Down
5 changes: 4 additions & 1 deletion src/components/views/messages/EventContentBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
import MatrixClientContext from "../../../contexts/MatrixClientContext.tsx";
import { useSettingValue } from "../../../hooks/useSettings.ts";
import { filterBoolean } from "../../../utils/arrays.ts";
import { useMediaVisible } from "../../../hooks/useMediaVisible.ts";

/**
* Returns a RegExp pattern for the keyword in the push rule of the given Matrix event, if any
Expand Down Expand Up @@ -150,6 +151,7 @@ const EventContentBody = memo(
forwardRef<HTMLElement, Props>(
({ as, mxEvent, stripReply, content, linkify, highlights, includeDir = true, ...options }, ref) => {
const enableBigEmoji = useSettingValue("TextualBody.enableBigEmoji");
const [mediaIsVisible] = useMediaVisible(mxEvent?.getId(), mxEvent?.getRoomId());

const replacer = useReplacer(content, mxEvent, options);
const linkifyOptions = useMemo(
Expand All @@ -167,8 +169,9 @@ const EventContentBody = memo(
disableBigEmoji: isEmote || !enableBigEmoji,
// Part of Replies fallback support
stripReplyFallback: stripReply,
mediaIsVisible,
}),
[content, enableBigEmoji, highlights, isEmote, stripReply],
[content, mediaIsVisible, enableBigEmoji, highlights, isEmote, stripReply],
);

if (as === "div") includeDir = true; // force dir="auto" on divs
Expand Down
4 changes: 2 additions & 2 deletions src/components/views/messages/HideActionButton.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024, 2025 New Vector Ltd.
Copyright 2021 The Matrix.org Foundation C.I.C.

SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Expand All @@ -25,7 +25,7 @@ interface IProps {
* Quick action button for marking a media event as hidden.
*/
export const HideActionButton: React.FC<IProps> = ({ mxEvent }) => {
const [mediaIsVisible, setVisible] = useMediaVisible(mxEvent.getId()!);
const [mediaIsVisible, setVisible] = useMediaVisible(mxEvent.getId(), mxEvent.getRoomId());

if (!mediaIsVisible) {
return;
Expand Down
2 changes: 1 addition & 1 deletion src/components/views/messages/MImageBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -686,7 +686,7 @@ export class MImageBodyInner extends React.Component<IProps, IState> {

// Wrap MImageBody component so we can use a hook here.
const MImageBody: React.FC<IBodyProps> = (props) => {
const [mediaVisible, setVisible] = useMediaVisible(props.mxEvent.getId()!);
const [mediaVisible, setVisible] = useMediaVisible(props.mxEvent.getId(), props.mxEvent.getRoomId());
return <MImageBodyInner mediaVisible={mediaVisible} setMediaVisible={setVisible} {...props} />;
};

Expand Down
2 changes: 1 addition & 1 deletion src/components/views/messages/MImageReplyBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class MImageReplyBodyInner extends MImageBodyInner {
}
}
const MImageReplyBody: React.FC<IBodyProps> = (props) => {
const [mediaVisible, setVisible] = useMediaVisible(props.mxEvent.getId()!);
const [mediaVisible, setVisible] = useMediaVisible(props.mxEvent.getId(), props.mxEvent.getRoomId());
return <MImageReplyBodyInner mediaVisible={mediaVisible} setMediaVisible={setVisible} {...props} />;
};

Expand Down
2 changes: 1 addition & 1 deletion src/components/views/messages/MStickerBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class MStickerBodyInner extends MImageBodyInner {
}

const MStickerBody: React.FC<IBodyProps> = (props) => {
const [mediaVisible, setVisible] = useMediaVisible(props.mxEvent.getId()!);
const [mediaVisible, setVisible] = useMediaVisible(props.mxEvent.getId(), props.mxEvent.getRoomId());
return <MStickerBodyInner mediaVisible={mediaVisible} setMediaVisible={setVisible} {...props} />;
};

Expand Down
2 changes: 1 addition & 1 deletion src/components/views/messages/MVideoBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ class MVideoBodyInner extends React.PureComponent<IProps, IState> {

// Wrap MVideoBody component so we can use a hook here.
const MVideoBody: React.FC<IBodyProps> = (props) => {
const [mediaVisible, setVisible] = useMediaVisible(props.mxEvent.getId()!);
const [mediaVisible, setVisible] = useMediaVisible(props.mxEvent.getId(), props.mxEvent.getRoomId());
return <MVideoBodyInner mediaVisible={mediaVisible} setMediaVisible={setVisible} {...props} />;
};

Expand Down
Loading
Loading