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

Fix "[object Promise]" appearing in HTML exports #9975

Merged
merged 3 commits into from
Jan 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion src/DateUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ function withinCurrentYear(prevDate: Date, nextDate: Date): boolean {
return prevDate.getFullYear() === nextDate.getFullYear();
}

export function wantsDateSeparator(prevEventDate: Date, nextEventDate: Date): boolean {
export function wantsDateSeparator(prevEventDate: Date | undefined, nextEventDate: Date | undefined): boolean {
SimonBrandner marked this conversation as resolved.
Show resolved Hide resolved
if (!nextEventDate || !prevEventDate) {
return false;
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/structures/MessagePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ const groupedStateEvents = [
// check if there is a previous event and it has the same sender as this event
// and the types are the same/is in continuedTypes and the time between them is <= CONTINUATION_MAX_INTERVAL
export function shouldFormContinuation(
prevEvent: MatrixEvent,
prevEvent: MatrixEvent | null,
mxEvent: MatrixEvent,
showHiddenEvents: boolean,
threadsEnabled: boolean,
Expand Down Expand Up @@ -821,7 +821,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
// here.
return !this.props.canBackPaginate;
}
return wantsDateSeparator(prevEvent.getDate(), nextEventDate);
return wantsDateSeparator(prevEvent.getDate() || undefined, nextEventDate);
}

// Get a list of read receipts that should be shown next to this event
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export default class MessageEditHistoryDialog extends React.PureComponent<IProps
}
const baseEventId = this.props.mxEvent.getId();
allEvents.forEach((e, i) => {
if (!lastEvent || wantsDateSeparator(lastEvent.getDate(), e.getDate())) {
if (!lastEvent || wantsDateSeparator(lastEvent.getDate() || undefined, e.getDate() || undefined)) {
nodes.push(
<li key={e.getTs() + "~"}>
<DateSeparator roomId={e.getRoomId()} ts={e.getTs()} />
Expand Down
7 changes: 5 additions & 2 deletions src/components/views/rooms/SearchResultTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export default class SearchResultTile extends React.Component<IProps> {
// is this a continuation of the previous message?
const continuation =
prevEv &&
!wantsDateSeparator(prevEv.getDate(), mxEv.getDate()) &&
!wantsDateSeparator(prevEv.getDate() || undefined, mxEv.getDate() || undefined) &&
shouldFormContinuation(
prevEv,
mxEv,
Expand All @@ -96,7 +96,10 @@ export default class SearchResultTile extends React.Component<IProps> {
let lastInSection = true;
const nextEv = timeline[j + 1];
if (nextEv) {
const willWantDateSeparator = wantsDateSeparator(mxEv.getDate(), nextEv.getDate());
const willWantDateSeparator = wantsDateSeparator(
mxEv.getDate() || undefined,
nextEv.getDate() || undefined,
);
lastInSection =
willWantDateSeparator ||
mxEv.getSender() !== nextEv.getSender() ||
Expand Down
43 changes: 20 additions & 23 deletions src/utils/exportUtils/HtmlExport.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Copyright 2021, 2023 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -66,7 +66,7 @@ export default class HTMLExporter extends Exporter {
}

protected async getRoomAvatar(): Promise<ReactNode> {
let blob: Blob;
let blob: Blob | undefined = undefined;
const avatarUrl = Avatar.avatarUrlForRoom(this.room, 32, 32, "crop");
const avatarPath = "room.png";
if (avatarUrl) {
Expand All @@ -85,7 +85,7 @@ export default class HTMLExporter extends Exporter {
height={32}
name={this.room.name}
title={this.room.name}
url={blob ? avatarPath : null}
url={blob ? avatarPath : ""}
resizeMethod="crop"
/>
);
Expand All @@ -96,9 +96,9 @@ export default class HTMLExporter extends Exporter {
const roomAvatar = await this.getRoomAvatar();
const exportDate = formatFullDateNoDayNoTime(new Date());
const creator = this.room.currentState.getStateEvents(EventType.RoomCreate, "")?.getSender();
const creatorName = this.room?.getMember(creator)?.rawDisplayName || creator;
const exporter = this.client.getUserId();
const exporterName = this.room?.getMember(exporter)?.rawDisplayName;
const creatorName = (creator ? this.room.getMember(creator)?.rawDisplayName : creator) || creator;
const exporter = this.client.getUserId()!;
const exporterName = this.room.getMember(exporter)?.rawDisplayName;
const topic = this.room.currentState.getStateEvents(EventType.RoomTopic, "")?.getContent()?.topic || "";
const createdText = _t("%(creatorName)s created this room.", {
creatorName,
Expand Down Expand Up @@ -217,20 +217,19 @@ export default class HTMLExporter extends Exporter {
</html>`;
}

protected getAvatarURL(event: MatrixEvent): string {
protected getAvatarURL(event: MatrixEvent): string | undefined {
const member = event.sender;
return (
member.getMxcAvatarUrl() && mediaFromMxc(member.getMxcAvatarUrl()).getThumbnailOfSourceHttp(30, 30, "crop")
);
const avatarUrl = member?.getMxcAvatarUrl();
return avatarUrl ? mediaFromMxc(avatarUrl).getThumbnailOfSourceHttp(30, 30, "crop") : undefined;
}

protected async saveAvatarIfNeeded(event: MatrixEvent): Promise<void> {
const member = event.sender;
const member = event.sender!;
if (!this.avatars.has(member.userId)) {
try {
const avatarUrl = this.getAvatarURL(event);
this.avatars.set(member.userId, true);
const image = await fetch(avatarUrl);
const image = await fetch(avatarUrl!);
const blob = await image.blob();
this.addFile(`users/${member.userId.replace(/:/g, "-")}.png`, blob);
} catch (err) {
Expand All @@ -239,19 +238,19 @@ export default class HTMLExporter extends Exporter {
}
}

protected async getDateSeparator(event: MatrixEvent): Promise<string> {
protected getDateSeparator(event: MatrixEvent): string {
const ts = event.getTs();
const dateSeparator = (
<li key={ts}>
<DateSeparator forExport={true} key={ts} roomId={event.getRoomId()} ts={ts} />
<DateSeparator forExport={true} key={ts} roomId={event.getRoomId()!} ts={ts} />
</li>
);
return renderToStaticMarkup(dateSeparator);
}

protected async needsDateSeparator(event: MatrixEvent, prevEvent: MatrixEvent): Promise<boolean> {
if (prevEvent == null) return true;
return wantsDateSeparator(prevEvent.getDate(), event.getDate());
protected needsDateSeparator(event: MatrixEvent, prevEvent: MatrixEvent | null): boolean {
if (!prevEvent) return true;
return wantsDateSeparator(prevEvent.getDate() || undefined, event.getDate() || undefined);
}

public getEventTile(mxEv: MatrixEvent, continuation: boolean): JSX.Element {
Expand All @@ -264,9 +263,7 @@ export default class HTMLExporter extends Exporter {
isRedacted={mxEv.isRedacted()}
replacingEventId={mxEv.replacingEventId()}
forExport={true}
readReceipts={null}
alwaysShowTimestamps={true}
readReceiptMap={null}
showUrlPreview={false}
checkUnmounting={() => false}
isTwelveHour={false}
Expand All @@ -275,7 +272,6 @@ export default class HTMLExporter extends Exporter {
permalinkCreator={this.permalinkCreator}
lastSuccessful={false}
isSelectedEvent={false}
getRelationsForEvent={null}
showReactions={false}
layout={Layout.Group}
showReadReceipts={false}
Expand All @@ -286,7 +282,8 @@ export default class HTMLExporter extends Exporter {
}

protected async getEventTileMarkup(mxEv: MatrixEvent, continuation: boolean, filePath?: string): Promise<string> {
const hasAvatar = !!this.getAvatarURL(mxEv);
const avatarUrl = this.getAvatarURL(mxEv);
const hasAvatar = !!avatarUrl;
if (hasAvatar) await this.saveAvatarIfNeeded(mxEv);
const EventTile = this.getEventTile(mxEv, continuation);
let eventTileMarkup: string;
Expand All @@ -312,8 +309,8 @@ export default class HTMLExporter extends Exporter {
eventTileMarkup = eventTileMarkup.replace(/<span class="mx_MFileBody_info_icon".*?>.*?<\/span>/, "");
if (hasAvatar) {
eventTileMarkup = eventTileMarkup.replace(
encodeURI(this.getAvatarURL(mxEv)).replace(/&/g, "&amp;"),
`users/${mxEv.sender.userId.replace(/:/g, "-")}.png`,
encodeURI(avatarUrl).replace(/&/g, "&amp;"),
`users/${mxEv.sender!.userId.replace(/:/g, "-")}.png`,
);
}
return eventTileMarkup;
Expand Down
4 changes: 2 additions & 2 deletions src/utils/exportUtils/exportCSS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ const getExportCSS = async (usedClasses: Set<string>): Promise<string> => {

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

let css = "";
Expand Down
83 changes: 83 additions & 0 deletions test/components/views/dialogs/MessageEditHistoryDialog-test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
Copyright 2023 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from "react";
import { render, RenderResult } from "@testing-library/react";
import { EventType, MatrixEvent } from "matrix-js-sdk/src/matrix";

import type { MatrixClient } from "matrix-js-sdk/src/matrix";
import { flushPromises, mkMessage, stubClient } from "../../../test-utils";
import MessageEditHistoryDialog from "../../../../src/components/views/dialogs/MessageEditHistoryDialog";

describe("<MessageEditHistory />", () => {
const roomId = "!aroom:example.com";
let client: jest.Mocked<MatrixClient>;
let event: MatrixEvent;

beforeEach(() => {
client = stubClient() as jest.Mocked<MatrixClient>;
event = mkMessage({
event: true,
user: "@user:example.com",
room: "!room:example.com",
msg: "My Great Message",
});
});

async function renderComponent(): Promise<RenderResult> {
const result = render(<MessageEditHistoryDialog mxEvent={event} onFinished={jest.fn()} />);
await flushPromises();
return result;
}

function mockEdits(...edits: { msg: string; ts: number | undefined }[]) {
client.relations.mockImplementation(() =>
Promise.resolve({
events: edits.map(
(e) =>
new MatrixEvent({
type: EventType.RoomMessage,
room_id: roomId,
origin_server_ts: e.ts,
content: {
body: e.msg,
},
}),
),
}),
);
}

it("should match the snapshot", async () => {
mockEdits({ msg: "My Great Massage", ts: 1234 });

const { container } = await renderComponent();

expect(container).toMatchSnapshot();
});

it("should support events with ", async () => {
mockEdits(
{ msg: "My Great Massage", ts: undefined },
{ msg: "My Great Massage?", ts: undefined },
{ msg: "My Great Missage", ts: undefined },
);

const { container } = await renderComponent();

expect(container).toMatchSnapshot();
});
});
Loading