From 59cd5180afa79c89856c471f722e6d3aa19a2167 Mon Sep 17 00:00:00 2001 From: David Langley Date: Fri, 18 Oct 2024 13:56:08 +0100 Subject: [PATCH] Add reactions to html export (#28210) * Absorb the matrix-react-sdk repository (#28192) Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com> Co-authored-by: github-merge-queue Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Florian Duros Co-authored-by: Kim Brose Co-authored-by: Florian Duros Co-authored-by: R Midhun Suresh Co-authored-by: dbkr <986903+dbkr@users.noreply.github.com> Co-authored-by: ElementRobot Co-authored-by: dbkr Co-authored-by: David Baker Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Co-authored-by: David Langley Co-authored-by: Michael Weimann Co-authored-by: Timshel Co-authored-by: Sahil Silare <32628578+sahil9001@users.noreply.github.com> Co-authored-by: Will Hunt Co-authored-by: Hubert Chathi Co-authored-by: Andrew Ferrazzutti Co-authored-by: Robin Co-authored-by: Tulir Asokan * Update dependency @sentry/browser to v8.33.0 [SECURITY] (#28194) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update babel monorepo (#28196) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update dependency @types/react to v17.0.83 (#28138) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update dependency @matrix-org/spec to v1.12.0 (#28200) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update dependency @formatjs/intl-segmenter to v11.5.9 (#28197) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Remove references to `MatrixClient.crypto` (#28204) * Remove `VerificationExplorer` * Remove `remakeolm` slash command * Remove call to `crypto.cancelAndResendAllOutgoingKeyRequests` * Remove crypto mock in `LoginWithQR-test.tsx` * Remove `StopGadWidgetDriver.sendToDevice` * Remove remaining mock * Update dependency typescript to v5.6.3 (#28198) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update dependency eslint-plugin-unicorn to v56 (#28202) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update dependency stylelint to v16.10.0 (#28201) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Update browserslist (#28199) * Update browserslist * Update tests Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> * Add reactions to html export and add test * Add reaction to snapshot test * Update snapshot output * Remove logging * Add reaction to html export screenshot test. * lint * Update reference screenshot. --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com> Co-authored-by: github-merge-queue Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Florian Duros Co-authored-by: Kim Brose Co-authored-by: Florian Duros Co-authored-by: R Midhun Suresh Co-authored-by: dbkr <986903+dbkr@users.noreply.github.com> Co-authored-by: ElementRobot Co-authored-by: dbkr Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Co-authored-by: Michael Weimann Co-authored-by: Timshel Co-authored-by: Sahil Silare <32628578+sahil9001@users.noreply.github.com> Co-authored-by: Will Hunt Co-authored-by: Hubert Chathi Co-authored-by: Andrew Ferrazzutti Co-authored-by: Robin Co-authored-by: Tulir Asokan --- .../e2e/chat-export/html-export.spec.ts | 5 +- playwright/e2e/read-receipts/index.ts | 9 +-- playwright/pages/client.ts | 23 +++++++ .../html-export.spec.ts/html-export-linux.png | Bin 50430 -> 50271 bytes src/utils/exportUtils/Exporter.ts | 12 +++- src/utils/exportUtils/HtmlExport.tsx | 3 +- .../utils/exportUtils/HTMLExport-test.ts | 57 +++++++++++++++++- .../__snapshots__/HTMLExport-test.ts.snap | 2 +- 8 files changed, 97 insertions(+), 14 deletions(-) diff --git a/playwright/e2e/chat-export/html-export.spec.ts b/playwright/e2e/chat-export/html-export.spec.ts index 0e12e8d93e..9a66a4907a 100644 --- a/playwright/e2e/chat-export/html-export.spec.ts +++ b/playwright/e2e/chat-export/html-export.spec.ts @@ -96,7 +96,10 @@ test.describe("HTML Export", () => { // Send a bunch of messages to populate the room for (let i = 1; i < 10; i++) { - await app.client.sendMessage(room.roomId, { body: `Testing ${i}`, msgtype: "m.text" }); + const respone = await app.client.sendMessage(room.roomId, { body: `Testing ${i}`, msgtype: "m.text" }); + if (i == 1) { + await app.client.reactToMessage(room.roomId, null, respone.event_id, "🙃"); + } } // Wait for all the messages to be displayed diff --git a/playwright/e2e/read-receipts/index.ts b/playwright/e2e/read-receipts/index.ts index 2db4bcbecf..747717908b 100644 --- a/playwright/e2e/read-receipts/index.ts +++ b/playwright/e2e/read-receipts/index.ts @@ -222,14 +222,7 @@ export class MessageBuilder { threadId: !ev.isThreadRoot ? ev.threadRootId : undefined, })); const roomId = await room.evaluate((room) => room.roomId); - - await bot.sendEvent(roomId, threadId ?? null, "m.reaction", { - "m.relates_to": { - rel_type: "m.annotation", - event_id: id, - key: reaction, - }, - }); + await bot.reactToMessage(roomId, threadId, id, reaction); } })(this); } diff --git a/playwright/pages/client.ts b/playwright/pages/client.ts index 7d62f42ae0..2dfe7484f5 100644 --- a/playwright/pages/client.ts +++ b/playwright/pages/client.ts @@ -143,6 +143,29 @@ export class Client { ); } + /** + * Send a reaction to to a message + * @param roomId ID of the room to send the reaction into + * @param threadId ID of the thread to send into or null for main timeline + * @param eventId Event ID of the message you are reacting to + * @param reaction The reaction text to send + * @returns + */ + public async reactToMessage( + roomId: string, + threadId: string | null, + eventId: string, + reaction: string, + ): Promise { + return this.sendEvent(roomId, threadId ?? null, "m.reaction", { + "m.relates_to": { + rel_type: "m.annotation", + event_id: eventId, + key: reaction, + }, + }); + } + /** * Create a room with given options. * @param options the options to apply when creating the room diff --git a/playwright/snapshots/chat-export/html-export.spec.ts/html-export-linux.png b/playwright/snapshots/chat-export/html-export.spec.ts/html-export-linux.png index 9d301e7919920b1beb892116b379b55344024161..6a490c2157b6c17d7bf2d7a4df209903bbc07b45 100644 GIT binary patch delta 15845 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 diff --git a/src/utils/exportUtils/Exporter.ts b/src/utils/exportUtils/Exporter.ts index ed85ba8d67..053878886b 100644 --- a/src/utils/exportUtils/Exporter.ts +++ b/src/utils/exportUtils/Exporter.ts @@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only Please see LICENSE files in the repository root for full details. */ -import { Direction, MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; -import { MediaEventContent } from "matrix-js-sdk/src/types"; +import { Direction, MatrixEvent, Relations, Room } from "matrix-js-sdk/src/matrix"; +import { EventType, MediaEventContent, RelationType } from "matrix-js-sdk/src/types"; import { saveAs } from "file-saver"; import { logger } from "matrix-js-sdk/src/logger"; import sanitizeFilename from "sanitize-filename"; @@ -284,5 +284,13 @@ export default abstract class Exporter { return mxEv.getType() === attachmentTypes[0] || attachmentTypes.includes(mxEv.getContent().msgtype!); } + protected getRelationsForEvent = ( + eventId: string, + relationType: RelationType | string, + eventType: EventType | string, + ): Relations | undefined => { + return this.room.getUnfilteredTimelineSet().relations.getChildEventsForEvent(eventId, relationType, eventType); + }; + public abstract export(): Promise; } diff --git a/src/utils/exportUtils/HtmlExport.tsx b/src/utils/exportUtils/HtmlExport.tsx index 9a16a9e44b..08e488e5ff 100644 --- a/src/utils/exportUtils/HtmlExport.tsx +++ b/src/utils/exportUtils/HtmlExport.tsx @@ -288,9 +288,10 @@ export default class HTMLExporter extends Exporter { permalinkCreator={this.permalinkCreator} lastSuccessful={false} isSelectedEvent={false} - showReactions={false} + showReactions={true} layout={Layout.Group} showReadReceipts={false} + getRelationsForEvent={this.getRelationsForEvent} /> diff --git a/test/unit-tests/utils/exportUtils/HTMLExport-test.ts b/test/unit-tests/utils/exportUtils/HTMLExport-test.ts index 7d57866fcd..0fc96e4db7 100644 --- a/test/unit-tests/utils/exportUtils/HTMLExport-test.ts +++ b/test/unit-tests/utils/exportUtils/HTMLExport-test.ts @@ -7,19 +7,24 @@ Please see LICENSE files in the repository root for full details. */ import { + EventTimeline, + EventTimelineSet, EventType, IRoomEvent, MatrixClient, MatrixEvent, MsgType, + Relations, + RelationType, Room, RoomMember, RoomState, } from "matrix-js-sdk/src/matrix"; import fetchMock from "fetch-mock-jest"; import escapeHtml from "escape-html"; +import { RelationsContainer } from "matrix-js-sdk/src/models/relations-container"; -import { filterConsole, mkStubRoom, REPEATABLE_DATE, stubClient } from "../../../test-utils"; +import { filterConsole, mkReaction, mkStubRoom, REPEATABLE_DATE, stubClient } from "../../../test-utils"; import { ExportType, IExportOptions } from "../../../../src/utils/exportUtils/exportUtils"; import SdkConfig from "../../../../src/SdkConfig"; import HTMLExporter from "../../../../src/utils/exportUtils/HtmlExport"; @@ -123,6 +128,35 @@ describe("HTMLExport", () => { fetchMock.get(media.srcHttp!, body); } + function mockReactionForMessage(message: IRoomEvent): MatrixEvent { + const firstMessage = new MatrixEvent(message); + const reaction = mkReaction(firstMessage); + + const relationsContainer = { + getRelations: jest.fn(), + getChildEventsForEvent: jest.fn(), + } as unknown as RelationsContainer; + const relations = new Relations(RelationType.Annotation, EventType.Reaction, client); + relations.addEvent(reaction); + relationsContainer.getChildEventsForEvent = jest + .fn() + .mockImplementation( + (eventId: string, relationType: RelationType | string, eventType: EventType | string) => { + if (eventId === firstMessage.getId()) { + return relations; + } + }, + ); + + const timelineSet = { + relations: relationsContainer, + getLiveTimeline: () => timeline, + } as unknown as EventTimelineSet; + const timeline = new EventTimeline(timelineSet); + room.getUnfilteredTimelineSet = jest.fn().mockReturnValue(timelineSet); + return reaction; + } + it("should throw when created with invalid config for LastNMessages", async () => { expect( () => @@ -167,6 +201,7 @@ describe("HTMLExport", () => { body: `Message #${i}`, }, })); + mockReactionForMessage(events[0]); mockMessages(...events); const exporter = new HTMLExporter( @@ -587,4 +622,24 @@ describe("HTMLExport", () => { expect(await file.text()).toContain("testing testing"); expect(client.createMessagesRequest).not.toHaveBeenCalled(); }); + + it("should include reactions", async () => { + const reaction = mockReactionForMessage(EVENT_MESSAGE); + mockMessages(EVENT_MESSAGE); + const exporter = new HTMLExporter( + room, + ExportType.LastNMessages, + { + attachmentsIncluded: false, + maxSize: 1_024 * 1_024, + numberOfMessages: 40, + }, + () => {}, + ); + + await exporter.export(); + + const file = getMessageFile(exporter); + expect(await file.text()).toContain(reaction.getContent()["m.relates_to"]?.key); + }); }); diff --git a/test/unit-tests/utils/exportUtils/__snapshots__/HTMLExport-test.ts.snap b/test/unit-tests/utils/exportUtils/__snapshots__/HTMLExport-test.ts.snap index cd1f63aa8b..6a7adbabb2 100644 --- a/test/unit-tests/utils/exportUtils/__snapshots__/HTMLExport-test.ts.snap +++ b/test/unit-tests/utils/exportUtils/__snapshots__/HTMLExport-test.ts.snap @@ -62,7 +62,7 @@ exports[`HTMLExport should export 1`] = `

-
  • @user48:example.com
    Message #48
  • @user47:example.com
    Message #47
  • @user46:example.com
    Message #46
  • @user45:example.com
    Message #45
  • @user44:example.com
    Message #44
  • @user43:example.com
    Message #43
  • @user42:example.com
    Message #42
  • @user41:example.com
    Message #41
  • @user40:example.com
    Message #40
  • @user39:example.com
    Message #39
  • @user38:example.com
    Message #38
  • @user37:example.com
    Message #37
  • @user36:example.com
    Message #36
  • @user35:example.com
    Message #35
  • @user34:example.com
    Message #34
  • @user33:example.com
    Message #33
  • @user32:example.com
    Message #32
  • @user31:example.com
    Message #31
  • @user30:example.com
    Message #30
  • @user29:example.com
    Message #29
  • @user28:example.com
    Message #28
  • @user27:example.com
    Message #27
  • @user26:example.com
    Message #26
  • @user25:example.com
    Message #25
  • @user24:example.com
    Message #24
  • @user23:example.com
    Message #23
  • @user22:example.com
    Message #22
  • @user21:example.com
    Message #21
  • @user20:example.com
    Message #20
  • @user19:example.com
    Message #19
  • @user18:example.com
    Message #18
  • @user17:example.com
    Message #17
  • @user16:example.com
    Message #16
  • @user15:example.com
    Message #15
  • @user14:example.com
    Message #14
  • @user13:example.com
    Message #13
  • @user12:example.com
    Message #12
  • @user11:example.com
    Message #11
  • @user10:example.com
    Message #10
  • @user9:example.com
    Message #9
  • @user8:example.com
    Message #8
  • @user7:example.com
    Message #7
  • @user6:example.com
    Message #6
  • @user5:example.com
    Message #5
  • @user4:example.com
    Message #4
  • @user3:example.com
    Message #3
  • @user2:example.com
    Message #2
  • @user1:example.com
    Message #1
  • @user0:example.com
    Message #0
  • +
  • @user49:example.com
    Message #49
  • @user48:example.com
    Message #48
  • @user47:example.com
    Message #47
  • @user46:example.com
    Message #46
  • @user45:example.com
    Message #45
  • @user44:example.com
    Message #44
  • @user43:example.com
    Message #43
  • @user42:example.com
    Message #42
  • @user41:example.com
    Message #41
  • @user40:example.com
    Message #40
  • @user39:example.com
    Message #39
  • @user38:example.com
    Message #38
  • @user37:example.com
    Message #37
  • @user36:example.com
    Message #36
  • @user35:example.com
    Message #35
  • @user34:example.com
    Message #34
  • @user33:example.com
    Message #33
  • @user32:example.com
    Message #32
  • @user31:example.com
    Message #31
  • @user30:example.com
    Message #30
  • @user29:example.com
    Message #29
  • @user28:example.com
    Message #28
  • @user27:example.com
    Message #27
  • @user26:example.com
    Message #26
  • @user25:example.com
    Message #25
  • @user24:example.com
    Message #24
  • @user23:example.com
    Message #23
  • @user22:example.com
    Message #22
  • @user21:example.com
    Message #21
  • @user20:example.com
    Message #20
  • @user19:example.com
    Message #19
  • @user18:example.com
    Message #18
  • @user17:example.com
    Message #17
  • @user16:example.com
    Message #16
  • @user15:example.com
    Message #15
  • @user14:example.com
    Message #14
  • @user13:example.com
    Message #13
  • @user12:example.com
    Message #12
  • @user11:example.com
    Message #11
  • @user10:example.com
    Message #10
  • @user9:example.com
    Message #9
  • @user8:example.com
    Message #8
  • @user7:example.com
    Message #7
  • @user6:example.com
    Message #6
  • @user5:example.com
    Message #5
  • @user4:example.com
    Message #4
  • @user3:example.com
    Message #3
  • @user2:example.com
    Message #2
  • @user1:example.com
    Message #1
  • @user0:example.com
    Message #0