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 <github-merge-queue@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Florian Duros <florian.duros@ormaz.fr>
Co-authored-by: Kim Brose <kim.brose@nordeck.net>
Co-authored-by: Florian Duros <florianduros@element.io>
Co-authored-by: R Midhun Suresh <hi@midhun.dev>
Co-authored-by: dbkr <986903+dbkr@users.noreply.github.com>
Co-authored-by: ElementRobot <releases@riot.im>
Co-authored-by: dbkr <dbkr@users.noreply.github.com>
Co-authored-by: David Baker <dbkr@users.noreply.github.com>
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 <davidl@element.io>
Co-authored-by: Michael Weimann <michaelw@matrix.org>
Co-authored-by: Timshel <Timshel@users.noreply.github.com>
Co-authored-by: Sahil Silare <32628578+sahil9001@users.noreply.github.com>
Co-authored-by: Will Hunt <will@half-shot.uk>
Co-authored-by: Hubert Chathi <hubert@uhoreg.ca>
Co-authored-by: Andrew Ferrazzutti <andrewf@element.io>
Co-authored-by: Robin <robin@robin.town>
Co-authored-by: Tulir Asokan <tulir@maunium.net>

* 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 <github-merge-queue@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Florian Duros <florian.duros@ormaz.fr>
Co-authored-by: Kim Brose <kim.brose@nordeck.net>
Co-authored-by: Florian Duros <florianduros@element.io>
Co-authored-by: R Midhun Suresh <hi@midhun.dev>
Co-authored-by: dbkr <986903+dbkr@users.noreply.github.com>
Co-authored-by: ElementRobot <releases@riot.im>
Co-authored-by: dbkr <dbkr@users.noreply.github.com>
Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
Co-authored-by: Michael Weimann <michaelw@matrix.org>
Co-authored-by: Timshel <Timshel@users.noreply.github.com>
Co-authored-by: Sahil Silare <32628578+sahil9001@users.noreply.github.com>
Co-authored-by: Will Hunt <will@half-shot.uk>
Co-authored-by: Hubert Chathi <hubert@uhoreg.ca>
Co-authored-by: Andrew Ferrazzutti <andrewf@element.io>
Co-authored-by: Robin <robin@robin.town>
Co-authored-by: Tulir Asokan <tulir@maunium.net>
pull/28007/head
David Langley 2024-10-18 13:56:08 +01:00 committed by GitHub
parent 06d1239608
commit 59cd5180af
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 97 additions and 14 deletions

View File

@ -96,7 +96,10 @@ test.describe("HTML Export", () => {
// Send a bunch of messages to populate the room // Send a bunch of messages to populate the room
for (let i = 1; i < 10; i++) { 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 // Wait for all the messages to be displayed

View File

@ -222,14 +222,7 @@ export class MessageBuilder {
threadId: !ev.isThreadRoot ? ev.threadRootId : undefined, threadId: !ev.isThreadRoot ? ev.threadRootId : undefined,
})); }));
const roomId = await room.evaluate((room) => room.roomId); const roomId = await room.evaluate((room) => room.roomId);
await bot.reactToMessage(roomId, threadId, id, reaction);
await bot.sendEvent(roomId, threadId ?? null, "m.reaction", {
"m.relates_to": {
rel_type: "m.annotation",
event_id: id,
key: reaction,
},
});
} }
})(this); })(this);
} }

View File

@ -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<ISendEventResponse> {
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. * Create a room with given options.
* @param options the options to apply when creating the room * @param options the options to apply when creating the room

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

@ -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. Please see LICENSE files in the repository root for full details.
*/ */
import { Direction, MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; import { Direction, MatrixEvent, Relations, Room } from "matrix-js-sdk/src/matrix";
import { MediaEventContent } from "matrix-js-sdk/src/types"; import { EventType, MediaEventContent, RelationType } from "matrix-js-sdk/src/types";
import { saveAs } from "file-saver"; import { saveAs } from "file-saver";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import sanitizeFilename from "sanitize-filename"; import sanitizeFilename from "sanitize-filename";
@ -284,5 +284,13 @@ export default abstract class Exporter {
return mxEv.getType() === attachmentTypes[0] || attachmentTypes.includes(mxEv.getContent().msgtype!); 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<void>; public abstract export(): Promise<void>;
} }

View File

@ -288,9 +288,10 @@ export default class HTMLExporter extends Exporter {
permalinkCreator={this.permalinkCreator} permalinkCreator={this.permalinkCreator}
lastSuccessful={false} lastSuccessful={false}
isSelectedEvent={false} isSelectedEvent={false}
showReactions={false} showReactions={true}
layout={Layout.Group} layout={Layout.Group}
showReadReceipts={false} showReadReceipts={false}
getRelationsForEvent={this.getRelationsForEvent}
/> />
</TooltipProvider> </TooltipProvider>
</MatrixClientContext.Provider> </MatrixClientContext.Provider>

View File

@ -7,19 +7,24 @@ Please see LICENSE files in the repository root for full details.
*/ */
import { import {
EventTimeline,
EventTimelineSet,
EventType, EventType,
IRoomEvent, IRoomEvent,
MatrixClient, MatrixClient,
MatrixEvent, MatrixEvent,
MsgType, MsgType,
Relations,
RelationType,
Room, Room,
RoomMember, RoomMember,
RoomState, RoomState,
} from "matrix-js-sdk/src/matrix"; } from "matrix-js-sdk/src/matrix";
import fetchMock from "fetch-mock-jest"; import fetchMock from "fetch-mock-jest";
import escapeHtml from "escape-html"; 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 { ExportType, IExportOptions } from "../../../../src/utils/exportUtils/exportUtils";
import SdkConfig from "../../../../src/SdkConfig"; import SdkConfig from "../../../../src/SdkConfig";
import HTMLExporter from "../../../../src/utils/exportUtils/HtmlExport"; import HTMLExporter from "../../../../src/utils/exportUtils/HtmlExport";
@ -123,6 +128,35 @@ describe("HTMLExport", () => {
fetchMock.get(media.srcHttp!, body); 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 () => { it("should throw when created with invalid config for LastNMessages", async () => {
expect( expect(
() => () =>
@ -167,6 +201,7 @@ describe("HTMLExport", () => {
body: `Message #${i}`, body: `Message #${i}`,
}, },
})); }));
mockReactionForMessage(events[0]);
mockMessages(...events); mockMessages(...events);
const exporter = new HTMLExporter( const exporter = new HTMLExporter(
@ -587,4 +622,24 @@ describe("HTMLExport", () => {
expect(await file.text()).toContain("testing testing"); expect(await file.text()).toContain("testing testing");
expect(client.createMessagesRequest).not.toHaveBeenCalled(); 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);
});
}); });

File diff suppressed because one or more lines are too long