Skip to content
This repository was archived by the owner on Dec 7, 2024. It is now read-only.

Commit d3541b7

Browse files
authored
Merge pull request matrix-org#5714 from matrix-org/travis/media-customization
Support a media handling customisation endpoint
2 parents 4987359 + 6ab235f commit d3541b7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+564
-341
lines changed

docs/media-handling.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Media handling
2+
3+
Surely media should be as easy as just putting a URL into an `img` and calling it good, right?
4+
Not quite. Matrix uses something called a Matrix Content URI (better known as MXC URI) to identify
5+
content, which is then converted to a regular HTTPS URL on the homeserver. However, sometimes that
6+
URL can change depending on deployment considerations.
7+
8+
The react-sdk features a [customisation endpoint](https://github.com/vector-im/element-web/blob/develop/docs/customisations.md)
9+
for media handling where all conversions from MXC URI to HTTPS URL happen. This is to ensure that
10+
those obscure deployments can route all their media to the right place.
11+
12+
For development, there are currently two functions available: `mediaFromMxc` and `mediaFromContent`.
13+
The `mediaFromMxc` function should be self-explanatory. `mediaFromContent` takes an event content as
14+
a parameter and will automatically parse out the source media and thumbnail. Both functions return
15+
a `Media` object with a number of options on it, such as getting various common HTTPS URLs for the
16+
media.
17+
18+
**It is extremely important that all media calls are put through this customisation endpoint.** So
19+
much so it's a lint rule to avoid accidental use of the wrong functions.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@
157157
"jest": "^26.6.3",
158158
"jest-canvas-mock": "^2.3.0",
159159
"jest-environment-jsdom-sixteen": "^1.0.3",
160+
"jest-fetch-mock": "^3.0.3",
160161
"matrix-mock-request": "^1.2.3",
161162
"matrix-react-test-utils": "^0.2.2",
162163
"olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz",

res/css/views/messages/_MFileBody.scss

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2015, 2016 OpenMarket Ltd
2+
Copyright 2015, 2016, 2021 The Matrix.org Foundation C.I.C.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -16,6 +16,19 @@ limitations under the License.
1616

1717
.mx_MFileBody_download {
1818
color: $accent-color;
19+
20+
.mx_MFileBody_download_icon {
21+
// 12px instead of 14px to better match surrounding font size
22+
width: 12px;
23+
height: 12px;
24+
mask-size: 12px;
25+
26+
mask-position: center;
27+
mask-repeat: no-repeat;
28+
mask-image: url("$(res)/img/download.svg");
29+
background-color: $accent-color;
30+
display: inline-block;
31+
}
1932
}
2033

2134
.mx_MFileBody_download a {

src/Avatar.ts

Lines changed: 9 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,23 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo";
1817
import {RoomMember} from "matrix-js-sdk/src/models/room-member";
1918
import {User} from "matrix-js-sdk/src/models/user";
2019
import {Room} from "matrix-js-sdk/src/models/room";
2120

22-
import {MatrixClientPeg} from './MatrixClientPeg';
2321
import DMRoomMap from './utils/DMRoomMap';
22+
import {mediaFromMxc} from "./customisations/Media";
2423

2524
export type ResizeMethod = "crop" | "scale";
2625

2726
// Not to be used for BaseAvatar urls as that has similar default avatar fallback already
2827
export function avatarUrlForMember(member: RoomMember, width: number, height: number, resizeMethod: ResizeMethod) {
2928
let url: string;
30-
if (member && member.getAvatarUrl) {
31-
url = member.getAvatarUrl(
32-
MatrixClientPeg.get().getHomeserverUrl(),
29+
if (member?.getMxcAvatarUrl()) {
30+
url = mediaFromMxc(member.getMxcAvatarUrl()).getThumbnailOfSourceHttp(
3331
Math.floor(width * window.devicePixelRatio),
3432
Math.floor(height * window.devicePixelRatio),
3533
resizeMethod,
36-
false,
37-
false,
3834
);
3935
}
4036
if (!url) {
@@ -47,16 +43,12 @@ export function avatarUrlForMember(member: RoomMember, width: number, height: nu
4743
}
4844

4945
export function avatarUrlForUser(user: User, width: number, height: number, resizeMethod?: ResizeMethod) {
50-
const url = getHttpUriForMxc(
51-
MatrixClientPeg.get().getHomeserverUrl(), user.avatarUrl,
46+
if (!user.avatarUrl) return null;
47+
return mediaFromMxc(user.avatarUrl).getThumbnailOfSourceHttp(
5248
Math.floor(width * window.devicePixelRatio),
5349
Math.floor(height * window.devicePixelRatio),
5450
resizeMethod,
5551
);
56-
if (!url || url.length === 0) {
57-
return null;
58-
}
59-
return url;
6052
}
6153

6254
function isValidHexColor(color: string): boolean {
@@ -154,15 +146,8 @@ export function getInitialLetter(name: string): string {
154146
export function avatarUrlForRoom(room: Room, width: number, height: number, resizeMethod?: ResizeMethod) {
155147
if (!room) return null; // null-guard
156148

157-
const explicitRoomAvatar = room.getAvatarUrl(
158-
MatrixClientPeg.get().getHomeserverUrl(),
159-
width,
160-
height,
161-
resizeMethod,
162-
false,
163-
);
164-
if (explicitRoomAvatar) {
165-
return explicitRoomAvatar;
149+
if (room.getMxcAvatarUrl()) {
150+
return mediaFromMxc(room.getMxcAvatarUrl()).getThumbnailOfSourceHttp(width, height, resizeMethod);
166151
}
167152

168153
// space rooms cannot be DMs so skip the rest
@@ -177,14 +162,8 @@ export function avatarUrlForRoom(room: Room, width: number, height: number, resi
177162
// then still try to show any avatar (pref. other member)
178163
otherMember = room.getAvatarFallbackMember();
179164
}
180-
if (otherMember) {
181-
return otherMember.getAvatarUrl(
182-
MatrixClientPeg.get().getHomeserverUrl(),
183-
width,
184-
height,
185-
resizeMethod,
186-
false,
187-
);
165+
if (otherMember?.getMxcAvatarUrl()) {
166+
return mediaFromMxc(otherMember.getMxcAvatarUrl()).getThumbnailOfSourceHttp(width, height, resizeMethod);
188167
}
189168
return null;
190169
}

src/HtmlUtils.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@ import { AllHtmlEntities } from 'html-entities';
3232
import SettingsStore from './settings/SettingsStore';
3333
import cheerio from 'cheerio';
3434

35-
import {MatrixClientPeg} from './MatrixClientPeg';
3635
import {tryTransformPermalinkToLocalHref} from "./utils/permalinks/Permalinks";
3736
import {SHORTCODE_TO_EMOJI, getEmojiFromUnicode} from "./emoji";
3837
import ReplyThread from "./components/views/elements/ReplyThread";
38+
import {mediaFromMxc} from "./customisations/Media";
3939

4040
linkifyMatrix(linkify);
4141

@@ -181,11 +181,9 @@ const transformTags: IExtendedSanitizeOptions["transformTags"] = { // custom to
181181
if (!attribs.src || !attribs.src.startsWith('mxc://') || !SettingsStore.getValue("showImages")) {
182182
return { tagName, attribs: {}};
183183
}
184-
attribs.src = MatrixClientPeg.get().mxcUrlToHttp(
185-
attribs.src,
186-
attribs.width || 800,
187-
attribs.height || 600,
188-
);
184+
const width = Number(attribs.width) || 800;
185+
const height = Number(attribs.height) || 600;
186+
attribs.src = mediaFromMxc(attribs.src).getThumbnailOfSourceHttp(width, height);
189187
return { tagName, attribs };
190188
},
191189
'code': function(tagName: string, attribs: sanitizeHtml.Attributes) {

src/Notifier.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {SettingLevel} from "./settings/SettingLevel";
3636
import {isPushNotifyDisabled} from "./settings/controllers/NotificationControllers";
3737
import RoomViewStore from "./stores/RoomViewStore";
3838
import UserActivity from "./UserActivity";
39+
import {mediaFromMxc} from "./customisations/Media";
3940

4041
/*
4142
* Dispatches:
@@ -150,7 +151,7 @@ export const Notifier = {
150151
// Ideally in here we could use MSC1310 to detect the type of file, and reject it.
151152

152153
return {
153-
url: MatrixClientPeg.get().mxcUrlToHttp(content.url),
154+
url: mediaFromMxc(content.url).srcHttp,
154155
name: content.name,
155156
type: content.type,
156157
size: content.size,

src/autocomplete/CommunityProvider.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {sortBy} from "lodash";
2727
import {makeGroupPermalink} from "../utils/permalinks/Permalinks";
2828
import {ICompletion, ISelectionRange} from "./Autocompleter";
2929
import FlairStore from "../stores/FlairStore";
30+
import {mediaFromMxc} from "../customisations/Media";
3031

3132
const COMMUNITY_REGEX = /\B\+\S*/g;
3233

@@ -95,7 +96,7 @@ export default class CommunityProvider extends AutocompleteProvider {
9596
name={name || groupId}
9697
width={24}
9798
height={24}
98-
url={avatarUrl ? cli.mxcUrlToHttp(avatarUrl, 24, 24) : null} />
99+
url={avatarUrl ? mediaFromMxc(avatarUrl).getSquareThumbnailHttp(24) : null} />
99100
</PillCompletion>
100101
),
101102
range,

src/components/structures/GroupView.js

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import {Group} from "matrix-js-sdk";
3939
import {allSettled, sleep} from "../../utils/promise";
4040
import RightPanelStore from "../../stores/RightPanelStore";
4141
import AutoHideScrollbar from "./AutoHideScrollbar";
42+
import {mediaFromMxc} from "../../customisations/Media";
4243
import {replaceableComponent} from "../../utils/replaceableComponent";
4344

4445
const LONG_DESC_PLACEHOLDER = _td(
@@ -368,8 +369,7 @@ class FeaturedUser extends React.Component {
368369

369370
const permalink = makeUserPermalink(this.props.summaryInfo.user_id);
370371
const userNameNode = <a href={permalink} onClick={this.onClick}>{ name }</a>;
371-
const httpUrl = MatrixClientPeg.get()
372-
.mxcUrlToHttp(this.props.summaryInfo.avatar_url, 64, 64);
372+
const httpUrl = mediaFromMxc(this.props.summaryInfo.avatar_url).getSquareThumbnailHttp(64);
373373

374374
const deleteButton = this.props.editing ?
375375
<img
@@ -981,10 +981,9 @@ export default class GroupView extends React.Component {
981981
<Spinner />
982982
</div>;
983983
}
984-
const httpInviterAvatar = this.state.inviterProfile ?
985-
this._matrixClient.mxcUrlToHttp(
986-
this.state.inviterProfile.avatarUrl, 36, 36,
987-
) : null;
984+
const httpInviterAvatar = this.state.inviterProfile
985+
? mediaFromMxc(this.state.inviterProfile.avatarUrl).getSquareThumbnailHttp(36)
986+
: null;
988987

989988
const inviter = group.inviter || {};
990989
let inviterName = inviter.userId;

src/components/structures/LeftPanel.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,11 @@ import {Key} from "../../Keyboard";
3636
import IndicatorScrollbar from "../structures/IndicatorScrollbar";
3737
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
3838
import { OwnProfileStore } from "../../stores/OwnProfileStore";
39-
import { MatrixClientPeg } from "../../MatrixClientPeg";
4039
import RoomListNumResults from "../views/rooms/RoomListNumResults";
4140
import LeftPanelWidget from "./LeftPanelWidget";
4241
import SpacePanel from "../views/spaces/SpacePanel";
4342
import {replaceableComponent} from "../../utils/replaceableComponent";
43+
import {mediaFromMxc} from "../../customisations/Media";
4444

4545
interface IProps {
4646
isMinimized: boolean;
@@ -121,7 +121,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
121121
let avatarUrl = OwnProfileStore.instance.getHttpAvatarUrl(avatarSize);
122122
const settingBgMxc = SettingsStore.getValue("RoomList.backgroundImage");
123123
if (settingBgMxc) {
124-
avatarUrl = MatrixClientPeg.get().mxcUrlToHttp(settingBgMxc, avatarSize, avatarSize);
124+
avatarUrl = mediaFromMxc(settingBgMxc).getSquareThumbnailHttp(avatarSize);
125125
}
126126

127127
const avatarUrlProp = `url(${avatarUrl})`;

src/components/structures/RoomDirectory.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@ import { _t } from '../../languageHandler';
2727
import SdkConfig from '../../SdkConfig';
2828
import { instanceForInstanceId, protocolNameForInstanceId } from '../../utils/DirectoryUtils';
2929
import Analytics from '../../Analytics';
30-
import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo";
3130
import {ALL_ROOMS} from "../views/directory/NetworkDropdown";
3231
import SettingsStore from "../../settings/SettingsStore";
3332
import GroupFilterOrderStore from "../../stores/GroupFilterOrderStore";
3433
import GroupStore from "../../stores/GroupStore";
3534
import FlairStore from "../../stores/FlairStore";
3635
import CountlyAnalytics from "../../CountlyAnalytics";
3736
import {replaceableComponent} from "../../utils/replaceableComponent";
37+
import {mediaFromMxc} from "../../customisations/Media";
3838

3939
const MAX_NAME_LENGTH = 80;
4040
const MAX_TOPIC_LENGTH = 800;
@@ -521,10 +521,9 @@ export default class RoomDirectory extends React.Component {
521521
topic = `${topic.substring(0, MAX_TOPIC_LENGTH)}...`;
522522
}
523523
topic = linkifyAndSanitizeHtml(topic);
524-
const avatarUrl = getHttpUriForMxc(
525-
MatrixClientPeg.get().getHomeserverUrl(),
526-
room.avatar_url, 32, 32, "crop",
527-
);
524+
let avatarUrl = null;
525+
if (room.avatar_url) avatarUrl = mediaFromMxc(room.avatar_url).getSquareThumbnailHttp(32);
526+
528527
return [
529528
<div key={ `${room.room_id}_avatar` }
530529
onClick={(ev) => this.onRoomClicked(room, ev)}

0 commit comments

Comments
 (0)