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

Commit 5fbc5af

Browse files
authored
Add support for rendering media captions (#43)
* Add support for rendering media captions * Run prettier * Deduplicate body props * Add basic test * Fix import order in test * Fix test?
1 parent 26b0e83 commit 5fbc5af

File tree

4 files changed

+113
-22
lines changed

4 files changed

+113
-22
lines changed

src/components/views/messages/MessageEvent.tsx

+37-20
Original file line numberDiff line numberDiff line change
@@ -190,25 +190,42 @@ export default class MessageEvent extends React.Component<IProps> implements IMe
190190
}
191191
}
192192

193-
return BodyType ? (
194-
<BodyType
195-
ref={this.body}
196-
mxEvent={this.props.mxEvent}
197-
highlights={this.props.highlights}
198-
highlightLink={this.props.highlightLink}
199-
showUrlPreview={this.props.showUrlPreview}
200-
forExport={this.props.forExport}
201-
maxImageHeight={this.props.maxImageHeight}
202-
replacingEventId={this.props.replacingEventId}
203-
editState={this.props.editState}
204-
onHeightChanged={this.props.onHeightChanged}
205-
onMessageAllowed={this.onTileUpdate}
206-
permalinkCreator={this.props.permalinkCreator}
207-
mediaEventHelper={this.mediaHelper}
208-
getRelationsForEvent={this.props.getRelationsForEvent}
209-
isSeeingThroughMessageHiddenForModeration={this.props.isSeeingThroughMessageHiddenForModeration}
210-
inhibitInteraction={this.props.inhibitInteraction}
211-
/>
212-
) : null;
193+
const hasCaption =
194+
[MsgType.Image, MsgType.File, MsgType.Audio, MsgType.Video].includes(msgtype as MsgType) &&
195+
content.filename &&
196+
content.filename !== content.body;
197+
const bodyProps: IBodyProps = {
198+
ref: this.body,
199+
mxEvent: this.props.mxEvent,
200+
highlights: this.props.highlights,
201+
highlightLink: this.props.highlightLink,
202+
showUrlPreview: this.props.showUrlPreview,
203+
forExport: this.props.forExport,
204+
maxImageHeight: this.props.maxImageHeight,
205+
replacingEventId: this.props.replacingEventId,
206+
editState: this.props.editState,
207+
onHeightChanged: this.props.onHeightChanged,
208+
onMessageAllowed: this.onTileUpdate,
209+
permalinkCreator: this.props.permalinkCreator,
210+
mediaEventHelper: this.mediaHelper,
211+
getRelationsForEvent: this.props.getRelationsForEvent,
212+
isSeeingThroughMessageHiddenForModeration: this.props.isSeeingThroughMessageHiddenForModeration,
213+
inhibitInteraction: this.props.inhibitInteraction,
214+
};
215+
if (hasCaption) {
216+
return <CaptionBody {...bodyProps} WrappedBodyType={BodyType} />;
217+
}
218+
219+
return BodyType ? <BodyType {...bodyProps} /> : null;
213220
}
214221
}
222+
223+
const CaptionBody: React.FunctionComponent<IBodyProps & { WrappedBodyType: React.ComponentType<IBodyProps> }> = ({
224+
WrappedBodyType,
225+
...props
226+
}) => (
227+
<div className="mx_EventTile_content">
228+
<WrappedBodyType {...props} />
229+
<TextualBody {...{ ...props, ref: undefined }} />
230+
</div>
231+
);

src/components/views/messages/TextualBody.tsx

+11
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,9 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
556556
const content = mxEvent.getContent();
557557
const isNotice = content.msgtype === MsgType.Notice;
558558
const isEmote = content.msgtype === MsgType.Emote;
559+
const isCaption = [MsgType.Image, MsgType.File, MsgType.Audio, MsgType.Video].includes(
560+
content.msgtype as MsgType,
561+
);
559562

560563
const willHaveWrapper =
561564
this.props.replacingEventId || this.props.isSeeingThroughMessageHiddenForModeration || isEmote;
@@ -635,6 +638,14 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
635638
</div>
636639
);
637640
}
641+
if (isCaption) {
642+
return (
643+
<div className="mx_MTextBody mx_EventTile_caption" onClick={this.onBodyLinkClick}>
644+
{body}
645+
{widgets}
646+
</div>
647+
);
648+
}
638649
return (
639650
<div className="mx_MTextBody mx_EventTile_content" onClick={this.onBodyLinkClick}>
640651
{body}

src/utils/FileUtils.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ export function presentableTextForFile(
3838
shortened = false,
3939
): string {
4040
let text = fallbackText;
41-
if (content.body?.length) {
41+
if (content.filename?.length) {
42+
text = content.filename;
43+
} else if (content.body?.length) {
4244
// The content body should be the name of the file including a
4345
// file extension.
4446
text = content.body;

test/components/views/messages/MessageEvent-test.tsx

+62-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ Please see LICENSE files in the repository root for full details.
88

99
import React from "react";
1010
import { render, RenderResult } from "@testing-library/react";
11-
import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
11+
import { MatrixClient, MatrixEvent, EventType, Room, MsgType } from "matrix-js-sdk/src/matrix";
12+
import fetchMock from "fetch-mock-jest";
13+
import fs from "fs";
14+
import path from "path";
1215

1316
import SettingsStore from "../../../../src/settings/SettingsStore";
1417
import { VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from "../../../../src/voice-broadcast";
@@ -25,6 +28,26 @@ jest.mock("../../../../src/voice-broadcast/components/VoiceBroadcastBody", () =>
2528
VoiceBroadcastBody: () => <div data-testid="voice-broadcast-body" />,
2629
}));
2730

31+
jest.mock("../../../../src/components/views/messages/MImageBody", () => ({
32+
__esModule: true,
33+
default: () => <div data-testid="image-body" />,
34+
}));
35+
36+
jest.mock("../../../../src/components/views/messages/MImageReplyBody", () => ({
37+
__esModule: true,
38+
default: () => <div data-testid="image-reply-body" />,
39+
}));
40+
41+
jest.mock("../../../../src/components/views/messages/MStickerBody", () => ({
42+
__esModule: true,
43+
default: () => <div data-testid="sticker-body" />,
44+
}));
45+
46+
jest.mock("../../../../src/components/views/messages/TextualBody.tsx", () => ({
47+
__esModule: true,
48+
default: () => <div data-testid="textual-body" />,
49+
}));
50+
2851
describe("MessageEvent", () => {
2952
let room: Room;
3053
let client: MatrixClient;
@@ -68,4 +91,42 @@ describe("MessageEvent", () => {
6891
result.getByTestId("voice-broadcast-body");
6992
});
7093
});
94+
95+
describe("when an image with a caption is sent", () => {
96+
let result: RenderResult;
97+
98+
beforeEach(() => {
99+
event = mkEvent({
100+
event: true,
101+
type: EventType.RoomMessage,
102+
user: client.getUserId()!,
103+
room: room.roomId,
104+
content: {
105+
body: "caption for a test image",
106+
format: "org.matrix.custom.html",
107+
formatted_body: "<strong>caption for a test image</strong>",
108+
msgtype: MsgType.Image,
109+
filename: "image.webp",
110+
info: {
111+
w: 40,
112+
h: 50,
113+
},
114+
url: "mxc://server/image",
115+
},
116+
});
117+
result = renderMessageEvent();
118+
});
119+
120+
it("should render a TextualBody and an ImageBody", () => {
121+
fetchMock.getOnce(
122+
"https://server/_matrix/media/v3/download/server/image",
123+
{
124+
body: fs.readFileSync(path.resolve(__dirname, "..", "..", "..", "images", "animated-logo.webp")),
125+
},
126+
{ sendAsJson: false },
127+
);
128+
result.getByTestId("image-body");
129+
result.getByTestId("textual-body");
130+
});
131+
});
71132
});

0 commit comments

Comments
 (0)