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

Commit 4c1e4f5

Browse files
authored
Fix "[object Promise]" appearing in HTML exports (#9975)
Fixes element-hq/element-web#24272
1 parent 3e2bf56 commit 4c1e4f5

File tree

13 files changed

+893
-82
lines changed

13 files changed

+893
-82
lines changed

src/DateUtils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ function withinCurrentYear(prevDate: Date, nextDate: Date): boolean {
175175
return prevDate.getFullYear() === nextDate.getFullYear();
176176
}
177177

178-
export function wantsDateSeparator(prevEventDate: Date, nextEventDate: Date): boolean {
178+
export function wantsDateSeparator(prevEventDate: Date | undefined, nextEventDate: Date | undefined): boolean {
179179
if (!nextEventDate || !prevEventDate) {
180180
return false;
181181
}

src/components/structures/MessagePanel.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ const groupedStateEvents = [
7272
// check if there is a previous event and it has the same sender as this event
7373
// and the types are the same/is in continuedTypes and the time between them is <= CONTINUATION_MAX_INTERVAL
7474
export function shouldFormContinuation(
75-
prevEvent: MatrixEvent,
75+
prevEvent: MatrixEvent | null,
7676
mxEvent: MatrixEvent,
7777
showHiddenEvents: boolean,
7878
threadsEnabled: boolean,
@@ -821,7 +821,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
821821
// here.
822822
return !this.props.canBackPaginate;
823823
}
824-
return wantsDateSeparator(prevEvent.getDate(), nextEventDate);
824+
return wantsDateSeparator(prevEvent.getDate() || undefined, nextEventDate);
825825
}
826826

827827
// Get a list of read receipts that should be shown next to this event

src/components/views/dialogs/MessageEditHistoryDialog.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ export default class MessageEditHistoryDialog extends React.PureComponent<IProps
130130
}
131131
const baseEventId = this.props.mxEvent.getId();
132132
allEvents.forEach((e, i) => {
133-
if (!lastEvent || wantsDateSeparator(lastEvent.getDate(), e.getDate())) {
133+
if (!lastEvent || wantsDateSeparator(lastEvent.getDate() || undefined, e.getDate() || undefined)) {
134134
nodes.push(
135135
<li key={e.getTs() + "~"}>
136136
<DateSeparator roomId={e.getRoomId()} ts={e.getTs()} />

src/components/views/rooms/SearchResultTile.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export default class SearchResultTile extends React.Component<IProps> {
8484
// is this a continuation of the previous message?
8585
const continuation =
8686
prevEv &&
87-
!wantsDateSeparator(prevEv.getDate(), mxEv.getDate()) &&
87+
!wantsDateSeparator(prevEv.getDate() || undefined, mxEv.getDate() || undefined) &&
8888
shouldFormContinuation(
8989
prevEv,
9090
mxEv,
@@ -96,7 +96,10 @@ export default class SearchResultTile extends React.Component<IProps> {
9696
let lastInSection = true;
9797
const nextEv = timeline[j + 1];
9898
if (nextEv) {
99-
const willWantDateSeparator = wantsDateSeparator(mxEv.getDate(), nextEv.getDate());
99+
const willWantDateSeparator = wantsDateSeparator(
100+
mxEv.getDate() || undefined,
101+
nextEv.getDate() || undefined,
102+
);
100103
lastInSection =
101104
willWantDateSeparator ||
102105
mxEv.getSender() !== nextEv.getSender() ||

src/utils/exportUtils/HtmlExport.tsx

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2021 The Matrix.org Foundation C.I.C.
2+
Copyright 2021, 2023 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.
@@ -66,7 +66,7 @@ export default class HTMLExporter extends Exporter {
6666
}
6767

6868
protected async getRoomAvatar(): Promise<ReactNode> {
69-
let blob: Blob;
69+
let blob: Blob | undefined = undefined;
7070
const avatarUrl = Avatar.avatarUrlForRoom(this.room, 32, 32, "crop");
7171
const avatarPath = "room.png";
7272
if (avatarUrl) {
@@ -85,7 +85,7 @@ export default class HTMLExporter extends Exporter {
8585
height={32}
8686
name={this.room.name}
8787
title={this.room.name}
88-
url={blob ? avatarPath : null}
88+
url={blob ? avatarPath : ""}
8989
resizeMethod="crop"
9090
/>
9191
);
@@ -96,9 +96,9 @@ export default class HTMLExporter extends Exporter {
9696
const roomAvatar = await this.getRoomAvatar();
9797
const exportDate = formatFullDateNoDayNoTime(new Date());
9898
const creator = this.room.currentState.getStateEvents(EventType.RoomCreate, "")?.getSender();
99-
const creatorName = this.room?.getMember(creator)?.rawDisplayName || creator;
100-
const exporter = this.client.getUserId();
101-
const exporterName = this.room?.getMember(exporter)?.rawDisplayName;
99+
const creatorName = (creator ? this.room.getMember(creator)?.rawDisplayName : creator) || creator;
100+
const exporter = this.client.getUserId()!;
101+
const exporterName = this.room.getMember(exporter)?.rawDisplayName;
102102
const topic = this.room.currentState.getStateEvents(EventType.RoomTopic, "")?.getContent()?.topic || "";
103103
const createdText = _t("%(creatorName)s created this room.", {
104104
creatorName,
@@ -217,20 +217,19 @@ export default class HTMLExporter extends Exporter {
217217
</html>`;
218218
}
219219

220-
protected getAvatarURL(event: MatrixEvent): string {
220+
protected getAvatarURL(event: MatrixEvent): string | undefined {
221221
const member = event.sender;
222-
return (
223-
member.getMxcAvatarUrl() && mediaFromMxc(member.getMxcAvatarUrl()).getThumbnailOfSourceHttp(30, 30, "crop")
224-
);
222+
const avatarUrl = member?.getMxcAvatarUrl();
223+
return avatarUrl ? mediaFromMxc(avatarUrl).getThumbnailOfSourceHttp(30, 30, "crop") : undefined;
225224
}
226225

227226
protected async saveAvatarIfNeeded(event: MatrixEvent): Promise<void> {
228-
const member = event.sender;
227+
const member = event.sender!;
229228
if (!this.avatars.has(member.userId)) {
230229
try {
231230
const avatarUrl = this.getAvatarURL(event);
232231
this.avatars.set(member.userId, true);
233-
const image = await fetch(avatarUrl);
232+
const image = await fetch(avatarUrl!);
234233
const blob = await image.blob();
235234
this.addFile(`users/${member.userId.replace(/:/g, "-")}.png`, blob);
236235
} catch (err) {
@@ -239,19 +238,19 @@ export default class HTMLExporter extends Exporter {
239238
}
240239
}
241240

242-
protected async getDateSeparator(event: MatrixEvent): Promise<string> {
241+
protected getDateSeparator(event: MatrixEvent): string {
243242
const ts = event.getTs();
244243
const dateSeparator = (
245244
<li key={ts}>
246-
<DateSeparator forExport={true} key={ts} roomId={event.getRoomId()} ts={ts} />
245+
<DateSeparator forExport={true} key={ts} roomId={event.getRoomId()!} ts={ts} />
247246
</li>
248247
);
249248
return renderToStaticMarkup(dateSeparator);
250249
}
251250

252-
protected async needsDateSeparator(event: MatrixEvent, prevEvent: MatrixEvent): Promise<boolean> {
253-
if (prevEvent == null) return true;
254-
return wantsDateSeparator(prevEvent.getDate(), event.getDate());
251+
protected needsDateSeparator(event: MatrixEvent, prevEvent: MatrixEvent | null): boolean {
252+
if (!prevEvent) return true;
253+
return wantsDateSeparator(prevEvent.getDate() || undefined, event.getDate() || undefined);
255254
}
256255

257256
public getEventTile(mxEv: MatrixEvent, continuation: boolean): JSX.Element {
@@ -264,9 +263,7 @@ export default class HTMLExporter extends Exporter {
264263
isRedacted={mxEv.isRedacted()}
265264
replacingEventId={mxEv.replacingEventId()}
266265
forExport={true}
267-
readReceipts={null}
268266
alwaysShowTimestamps={true}
269-
readReceiptMap={null}
270267
showUrlPreview={false}
271268
checkUnmounting={() => false}
272269
isTwelveHour={false}
@@ -275,7 +272,6 @@ export default class HTMLExporter extends Exporter {
275272
permalinkCreator={this.permalinkCreator}
276273
lastSuccessful={false}
277274
isSelectedEvent={false}
278-
getRelationsForEvent={null}
279275
showReactions={false}
280276
layout={Layout.Group}
281277
showReadReceipts={false}
@@ -286,7 +282,8 @@ export default class HTMLExporter extends Exporter {
286282
}
287283

288284
protected async getEventTileMarkup(mxEv: MatrixEvent, continuation: boolean, filePath?: string): Promise<string> {
289-
const hasAvatar = !!this.getAvatarURL(mxEv);
285+
const avatarUrl = this.getAvatarURL(mxEv);
286+
const hasAvatar = !!avatarUrl;
290287
if (hasAvatar) await this.saveAvatarIfNeeded(mxEv);
291288
const EventTile = this.getEventTile(mxEv, continuation);
292289
let eventTileMarkup: string;
@@ -312,8 +309,8 @@ export default class HTMLExporter extends Exporter {
312309
eventTileMarkup = eventTileMarkup.replace(/<span class="mx_MFileBody_info_icon".*?>.*?<\/span>/, "");
313310
if (hasAvatar) {
314311
eventTileMarkup = eventTileMarkup.replace(
315-
encodeURI(this.getAvatarURL(mxEv)).replace(/&/g, "&amp;"),
316-
`users/${mxEv.sender.userId.replace(/:/g, "-")}.png`,
312+
encodeURI(avatarUrl).replace(/&/g, "&amp;"),
313+
`users/${mxEv.sender!.userId.replace(/:/g, "-")}.png`,
317314
);
318315
}
319316
return eventTileMarkup;

src/utils/exportUtils/exportCSS.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ const getExportCSS = async (usedClasses: Set<string>): Promise<string> => {
5858

5959
// If the light theme isn't loaded we will have to fetch & parse it manually
6060
if (!stylesheets.some(isLightTheme)) {
61-
const href = document.querySelector<HTMLLinkElement>('link[rel="stylesheet"][href$="theme-light.css"]').href;
62-
stylesheets.push(await getRulesFromCssFile(href));
61+
const href = document.querySelector<HTMLLinkElement>('link[rel="stylesheet"][href$="theme-light.css"]')?.href;
62+
if (href) stylesheets.push(await getRulesFromCssFile(href));
6363
}
6464

6565
let css = "";
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
Copyright 2023 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import React from "react";
18+
import { render, RenderResult } from "@testing-library/react";
19+
import { EventType, MatrixEvent } from "matrix-js-sdk/src/matrix";
20+
21+
import type { MatrixClient } from "matrix-js-sdk/src/matrix";
22+
import { flushPromises, mkMessage, stubClient } from "../../../test-utils";
23+
import MessageEditHistoryDialog from "../../../../src/components/views/dialogs/MessageEditHistoryDialog";
24+
25+
describe("<MessageEditHistory />", () => {
26+
const roomId = "!aroom:example.com";
27+
let client: jest.Mocked<MatrixClient>;
28+
let event: MatrixEvent;
29+
30+
beforeEach(() => {
31+
client = stubClient() as jest.Mocked<MatrixClient>;
32+
event = mkMessage({
33+
event: true,
34+
user: "@user:example.com",
35+
room: "!room:example.com",
36+
msg: "My Great Message",
37+
});
38+
});
39+
40+
async function renderComponent(): Promise<RenderResult> {
41+
const result = render(<MessageEditHistoryDialog mxEvent={event} onFinished={jest.fn()} />);
42+
await flushPromises();
43+
return result;
44+
}
45+
46+
function mockEdits(...edits: { msg: string; ts: number | undefined }[]) {
47+
client.relations.mockImplementation(() =>
48+
Promise.resolve({
49+
events: edits.map(
50+
(e) =>
51+
new MatrixEvent({
52+
type: EventType.RoomMessage,
53+
room_id: roomId,
54+
origin_server_ts: e.ts,
55+
content: {
56+
body: e.msg,
57+
},
58+
}),
59+
),
60+
}),
61+
);
62+
}
63+
64+
it("should match the snapshot", async () => {
65+
mockEdits({ msg: "My Great Massage", ts: 1234 });
66+
67+
const { container } = await renderComponent();
68+
69+
expect(container).toMatchSnapshot();
70+
});
71+
72+
it("should support events with ", async () => {
73+
mockEdits(
74+
{ msg: "My Great Massage", ts: undefined },
75+
{ msg: "My Great Massage?", ts: undefined },
76+
{ msg: "My Great Missage", ts: undefined },
77+
);
78+
79+
const { container } = await renderComponent();
80+
81+
expect(container).toMatchSnapshot();
82+
});
83+
});

0 commit comments

Comments
 (0)