From 933a9c1ae3b91678a76a2bcf529dafe4da78f0b8 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 20 Aug 2024 15:22:11 +0100 Subject: [PATCH 001/154] Rename prettier config file to .cjs (#12903) For the same reason as https://github.com/matrix-org/matrix-js-sdk/pull/4353 --- .prettierrc.js => .prettierrc.cjs | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .prettierrc.js => .prettierrc.cjs (100%) diff --git a/.prettierrc.js b/.prettierrc.cjs similarity index 100% rename from .prettierrc.js rename to .prettierrc.cjs From 4064db1d022e2fe98ee7f09b1a96f5d5e445b042 Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Wed, 21 Aug 2024 01:21:54 -0500 Subject: [PATCH 002/154] [create-pull-request] automated change (#12907) Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com> --- playwright/plugins/homeserver/synapse/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright/plugins/homeserver/synapse/index.ts b/playwright/plugins/homeserver/synapse/index.ts index 86490bb0f11..4da7c8a3261 100644 --- a/playwright/plugins/homeserver/synapse/index.ts +++ b/playwright/plugins/homeserver/synapse/index.ts @@ -28,7 +28,7 @@ import { randB64Bytes } from "../../utils/rand"; // Docker tag to use for synapse docker image. // We target a specific digest as every now and then a Synapse update will break our CI. // This digest is updated by the playwright-image-updates.yaml workflow periodically. -const DOCKER_TAG = "develop@sha256:92bd527fb219e2b8bad770f25140c0117fe08fd948b991bf669c3f86bf4f2c61"; +const DOCKER_TAG = "develop@sha256:4c891449943a2e7413a47784f3319caabfa185ad2caf96959f2175df34047917"; async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise> { const templateDir = path.join(__dirname, "templates", opts.template); From 3d80eff65bb93399dc30f1088ddb75a21b423c73 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Wed, 21 Aug 2024 10:50:00 +0200 Subject: [PATCH 003/154] Add Pin/Unpin action in quick access of the message action bar (#12897) * Add Pin/Unpin action in quick access of the message action bar * Add tests for `MessageActionBar` * Add tests for `PinningUtils` * Fix `MessageContextMenu-test` * Add e2e test to pin/unpin from message action bar --- playwright/e2e/pinned-messages/index.ts | 26 +- .../pinned-messages/pinned-messages.spec.ts | 11 + .../context_menus/_MessageContextMenu.pcss | 4 +- .../context_menus/MessageContextMenu.tsx | 64 ++--- .../views/messages/MessageActionBar.tsx | 30 +++ src/utils/PinningUtils.ts | 81 +++++- .../context_menus/MessageContextMenu-test.tsx | 104 ++++---- .../views/messages/MessageActionBar-test.tsx | 36 ++- test/utils/PinningUtils-test.ts | 252 ++++++++++++++++++ 9 files changed, 503 insertions(+), 105 deletions(-) create mode 100644 test/utils/PinningUtils-test.ts diff --git a/playwright/e2e/pinned-messages/index.ts b/playwright/e2e/pinned-messages/index.ts index a67df09d86e..5e61b11e857 100644 --- a/playwright/e2e/pinned-messages/index.ts +++ b/playwright/e2e/pinned-messages/index.ts @@ -100,13 +100,35 @@ export class Helpers { } /** - * Pin the given message + * Pin the given message from the quick actions + * @param message + * @param unpin + */ + async pinMessageFromQuickActions(message: string, unpin = false) { + const timelineMessage = this.page.locator(".mx_MTextBody", { hasText: message }); + await timelineMessage.hover(); + await this.page.getByRole("button", { name: unpin ? "Unpin" : "Pin", exact: true }).click(); + } + + /** + * Pin the given messages from the quick actions + * @param messages + * @param unpin + */ + async pinMessagesFromQuickActions(messages: string[], unpin = false) { + for (const message of messages) { + await this.pinMessageFromQuickActions(message, unpin); + } + } + + /** + * Pin the given message from the contextual menu * @param message */ async pinMessage(message: string) { const timelineMessage = this.page.locator(".mx_MTextBody", { hasText: message }); await timelineMessage.click({ button: "right" }); - await this.page.getByRole("menuitem", { name: "Pin" }).click(); + await this.page.getByRole("menuitem", { name: "Pin", exact: true }).click(); } /** diff --git a/playwright/e2e/pinned-messages/pinned-messages.spec.ts b/playwright/e2e/pinned-messages/pinned-messages.spec.ts index be1c92223f6..53f657ea7fa 100644 --- a/playwright/e2e/pinned-messages/pinned-messages.spec.ts +++ b/playwright/e2e/pinned-messages/pinned-messages.spec.ts @@ -76,4 +76,15 @@ test.describe("Pinned messages", () => { await util.backPinnedMessagesList(); await util.assertPinnedCountInRoomInfo(0); }); + + test("should be able to pin and unpin from the quick actions", async ({ page, app, room1, util }) => { + await util.goTo(room1); + await util.receiveMessages(room1, ["Msg1", "Msg2", "Msg3", "Msg4"]); + await util.pinMessagesFromQuickActions(["Msg1"]); + await util.openRoomInfo(); + await util.assertPinnedCountInRoomInfo(1); + + await util.pinMessagesFromQuickActions(["Msg1"], true); + await util.assertPinnedCountInRoomInfo(0); + }); }); diff --git a/res/css/views/context_menus/_MessageContextMenu.pcss b/res/css/views/context_menus/_MessageContextMenu.pcss index be113c770f6..28529eabf98 100644 --- a/res/css/views/context_menus/_MessageContextMenu.pcss +++ b/res/css/views/context_menus/_MessageContextMenu.pcss @@ -81,11 +81,11 @@ limitations under the License. } .mx_MessageContextMenu_iconPin::before { - mask-image: url("$(res)/img/element-icons/room/pin-upright.svg"); + mask-image: url("@vector-im/compound-design-tokens/icons/pin.svg"); } .mx_MessageContextMenu_iconUnpin::before { - mask-image: url("$(res)/img/element-icons/room/pin.svg"); + mask-image: url("@vector-im/compound-design-tokens/icons/unpin.svg"); } .mx_MessageContextMenu_iconCopy::before { diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 801ab0b023e..2d5a81a89b6 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -36,9 +36,8 @@ import Modal from "../../../Modal"; import Resend from "../../../Resend"; import SettingsStore from "../../../settings/SettingsStore"; import { isUrlPermitted } from "../../../HtmlUtils"; -import { canEditContent, canPinEvent, editEvent, isContentActionable } from "../../../utils/EventUtils"; +import { canEditContent, editEvent, isContentActionable } from "../../../utils/EventUtils"; import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from "./IconizedContextMenu"; -import { ReadPinsEventId } from "../right_panel/types"; import { Action } from "../../../dispatcher/actions"; import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"; import { ButtonEvent } from "../elements/AccessibleButton"; @@ -60,6 +59,7 @@ import { getForwardableEvent } from "../../../events/forward/getForwardableEvent import { getShareableLocationEvent } from "../../../events/location/getShareableLocationEvent"; import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload"; import { CardContext } from "../right_panel/context"; +import PinningUtils from "../../../utils/PinningUtils"; interface IReplyInThreadButton { mxEvent: MatrixEvent; @@ -177,24 +177,11 @@ export default class MessageContextMenu extends React.Component this.props.mxEvent.getType() !== EventType.RoomServerAcl && this.props.mxEvent.getType() !== EventType.RoomEncryption; - let canPin = - !!room?.currentState.mayClientSendStateEvent(EventType.RoomPinnedEvents, cli) && - canPinEvent(this.props.mxEvent); - - // HACK: Intentionally say we can't pin if the user doesn't want to use the functionality - if (!SettingsStore.getValue("feature_pinning")) canPin = false; + const canPin = PinningUtils.canPinOrUnpin(cli, this.props.mxEvent); this.setState({ canRedact, canPin }); }; - private isPinned(): boolean { - const room = MatrixClientPeg.safeGet().getRoom(this.props.mxEvent.getRoomId()); - const pinnedEvent = room?.currentState.getStateEvents(EventType.RoomPinnedEvents, ""); - if (!pinnedEvent) return false; - const content = pinnedEvent.getContent(); - return content.pinned && Array.isArray(content.pinned) && content.pinned.includes(this.props.mxEvent.getId()); - } - private canEndPoll(mxEvent: MatrixEvent): boolean { return ( M_POLL_START.matches(mxEvent.getType()) && @@ -257,22 +244,8 @@ export default class MessageContextMenu extends React.Component }; private onPinClick = (): void => { - const cli = MatrixClientPeg.safeGet(); - const room = cli.getRoom(this.props.mxEvent.getRoomId()); - if (!room) return; - const eventId = this.props.mxEvent.getId(); - - const pinnedIds = room.currentState?.getStateEvents(EventType.RoomPinnedEvents, "")?.getContent().pinned || []; - - if (pinnedIds.includes(eventId)) { - pinnedIds.splice(pinnedIds.indexOf(eventId), 1); - } else { - pinnedIds.push(eventId); - cli.setRoomAccountData(room.roomId, ReadPinsEventId, { - event_ids: [...(room.getAccountData(ReadPinsEventId)?.getContent()?.event_ids || []), eventId], - }); - } - cli.sendStateEvent(room.roomId, EventType.RoomPinnedEvents, { pinned: pinnedIds }, ""); + // Pin or unpin in background + PinningUtils.pinOrUnpinEvent(MatrixClientPeg.safeGet(), this.props.mxEvent); this.closeMenu(); }; @@ -452,17 +425,6 @@ export default class MessageContextMenu extends React.Component ); } - let pinButton: JSX.Element | undefined; - if (contentActionable && this.state.canPin) { - pinButton = ( - - ); - } - // This is specifically not behind the developerMode flag to give people insight into the Matrix const viewSourceButton = ( ); } + let pinButton: JSX.Element | undefined; + if (rightClick && this.state.canPin) { + const isPinned = PinningUtils.isPinned(MatrixClientPeg.safeGet(), this.props.mxEvent); + pinButton = ( + + ); + } + let viewInRoomButton: JSX.Element | undefined; if (isThreadRootEvent) { viewInRoomButton = ( @@ -671,13 +645,14 @@ export default class MessageContextMenu extends React.Component } let quickItemsList: JSX.Element | undefined; - if (editButton || replyButton || reactButton) { + if (editButton || replyButton || reactButton || pinButton) { quickItemsList = ( {reactButton} {replyButton} {replyInThreadButton} {editButton} + {pinButton} ); } @@ -688,7 +663,6 @@ export default class MessageContextMenu extends React.Component {openInMapSiteButton} {endPollButton} {forwardButton} - {pinButton} {permalinkButton} {reportEventButton} {externalURLButton} diff --git a/src/components/views/messages/MessageActionBar.tsx b/src/components/views/messages/MessageActionBar.tsx index 25547c78363..eebcb96b1e2 100644 --- a/src/components/views/messages/MessageActionBar.tsx +++ b/src/components/views/messages/MessageActionBar.tsx @@ -26,6 +26,8 @@ import { M_BEACON_INFO, } from "matrix-js-sdk/src/matrix"; import classNames from "classnames"; +import { Icon as PinIcon } from "@vector-im/compound-design-tokens/icons/pin.svg"; +import { Icon as UnpinIcon } from "@vector-im/compound-design-tokens/icons/unpin.svg"; import { Icon as ContextMenuIcon } from "../../../../res/img/element-icons/context-menu.svg"; import { Icon as EditIcon } from "../../../../res/img/element-icons/room/message-bar/edit.svg"; @@ -61,6 +63,7 @@ import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayloa import { GetRelationsForEvent, IEventTileType } from "../rooms/EventTile"; import { VoiceBroadcastInfoEventType } from "../../../voice-broadcast/types"; import { ButtonEvent } from "../elements/AccessibleButton"; +import PinningUtils from "../../../utils/PinningUtils"; interface IOptionsButtonProps { mxEvent: MatrixEvent; @@ -384,6 +387,17 @@ export default class MessageActionBar extends React.PureComponent => { + // Don't open the regular browser or our context menu on right-click + event.preventDefault(); + event.stopPropagation(); + + await PinningUtils.pinOrUnpinEvent(MatrixClientPeg.safeGet(), this.props.mxEvent); + }; + public render(): React.ReactNode { const toolbarOpts: JSX.Element[] = []; if (canEditContent(MatrixClientPeg.safeGet(), this.props.mxEvent)) { @@ -401,6 +415,22 @@ export default class MessageActionBar extends React.PureComponent + {isPinned ? : } + , + ); + } + const cancelSendingButton = ( { + const room = matrixClient.getRoom(mxEvent.getRoomId()); + if (!room) return; + + const eventId = mxEvent.getId(); + if (!eventId) return; + + // Get the current pinned events of the room + const pinnedIds: Array = + room + .getLiveTimeline() + .getState(EventTimeline.FORWARDS) + ?.getStateEvents(EventType.RoomPinnedEvents, "") + ?.getContent().pinned || []; + + // If the event is already pinned, unpin it + if (pinnedIds.includes(eventId)) { + pinnedIds.splice(pinnedIds.indexOf(eventId), 1); + } else { + // Otherwise, pin it + pinnedIds.push(eventId); + await matrixClient.setRoomAccountData(room.roomId, ReadPinsEventId, { + event_ids: [...(room.getAccountData(ReadPinsEventId)?.getContent()?.event_ids || []), eventId], + }); + } + await matrixClient.sendStateEvent(room.roomId, EventType.RoomPinnedEvents, { pinned: pinnedIds }, ""); + } } diff --git a/test/components/views/context_menus/MessageContextMenu-test.tsx b/test/components/views/context_menus/MessageContextMenu-test.tsx index 2be71e39cbe..7be05452c82 100644 --- a/test/components/views/context_menus/MessageContextMenu-test.tsx +++ b/test/components/views/context_menus/MessageContextMenu-test.tsx @@ -15,7 +15,7 @@ limitations under the License. */ import React from "react"; -import { fireEvent, render, RenderResult } from "@testing-library/react"; +import { fireEvent, render, RenderResult, screen, waitFor } from "@testing-library/react"; import { EventStatus, MatrixEvent, @@ -28,9 +28,11 @@ import { FeatureSupport, Thread, M_POLL_KIND_DISCLOSED, + EventTimeline, } from "matrix-js-sdk/src/matrix"; import { PollStartEvent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent"; import { mocked } from "jest-mock"; +import userEvent from "@testing-library/user-event"; import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; import RoomContext, { TimelineRenderingType } from "../../../../src/contexts/RoomContext"; @@ -83,8 +85,16 @@ describe("MessageContextMenu", () => { }); describe("message pinning", () => { + let room: Room; + beforeEach(() => { + room = makeDefaultRoom(); + jest.spyOn(SettingsStore, "getValue").mockReturnValue(true); + jest.spyOn( + room.getLiveTimeline().getState(EventTimeline.FORWARDS)!, + "mayClientSendStateEvent", + ).mockReturnValue(true); }); afterAll(() => { @@ -95,25 +105,23 @@ describe("MessageContextMenu", () => { const eventContent = createMessageEventContent("hello"); const event = new MatrixEvent({ type: EventType.RoomMessage, content: eventContent }); - const room = makeDefaultRoom(); // mock permission to disallow adding pinned messages to room - jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(false); + jest.spyOn( + room.getLiveTimeline().getState(EventTimeline.FORWARDS)!, + "mayClientSendStateEvent", + ).mockReturnValue(false); - createMenu(event, {}, {}, undefined, room); + createMenu(event, { rightClick: true }, {}, undefined, room); - expect(document.querySelector('li[aria-label="Pin"]')).toBeFalsy(); + expect(screen.queryByRole("menuitem", { name: "Pin" })).toBeFalsy(); }); it("does not show pin option for beacon_info event", () => { const deadBeaconEvent = makeBeaconInfoEvent("@alice:server.org", roomId, { isLive: false }); - const room = makeDefaultRoom(); - // mock permission to allow adding pinned messages to room - jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true); - - createMenu(deadBeaconEvent, {}, {}, undefined, room); + createMenu(deadBeaconEvent, { rightClick: true }, {}, undefined, room); - expect(document.querySelector('li[aria-label="Pin"]')).toBeFalsy(); + expect(screen.queryByRole("menuitem", { name: "Pin" })).toBeFalsy(); }); it("does not show pin option when pinning feature is disabled", () => { @@ -124,15 +132,12 @@ describe("MessageContextMenu", () => { room_id: roomId, }); - const room = makeDefaultRoom(); - // mock permission to allow adding pinned messages to room - jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true); // disable pinning feature jest.spyOn(SettingsStore, "getValue").mockReturnValue(false); - createMenu(pinnableEvent, {}, {}, undefined, room); + createMenu(pinnableEvent, { rightClick: true }, {}, undefined, room); - expect(document.querySelector('li[aria-label="Pin"]')).toBeFalsy(); + expect(screen.queryByRole("menuitem", { name: "Pin" })).toBeFalsy(); }); it("shows pin option when pinning feature is enabled", () => { @@ -143,16 +148,12 @@ describe("MessageContextMenu", () => { room_id: roomId, }); - const room = makeDefaultRoom(); - // mock permission to allow adding pinned messages to room - jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true); + createMenu(pinnableEvent, { rightClick: true }, {}, undefined, room); - createMenu(pinnableEvent, {}, {}, undefined, room); - - expect(document.querySelector('li[aria-label="Pin"]')).toBeTruthy(); + expect(screen.getByRole("menuitem", { name: "Pin" })).toBeTruthy(); }); - it("pins event on pin option click", () => { + it("pins event on pin option click", async () => { const onFinished = jest.fn(); const eventContent = createMessageEventContent("hello"); const pinnableEvent = new MatrixEvent({ @@ -162,43 +163,48 @@ describe("MessageContextMenu", () => { }); pinnableEvent.event.event_id = "!3"; const client = MatrixClientPeg.safeGet(); - const room = makeDefaultRoom(); - // mock permission to allow adding pinned messages to room - jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true); + jest.spyOn(room.getLiveTimeline().getState(EventTimeline.FORWARDS)!, "getStateEvents").mockReturnValue({ + // @ts-ignore + getContent: () => ({ pinned: ["!1", "!2"] }), + }); // mock read pins account data const pinsAccountData = new MatrixEvent({ content: { event_ids: ["!1", "!2"] } }); jest.spyOn(room, "getAccountData").mockReturnValue(pinsAccountData); - createMenu(pinnableEvent, { onFinished }, {}, undefined, room); + createMenu(pinnableEvent, { onFinished, rightClick: true }, {}, undefined, room); - fireEvent.click(document.querySelector('li[aria-label="Pin"]')!); + await userEvent.click(screen.getByRole("menuitem", { name: "Pin" })); // added to account data - expect(client.setRoomAccountData).toHaveBeenCalledWith(roomId, ReadPinsEventId, { - event_ids: [ - // from account data - "!1", - "!2", - pinnableEvent.getId(), - ], - }); + await waitFor(() => + expect(client.setRoomAccountData).toHaveBeenCalledWith(roomId, ReadPinsEventId, { + event_ids: [ + // from account data + "!1", + "!2", + pinnableEvent.getId(), + ], + }), + ); // add to room's pins - expect(client.sendStateEvent).toHaveBeenCalledWith( - roomId, - EventType.RoomPinnedEvents, - { - pinned: [pinnableEvent.getId()], - }, - "", + await waitFor(() => + expect(client.sendStateEvent).toHaveBeenCalledWith( + roomId, + EventType.RoomPinnedEvents, + { + pinned: ["!1", "!2", pinnableEvent.getId()], + }, + "", + ), ); expect(onFinished).toHaveBeenCalled(); }); - it("unpins event on pin option click when event is pinned", () => { + it("unpins event on pin option click when event is pinned", async () => { const eventContent = createMessageEventContent("hello"); const pinnableEvent = new MatrixEvent({ type: EventType.RoomMessage, @@ -207,7 +213,6 @@ describe("MessageContextMenu", () => { }); pinnableEvent.event.event_id = "!3"; const client = MatrixClientPeg.safeGet(); - const room = makeDefaultRoom(); // make the event already pinned in the room const pinEvent = new MatrixEvent({ @@ -216,18 +221,15 @@ describe("MessageContextMenu", () => { state_key: "", content: { pinned: [pinnableEvent.getId(), "!another-event"] }, }); - room.currentState.setStateEvents([pinEvent]); - - // mock permission to allow adding pinned messages to room - jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true); + room.getLiveTimeline().getState(EventTimeline.FORWARDS)!.setStateEvents([pinEvent]); // mock read pins account data const pinsAccountData = new MatrixEvent({ content: { event_ids: ["!1", "!2"] } }); jest.spyOn(room, "getAccountData").mockReturnValue(pinsAccountData); - createMenu(pinnableEvent, {}, {}, undefined, room); + createMenu(pinnableEvent, { rightClick: true }, {}, undefined, room); - fireEvent.click(document.querySelector('li[aria-label="Unpin"]')!); + await userEvent.click(screen.getByRole("menuitem", { name: "Unpin" })); expect(client.setRoomAccountData).not.toHaveBeenCalled(); diff --git a/test/components/views/messages/MessageActionBar-test.tsx b/test/components/views/messages/MessageActionBar-test.tsx index 33d1f0d8eed..ad184314a25 100644 --- a/test/components/views/messages/MessageActionBar-test.tsx +++ b/test/components/views/messages/MessageActionBar-test.tsx @@ -25,6 +25,7 @@ import { Room, FeatureSupport, Thread, + EventTimeline, } from "matrix-js-sdk/src/matrix"; import MessageActionBar from "../../../../src/components/views/messages/MessageActionBar"; @@ -51,6 +52,8 @@ describe("", () => { ...mockClientMethodsUser(userId), ...mockClientMethodsEvents(), getRoom: jest.fn(), + setRoomAccountData: jest.fn(), + sendStateEvent: jest.fn(), }); const room = new Room(roomId, client, userId); @@ -442,10 +445,10 @@ describe("", () => { }); }); - it.each([["React"], ["Reply"], ["Reply in thread"], ["Edit"]])( + it.each([["React"], ["Reply"], ["Reply in thread"], ["Edit"], ["Pin"]])( "does not show context menu when right-clicking", (buttonLabel: string) => { - // For favourite button + // For favourite and pin buttons jest.spyOn(SettingsStore, "getValue").mockReturnValue(true); const event = new MouseEvent("contextmenu", { @@ -468,4 +471,33 @@ describe("", () => { fireEvent.contextMenu(queryByLabelText("Options")!); expect(queryByTestId("mx_MessageContextMenu")).toBeTruthy(); }); + + describe("pin button", () => { + beforeEach(() => { + // enable pin button + jest.spyOn(SettingsStore, "getValue").mockReturnValue(true); + }); + + afterEach(() => { + jest.spyOn( + room.getLiveTimeline().getState(EventTimeline.FORWARDS)!, + "mayClientSendStateEvent", + ).mockRestore(); + }); + + it("should not render pin button when user can't send state event", () => { + jest.spyOn( + room.getLiveTimeline().getState(EventTimeline.FORWARDS)!, + "mayClientSendStateEvent", + ).mockReturnValue(false); + + const { queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent }); + expect(queryByLabelText("Pin")).toBeFalsy(); + }); + + it("should render pin button", () => { + const { queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent }); + expect(queryByLabelText("Pin")).toBeTruthy(); + }); + }); }); diff --git a/test/utils/PinningUtils-test.ts b/test/utils/PinningUtils-test.ts new file mode 100644 index 00000000000..07b26adb8a9 --- /dev/null +++ b/test/utils/PinningUtils-test.ts @@ -0,0 +1,252 @@ +/* + * Copyright 2024 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 { EventTimeline, EventType, IEvent, MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; +import { mocked } from "jest-mock"; + +import { createTestClient } from "../test-utils"; +import PinningUtils from "../../src/utils/PinningUtils"; +import SettingsStore from "../../src/settings/SettingsStore"; +import { canPinEvent, isContentActionable } from "../../src/utils/EventUtils"; +import { ReadPinsEventId } from "../../src/components/views/right_panel/types"; + +jest.mock("../../src/utils/EventUtils", () => { + return { + isContentActionable: jest.fn(), + canPinEvent: jest.fn(), + }; +}); + +describe("PinningUtils", () => { + const roomId = "!room:example.org"; + const userId = "@alice:example.org"; + + const mockedIsContentActionable = mocked(isContentActionable); + const mockedCanPinEvent = mocked(canPinEvent); + + let matrixClient: MatrixClient; + let room: Room; + + /** + * Create a pinned event with the given content. + * @param content + */ + function makePinEvent(content?: Partial) { + return new MatrixEvent({ + type: EventType.RoomMessage, + sender: userId, + content: { + body: "First pinned message", + msgtype: "m.text", + }, + room_id: roomId, + origin_server_ts: 0, + event_id: "$eventId", + ...content, + }); + } + + beforeEach(() => { + // Enable feature pinning + jest.spyOn(SettingsStore, "getValue").mockReturnValue(true); + mockedIsContentActionable.mockImplementation(() => true); + mockedCanPinEvent.mockImplementation(() => true); + + matrixClient = createTestClient(); + room = new Room(roomId, matrixClient, userId); + matrixClient.getRoom = jest.fn().mockReturnValue(room); + + jest.spyOn( + matrixClient.getRoom(roomId)!.getLiveTimeline().getState(EventTimeline.FORWARDS)!, + "mayClientSendStateEvent", + ).mockReturnValue(true); + }); + + describe("isPinnable", () => { + test.each(PinningUtils.PINNABLE_EVENT_TYPES)("should return true for pinnable event types", (eventType) => { + const event = makePinEvent({ type: eventType }); + expect(PinningUtils.isPinnable(event)).toBe(true); + }); + + test("should return false for a non pinnable event type", () => { + const event = makePinEvent({ type: EventType.RoomCreate }); + expect(PinningUtils.isPinnable(event)).toBe(false); + }); + + test("should return false for a redacted event", () => { + const event = makePinEvent({ unsigned: { redacted_because: "because" as unknown as IEvent } }); + expect(PinningUtils.isPinnable(event)).toBe(false); + }); + }); + + describe("isPinned", () => { + test("should return false if no room", () => { + matrixClient.getRoom = jest.fn().mockReturnValue(undefined); + const event = makePinEvent(); + + expect(PinningUtils.isPinned(matrixClient, event)).toBe(false); + }); + + test("should return false if no pinned event", () => { + jest.spyOn( + matrixClient.getRoom(roomId)!.getLiveTimeline().getState(EventTimeline.FORWARDS)!, + "getStateEvents", + ).mockReturnValue(null); + + const event = makePinEvent(); + expect(PinningUtils.isPinned(matrixClient, event)).toBe(false); + }); + + test("should return false if pinned events do not contain the event id", () => { + jest.spyOn( + matrixClient.getRoom(roomId)!.getLiveTimeline().getState(EventTimeline.FORWARDS)!, + "getStateEvents", + ).mockReturnValue({ + // @ts-ignore + getContent: () => ({ pinned: ["$otherEventId"] }), + }); + + const event = makePinEvent(); + expect(PinningUtils.isPinned(matrixClient, event)).toBe(false); + }); + + test("should return true if pinned events contains the event id", () => { + const event = makePinEvent(); + jest.spyOn( + matrixClient.getRoom(roomId)!.getLiveTimeline().getState(EventTimeline.FORWARDS)!, + "getStateEvents", + ).mockReturnValue({ + // @ts-ignore + getContent: () => ({ pinned: [event.getId()] }), + }); + + expect(PinningUtils.isPinned(matrixClient, event)).toBe(true); + }); + }); + + describe("canPinOrUnpin", () => { + test("should return false if pinning is disabled", () => { + // Disable feature pinning + jest.spyOn(SettingsStore, "getValue").mockReturnValue(false); + const event = makePinEvent(); + + expect(PinningUtils.canPinOrUnpin(matrixClient, event)).toBe(false); + }); + + test("should return false if event is not actionable", () => { + mockedIsContentActionable.mockImplementation(() => false); + const event = makePinEvent(); + + expect(PinningUtils.canPinOrUnpin(matrixClient, event)).toBe(false); + }); + + test("should return false if no room", () => { + matrixClient.getRoom = jest.fn().mockReturnValue(undefined); + const event = makePinEvent(); + + expect(PinningUtils.canPinOrUnpin(matrixClient, event)).toBe(false); + }); + + test("should return false if client cannot send state event", () => { + jest.spyOn( + matrixClient.getRoom(roomId)!.getLiveTimeline().getState(EventTimeline.FORWARDS)!, + "mayClientSendStateEvent", + ).mockReturnValue(false); + const event = makePinEvent(); + + expect(PinningUtils.canPinOrUnpin(matrixClient, event)).toBe(false); + }); + + test("should return false if event is not pinnable", () => { + mockedCanPinEvent.mockReturnValue(false); + const event = makePinEvent(); + + expect(PinningUtils.canPinOrUnpin(matrixClient, event)).toBe(false); + }); + + test("should return true if all conditions are met", () => { + const event = makePinEvent(); + + expect(PinningUtils.canPinOrUnpin(matrixClient, event)).toBe(true); + }); + }); + + describe("pinOrUnpinEvent", () => { + test("should do nothing if no room", async () => { + matrixClient.getRoom = jest.fn().mockReturnValue(undefined); + const event = makePinEvent(); + + await PinningUtils.pinOrUnpinEvent(matrixClient, event); + expect(matrixClient.sendStateEvent).not.toHaveBeenCalled(); + }); + + test("should do nothing if no event id", async () => { + const event = makePinEvent({ event_id: undefined }); + + await PinningUtils.pinOrUnpinEvent(matrixClient, event); + expect(matrixClient.sendStateEvent).not.toHaveBeenCalled(); + }); + + test("should pin the event if not pinned", async () => { + jest.spyOn( + matrixClient.getRoom(roomId)!.getLiveTimeline().getState(EventTimeline.FORWARDS)!, + "getStateEvents", + ).mockReturnValue({ + // @ts-ignore + getContent: () => ({ pinned: ["$otherEventId"] }), + }); + + jest.spyOn(room, "getAccountData").mockReturnValue({ + getContent: jest.fn().mockReturnValue({ + event_ids: ["$otherEventId"], + }), + } as unknown as MatrixEvent); + + const event = makePinEvent(); + await PinningUtils.pinOrUnpinEvent(matrixClient, event); + + expect(matrixClient.setRoomAccountData).toHaveBeenCalledWith(roomId, ReadPinsEventId, { + event_ids: ["$otherEventId", event.getId()], + }); + expect(matrixClient.sendStateEvent).toHaveBeenCalledWith( + roomId, + EventType.RoomPinnedEvents, + { pinned: ["$otherEventId", event.getId()] }, + "", + ); + }); + + test("should unpin the event if already pinned", async () => { + const event = makePinEvent(); + + jest.spyOn( + matrixClient.getRoom(roomId)!.getLiveTimeline().getState(EventTimeline.FORWARDS)!, + "getStateEvents", + ).mockReturnValue({ + // @ts-ignore + getContent: () => ({ pinned: [event.getId(), "$otherEventId"] }), + }); + + await PinningUtils.pinOrUnpinEvent(matrixClient, event); + expect(matrixClient.sendStateEvent).toHaveBeenCalledWith( + roomId, + EventType.RoomPinnedEvents, + { pinned: ["$otherEventId"] }, + "", + ); + }); + }); +}); From a7e907e0e67797051a032578c6ad14bf39f7c2f2 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Wed, 21 Aug 2024 11:02:35 +0200 Subject: [PATCH 004/154] Add thread information in pinned message list (#12902) --- res/css/views/rooms/_PinnedEventTile.pcss | 23 +++++ .../views/rooms/PinnedEventTile.tsx | 35 ++++++++ src/i18n/strings/en_EN.json | 1 + .../views/rooms/PinnedEventTile-test.tsx | 30 +++++++ .../PinnedEventTile-test.tsx.snap | 86 ++++++++++++++++++- 5 files changed, 171 insertions(+), 4 deletions(-) diff --git a/res/css/views/rooms/_PinnedEventTile.pcss b/res/css/views/rooms/_PinnedEventTile.pcss index b42de756499..b37e3724fcd 100644 --- a/res/css/views/rooms/_PinnedEventTile.pcss +++ b/res/css/views/rooms/_PinnedEventTile.pcss @@ -37,5 +37,28 @@ limitations under the License. white-space: nowrap; } } + + .mx_PinnedEventTile_thread { + display: flex; + gap: var(--cpd-space-2x); + font: var(--cpd-font-body-sm-regular); + + svg { + width: 20px; + fill: var(--cpd-color-icon-tertiary); + } + + span { + display: flex; + color: var(--cpd-color-text-secondary); + } + + button { + background: transparent; + border: none; + cursor: pointer; + text-decoration: underline; + } + } } } diff --git a/src/components/views/rooms/PinnedEventTile.tsx b/src/components/views/rooms/PinnedEventTile.tsx index 5252e5124d7..5fb9c07f452 100644 --- a/src/components/views/rooms/PinnedEventTile.tsx +++ b/src/components/views/rooms/PinnedEventTile.tsx @@ -23,6 +23,7 @@ import { Icon as UnpinIcon } from "@vector-im/compound-design-tokens/icons/unpin import { Icon as ForwardIcon } from "@vector-im/compound-design-tokens/icons/forward.svg"; import { Icon as TriggerIcon } from "@vector-im/compound-design-tokens/icons/overflow-horizontal.svg"; import { Icon as DeleteIcon } from "@vector-im/compound-design-tokens/icons/delete.svg"; +import { Icon as ThreadIcon } from "@vector-im/compound-design-tokens/icons/threads.svg"; import classNames from "classnames"; import dis from "../../../dispatcher/dispatcher"; @@ -39,6 +40,7 @@ import { isContentActionable } from "../../../utils/EventUtils"; import { getForwardableEvent } from "../../../events"; import { OpenForwardDialogPayload } from "../../../dispatcher/payloads/OpenForwardDialogPayload"; import { createRedactEventDialog } from "../dialogs/ConfirmRedactDialog"; +import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload"; const AVATAR_SIZE = "32px"; @@ -69,6 +71,9 @@ export function PinnedEventTile({ event, room, permalinkCreator }: PinnedEventTi throw new Error("Pinned event unexpectedly has no sender"); } + const isInThread = Boolean(event.threadRootId); + const displayThreadInfo = !event.isThreadRoot && isInThread; + return (
@@ -97,6 +102,36 @@ export function PinnedEventTile({ event, room, permalinkCreator }: PinnedEventTi permalinkCreator={permalinkCreator} replacingEventId={event.replacingEventId()} /> + {displayThreadInfo && ( +
+ + {_t( + "right_panel|pinned_messages|reply_thread", + {}, + { + link: (sub) => ( + + ), + }, + )} +
+ )}
); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 1b7b1f2ed99..889fc157e9e 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1850,6 +1850,7 @@ "other": "You can only pin up to %(count)s widgets" }, "menu": "Open menu", + "reply_thread": "Reply to a thread message", "title": "Pinned messages", "unpin_all": { "button": "Unpin all messages", diff --git a/test/components/views/rooms/PinnedEventTile-test.tsx b/test/components/views/rooms/PinnedEventTile-test.tsx index ed27a4e495e..124138d834a 100644 --- a/test/components/views/rooms/PinnedEventTile-test.tsx +++ b/test/components/views/rooms/PinnedEventTile-test.tsx @@ -97,6 +97,36 @@ describe("", () => { expect(container).toMatchSnapshot(); }); + it("should render pinned event with thread info", async () => { + const event = makePinEvent({ + content: { + "body": "First pinned message", + "msgtype": "m.text", + "m.relates_to": { + "event_id": "$threadRootEventId", + "is_falling_back": true, + "m.in_reply_to": { + event_id: "$$threadRootEventId", + }, + "rel_type": "m.thread", + }, + }, + }); + const threadRootEvent = makePinEvent({ event_id: "$threadRootEventId" }); + jest.spyOn(room, "findEventById").mockReturnValue(threadRootEvent); + + const { container } = renderComponent(event); + expect(container).toMatchSnapshot(); + + await userEvent.click(screen.getByRole("button", { name: "thread message" })); + // Check that the thread is opened + expect(dis.dispatch).toHaveBeenCalledWith({ + action: Action.ShowThread, + rootEvent: threadRootEvent, + push: true, + }); + }); + it("should render the menu without unpin and delete", async () => { jest.spyOn(room.getLiveTimeline().getState(EventTimeline.FORWARDS)!, "mayClientSendStateEvent").mockReturnValue( false, diff --git a/test/components/views/rooms/__snapshots__/PinnedEventTile-test.tsx.snap b/test/components/views/rooms/__snapshots__/PinnedEventTile-test.tsx.snap index b44b6a41a0a..bf8cafe2dc6 100644 --- a/test/components/views/rooms/__snapshots__/PinnedEventTile-test.tsx.snap +++ b/test/components/views/rooms/__snapshots__/PinnedEventTile-test.tsx.snap @@ -65,10 +65,88 @@ exports[` should render pinned event 1`] = ` `; +exports[` should render pinned event with thread info 1`] = ` +
+
+
+ + a + +
+
+
+ + @alice:server.org + + +
+
+
+ First pinned message +
+
+
+
+ + Reply to a + + +
+
+
+
+`; + exports[` should render the menu with all the options 1`] = `
should render the menu with all the options 1`] = ` data-side="right" data-state="open" dir="ltr" - id="radix-7" + id="radix-9" role="menu" style="outline: none; --radix-dropdown-menu-content-transform-origin: var(--radix-popper-transform-origin); --radix-dropdown-menu-content-available-width: var(--radix-popper-available-width); --radix-dropdown-menu-content-available-height: var(--radix-popper-available-height); --radix-dropdown-menu-trigger-width: var(--radix-popper-anchor-width); --radix-dropdown-menu-trigger-height: var(--radix-popper-anchor-height); pointer-events: auto;" tabindex="-1" @@ -226,7 +304,7 @@ exports[` should render the menu with all the options 1`] = ` exports[` should render the menu without unpin and delete 1`] = `
should render the menu without unpin and delete 1`] data-side="right" data-state="open" dir="ltr" - id="radix-3" + id="radix-5" role="menu" style="outline: none; --radix-dropdown-menu-content-transform-origin: var(--radix-popper-transform-origin); --radix-dropdown-menu-content-available-width: var(--radix-popper-available-width); --radix-dropdown-menu-content-available-height: var(--radix-popper-available-height); --radix-dropdown-menu-trigger-width: var(--radix-popper-anchor-width); --radix-dropdown-menu-trigger-height: var(--radix-popper-anchor-height); pointer-events: auto;" tabindex="-1" From 2768b9cbb21c7be70b8943346b7809170b8ae60f Mon Sep 17 00:00:00 2001 From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Date: Wed, 21 Aug 2024 10:09:49 +0100 Subject: [PATCH 005/154] Set entrypoints to use ./lib rather than ./src (#12906) Currently, we replace the entrypoints in package.json during the release cycle. I think, historically, this was done to make element-web development easier, but that doesn't actually use these entrypoints (instead it imports from `src`). Accordingly, I think the switcheroo is unnecessary; furthermore it causes a whole bunch of confusion by making the development environment different from the release environment, and it complicates our CI and release process. In short, the switcheroo has to die. --- package.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 468713b1ce2..a37866dbbe5 100644 --- a/package.json +++ b/package.json @@ -26,10 +26,8 @@ "engines": { "node": ">=20.0.0" }, - "main": "./src/index.ts", - "matrix_src_main": "./src/index.ts", - "matrix_lib_main": "./lib/index.ts", - "matrix_lib_typings": "./lib/index.d.ts", + "main": "./lib/index.ts", + "typings": "./lib/index.d.ts", "matrix_i18n_extra_translation_funcs": [ "UserFriendlyError" ], From 72d5659e29871f61828dee7d8d3d0b567c15e3f1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 21 Aug 2024 16:21:11 +0100 Subject: [PATCH 006/154] Update dependency @types/node to v18.19.44 (#12911) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 16c8cb13ec2..18cd2db79b7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2724,9 +2724,9 @@ undici-types "~5.26.4" "@types/node@18": - version "18.19.42" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.42.tgz#b54ed4752c85427906aab40917b0f7f3d724bf72" - integrity sha512-d2ZFc/3lnK2YCYhos8iaNIYu9Vfhr92nHiyJHRltXWjXUBjEE+A4I58Tdbnw4VhggSW+2j5y5gTrLs4biNnubg== + version "18.19.45" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.45.tgz#a9ebfe4c316a356be7ca11f753ecb2feda6d6bdf" + integrity sha512-VZxPKNNhjKmaC1SUYowuXSRSMGyQGmQjvvA1xE4QZ0xce2kLtEhPDS+kqpCPBZYgqblCLQ2DAjSzmgCM5auvhA== dependencies: undici-types "~5.26.4" From fdc5acd5a4a2a112cc0eabb73eb28dd5540bfb69 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 21 Aug 2024 16:29:01 +0100 Subject: [PATCH 007/154] Update dependency @types/react-transition-group to v4.4.11 (#12912) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 18cd2db79b7..ccc28991ddb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2792,9 +2792,9 @@ redux "^4.0.0" "@types/react-transition-group@^4.4.0": - version "4.4.10" - resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.10.tgz#6ee71127bdab1f18f11ad8fb3322c6da27c327ac" - integrity sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q== + version "4.4.11" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.11.tgz#d963253a611d757de01ebb241143b1017d5d63d5" + integrity sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA== dependencies: "@types/react" "*" From 70665d3ce38ffb08a89bd23295628ca362847535 Mon Sep 17 00:00:00 2001 From: David Langley Date: Thu, 22 Aug 2024 13:54:01 +0100 Subject: [PATCH 008/154] RTE drafts (#12674) * Add drafts to the RTE and tests * test drafts in threads * lint * Add unit test. * Fix test failure * Remove unused import * Clean up wysiwyg drafts and add test. * Fix typo * Add timeout to allow for wasm loading. --------- Co-authored-by: Florian Duros --- playwright/e2e/composer/RTE.spec.ts | 105 ++++++++++++++++++ src/DraftCleaner.ts | 14 ++- .../views/rooms/MessageComposer.tsx | 89 ++++++++++++++- .../components/structures/MatrixChat-test.tsx | 12 ++ .../views/rooms/MessageComposer-test.tsx | 84 +++++++++----- 5 files changed, 266 insertions(+), 38 deletions(-) diff --git a/playwright/e2e/composer/RTE.spec.ts b/playwright/e2e/composer/RTE.spec.ts index 53599d5320b..47243e1c829 100644 --- a/playwright/e2e/composer/RTE.spec.ts +++ b/playwright/e2e/composer/RTE.spec.ts @@ -249,5 +249,110 @@ test.describe("Composer", () => { ); }); }); + + test.describe("Drafts", () => { + test("drafts with rich and plain text", async ({ page, app }) => { + // Set up a second room to swtich to, to test drafts + const firstRoomname = "Composing Room"; + const secondRoomname = "Second Composing Room"; + await app.client.createRoom({ name: secondRoomname }); + + // Composer is visible + const composer = page.locator("div[contenteditable=true]"); + await expect(composer).toBeVisible(); + + // Type some formatted text + await composer.pressSequentially("my "); + await composer.press(`${CtrlOrMeta}+KeyB`); + await composer.pressSequentially("bold"); + + // Change to plain text mode + await page.getByRole("button", { name: "Hide formatting" }).click(); + + // Change to another room and back again + await app.viewRoomByName(secondRoomname); + await app.viewRoomByName(firstRoomname); + + // assert the markdown + await expect(page.locator("div[contenteditable=true]", { hasText: "my __bold__" })).toBeVisible(); + + // Change to plain text mode and assert the markdown + await page.getByRole("button", { name: "Show formatting" }).click(); + + // Change to another room and back again + await app.viewRoomByName(secondRoomname); + await app.viewRoomByName(firstRoomname); + + // Send the message and assert the message + await page.getByRole("button", { name: "Send message" }).click(); + await expect(page.locator(".mx_EventTile_last .mx_EventTile_body").getByText("my bold")).toBeVisible(); + }); + + test("draft with replies", async ({ page, app }) => { + // Set up a second room to swtich to, to test drafts + const firstRoomname = "Composing Room"; + const secondRoomname = "Second Composing Room"; + await app.client.createRoom({ name: secondRoomname }); + + // Composer is visible + const composer = page.locator("div[contenteditable=true]"); + await expect(composer).toBeVisible(); + + // Send a message + await composer.pressSequentially("my first message"); + await page.getByRole("button", { name: "Send message" }).click(); + + // Click reply + const tile = page.locator(".mx_EventTile_last"); + await tile.hover(); + await tile.getByRole("button", { name: "Reply", exact: true }).click(); + + // Type reply text + await composer.pressSequentially("my reply"); + + // Change to another room and back again + await app.viewRoomByName(secondRoomname); + await app.viewRoomByName(firstRoomname); + + // Assert reply mode and reply text + await expect(page.getByText("Replying")).toBeVisible(); + await expect(page.locator("div[contenteditable=true]", { hasText: "my reply" })).toBeVisible(); + }); + + test("draft in threads", async ({ page, app }) => { + // Set up a second room to swtich to, to test drafts + const firstRoomname = "Composing Room"; + const secondRoomname = "Second Composing Room"; + await app.client.createRoom({ name: secondRoomname }); + + // Composer is visible + const composer = page.locator("div[contenteditable=true]"); + await expect(composer).toBeVisible(); + + // Send a message + await composer.pressSequentially("my first message"); + await page.getByRole("button", { name: "Send message" }).click(); + + // Click reply + const tile = page.locator(".mx_EventTile_last"); + await tile.hover(); + await tile.getByRole("button", { name: "Reply in thread" }).click(); + + const thread = page.locator(".mx_ThreadView"); + const threadComposer = thread.locator("div[contenteditable=true]"); + + // Type threaded text + await threadComposer.pressSequentially("my threaded message"); + + // Change to another room and back again + await app.viewRoomByName(secondRoomname); + await app.viewRoomByName(firstRoomname); + + // Assert threaded draft + await expect( + thread.locator("div[contenteditable=true]", { hasText: "my threaded message" }), + ).toBeVisible(); + }); + }); }); }); diff --git a/src/DraftCleaner.ts b/src/DraftCleaner.ts index 5e6c1cbae7f..cede027f223 100644 --- a/src/DraftCleaner.ts +++ b/src/DraftCleaner.ts @@ -18,6 +18,7 @@ import { logger } from "matrix-js-sdk/src/logger"; import { MatrixClientPeg } from "./MatrixClientPeg"; import { EDITOR_STATE_STORAGE_PREFIX } from "./components/views/rooms/SendMessageComposer"; +import { WYSIWYG_EDITOR_STATE_STORAGE_PREFIX } from "./components/views/rooms/MessageComposer"; // The key used to persist the the timestamp we last cleaned up drafts export const DRAFT_LAST_CLEANUP_KEY = "mx_draft_cleanup"; @@ -61,14 +62,21 @@ function shouldCleanupDrafts(): boolean { } /** - * Clear all drafts for the CIDER editor if the room does not exist in the known rooms. + * Clear all drafts for the CIDER and WYSIWYG editors if the room does not exist in the known rooms. */ function cleaupDrafts(): void { for (let i = 0; i < localStorage.length; i++) { const keyName = localStorage.key(i); - if (!keyName?.startsWith(EDITOR_STATE_STORAGE_PREFIX)) continue; + if (!keyName) continue; + let roomId: string | undefined = undefined; + if (keyName.startsWith(EDITOR_STATE_STORAGE_PREFIX)) { + roomId = keyName.slice(EDITOR_STATE_STORAGE_PREFIX.length).split("_$")[0]; + } + if (keyName.startsWith(WYSIWYG_EDITOR_STATE_STORAGE_PREFIX)) { + roomId = keyName.slice(WYSIWYG_EDITOR_STATE_STORAGE_PREFIX.length).split("_$")[0]; + } + if (!roomId) continue; // Remove the prefix and the optional event id suffix to leave the room id - const roomId = keyName.slice(EDITOR_STATE_STORAGE_PREFIX.length).split("_$")[0]; const room = MatrixClientPeg.safeGet().getRoom(roomId); if (!room) { logger.debug(`Removing draft for unknown room with key ${keyName}`); diff --git a/src/components/views/rooms/MessageComposer.tsx b/src/components/views/rooms/MessageComposer.tsx index a4a73b495f1..b99c1c401ef 100644 --- a/src/components/views/rooms/MessageComposer.tsx +++ b/src/components/views/rooms/MessageComposer.tsx @@ -26,6 +26,7 @@ import { } from "matrix-js-sdk/src/matrix"; import { Optional } from "matrix-events-sdk"; import { Tooltip } from "@vector-im/compound-web"; +import { logger } from "matrix-js-sdk/src/logger"; import { _t } from "../../../languageHandler"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; @@ -65,6 +66,9 @@ import { createCantStartVoiceMessageBroadcastDialog } from "../dialogs/CantStart import { UIFeature } from "../../../settings/UIFeature"; import { formatTimeLeft } from "../../../DateUtils"; +// The prefix used when persisting editor drafts to localstorage. +export const WYSIWYG_EDITOR_STATE_STORAGE_PREFIX = "mx_wysiwyg_state_"; + let instanceCount = 0; interface ISendButtonProps { @@ -109,6 +113,12 @@ interface IState { initialComposerContent: string; } +type WysiwygComposerState = { + content: string; + isRichText: boolean; + replyEventId?: string; +}; + export class MessageComposer extends React.Component { private dispatcherRef?: string; private messageComposerInput = createRef(); @@ -129,11 +139,32 @@ export class MessageComposer extends React.Component { public constructor(props: IProps, context: React.ContextType) { super(props, context); + this.context = context; // otherwise React will only set it prior to render due to type def above + VoiceRecordingStore.instance.on(UPDATE_EVENT, this.onVoiceStoreUpdate); + window.addEventListener("beforeunload", this.saveWysiwygEditorState); + const isWysiwygLabEnabled = SettingsStore.getValue("feature_wysiwyg_composer"); + let isRichTextEnabled = true; + let initialComposerContent = ""; + if (isWysiwygLabEnabled) { + const wysiwygState = this.restoreWysiwygEditorState(); + if (wysiwygState) { + isRichTextEnabled = wysiwygState.isRichText; + initialComposerContent = wysiwygState.content; + if (wysiwygState.replyEventId) { + dis.dispatch({ + action: "reply_to_event", + event: this.props.room.findEventById(wysiwygState.replyEventId), + context: this.context.timelineRenderingType, + }); + } + } + } + this.state = { - isComposerEmpty: true, - composerContent: "", + isComposerEmpty: initialComposerContent?.length === 0, + composerContent: initialComposerContent, haveRecording: false, recordingTimeLeftSeconds: undefined, // when set to a number, shows a toast isMenuOpen: false, @@ -141,9 +172,9 @@ export class MessageComposer extends React.Component { showStickersButton: SettingsStore.getValue("MessageComposerInput.showStickersButton"), showPollsButton: SettingsStore.getValue("MessageComposerInput.showPollsButton"), showVoiceBroadcastButton: SettingsStore.getValue(Features.VoiceBroadcast), - isWysiwygLabEnabled: SettingsStore.getValue("feature_wysiwyg_composer"), - isRichTextEnabled: true, - initialComposerContent: "", + isWysiwygLabEnabled: isWysiwygLabEnabled, + isRichTextEnabled: isRichTextEnabled, + initialComposerContent: initialComposerContent, }; this.instanceId = instanceCount++; @@ -154,6 +185,52 @@ export class MessageComposer extends React.Component { SettingsStore.monitorSetting("feature_wysiwyg_composer", null); } + private get editorStateKey(): string { + let key = WYSIWYG_EDITOR_STATE_STORAGE_PREFIX + this.props.room.roomId; + if (this.props.relation?.rel_type === THREAD_RELATION_TYPE.name) { + key += `_${this.props.relation.event_id}`; + } + return key; + } + + private restoreWysiwygEditorState(): WysiwygComposerState | undefined { + const json = localStorage.getItem(this.editorStateKey); + if (json) { + try { + const state: WysiwygComposerState = JSON.parse(json); + return state; + } catch (e) { + logger.error(e); + } + } + return undefined; + } + + private saveWysiwygEditorState = (): void => { + if (this.shouldSaveWysiwygEditorState()) { + const { isRichTextEnabled, composerContent } = this.state; + const replyEventId = this.props.replyToEvent ? this.props.replyToEvent.getId() : undefined; + const item: WysiwygComposerState = { + content: composerContent, + isRichText: isRichTextEnabled, + replyEventId: replyEventId, + }; + localStorage.setItem(this.editorStateKey, JSON.stringify(item)); + } else { + this.clearStoredEditorState(); + } + }; + + // should save state when wysiwyg is enabled and has contents or reply is open + private shouldSaveWysiwygEditorState = (): boolean => { + const { isWysiwygLabEnabled, isComposerEmpty } = this.state; + return isWysiwygLabEnabled && (!isComposerEmpty || !!this.props.replyToEvent); + }; + + private clearStoredEditorState(): void { + localStorage.removeItem(this.editorStateKey); + } + private get voiceRecording(): Optional { return this._voiceRecording; } @@ -265,6 +342,8 @@ export class MessageComposer extends React.Component { UIStore.instance.stopTrackingElementDimensions(`MessageComposer${this.instanceId}`); UIStore.instance.removeListener(`MessageComposer${this.instanceId}`, this.onResize); + window.removeEventListener("beforeunload", this.saveWysiwygEditorState); + this.saveWysiwygEditorState(); // clean up our listeners by setting our cached recording to falsy (see internal setter) this.voiceRecording = null; } diff --git a/test/components/structures/MatrixChat-test.tsx b/test/components/structures/MatrixChat-test.tsx index 3b49ec21394..8d7f07e7dfe 100644 --- a/test/components/structures/MatrixChat-test.tsx +++ b/test/components/structures/MatrixChat-test.tsx @@ -624,6 +624,18 @@ describe("", () => { expect(localStorage.getItem(`mx_cider_state_${unknownRoomId}`)).toBeNull(); }); + it("should clean up wysiwyg drafts", async () => { + Date.now = jest.fn(() => timestamp); + localStorage.setItem(`mx_wysiwyg_state_${roomId}`, "fake_content"); + localStorage.setItem(`mx_wysiwyg_state_${unknownRoomId}`, "fake_content"); + await getComponentAndWaitForReady(); + mockClient.emit(ClientEvent.Sync, SyncState.Syncing, SyncState.Syncing); + // let things settle + await flushPromises(); + expect(localStorage.getItem(`mx_wysiwyg_state_${roomId}`)).not.toBeNull(); + expect(localStorage.getItem(`mx_wysiwyg_state_${unknownRoomId}`)).toBeNull(); + }); + it("should not clean up drafts before expiry", async () => { // Set the last cleanup to the recent past localStorage.setItem(`mx_cider_state_${unknownRoomId}`, "fake_content"); diff --git a/test/components/views/rooms/MessageComposer-test.tsx b/test/components/views/rooms/MessageComposer-test.tsx index 1aea150a8cd..6480a84007f 100644 --- a/test/components/views/rooms/MessageComposer-test.tsx +++ b/test/components/views/rooms/MessageComposer-test.tsx @@ -16,7 +16,7 @@ limitations under the License. import * as React from "react"; import { EventType, MatrixEvent, Room, RoomMember, THREAD_RELATION_TYPE } from "matrix-js-sdk/src/matrix"; -import { act, render, screen } from "@testing-library/react"; +import { act, fireEvent, render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { @@ -49,10 +49,6 @@ import { VoiceBroadcastInfoState, VoiceBroadcastRecording } from "../../../../sr import { mkVoiceBroadcastInfoStateEvent } from "../../../voice-broadcast/utils/test-utils"; import { SdkContextClass } from "../../../../src/contexts/SDKContext"; -jest.mock("../../../../src/components/views/rooms/wysiwyg_composer", () => ({ - SendWysiwygComposer: jest.fn().mockImplementation(() =>
), -})); - const openStickerPicker = async (): Promise => { await act(async () => { await userEvent.click(screen.getByLabelText("More options")); @@ -76,12 +72,6 @@ const setCurrentBroadcastRecording = (room: Room, state: VoiceBroadcastInfoState SdkContextClass.instance.voiceBroadcastRecordingsStore.setCurrent(recording); }; -const shouldClearModal = async (): Promise => { - afterEach(async () => { - await clearAllModals(); - }); -}; - const expectVoiceMessageRecordingTriggered = (): void => { // Checking for the voice message dialog text, if no mic can be found. // By this we know at least that starting a voice message was triggered. @@ -96,7 +86,8 @@ describe("MessageComposer", () => { mockPlatformPeg(); }); - afterEach(() => { + afterEach(async () => { + await clearAllModals(); jest.useRealTimers(); SdkContextClass.instance.voiceBroadcastRecordingsStore.clearCurrent(); @@ -415,8 +406,6 @@ describe("MessageComposer", () => { await flushPromises(); }); - shouldClearModal(); - it("should try to start a voice message", () => { expectVoiceMessageRecordingTriggered(); }); @@ -430,8 +419,6 @@ describe("MessageComposer", () => { await waitEnoughCyclesForModal(); }); - shouldClearModal(); - it("should not start a voice message and display the info dialog", async () => { expect(screen.queryByLabelText("Stop recording")).not.toBeInTheDocument(); expect(screen.getByText("Can't start voice message")).toBeInTheDocument(); @@ -446,8 +433,6 @@ describe("MessageComposer", () => { await waitEnoughCyclesForModal(); }); - shouldClearModal(); - it("should try to start a voice message and should not display the info dialog", async () => { expect(screen.queryByText("Can't start voice message")).not.toBeInTheDocument(); expectVoiceMessageRecordingTriggered(); @@ -467,13 +452,50 @@ describe("MessageComposer", () => { }); }); - it("should render SendWysiwygComposer when enabled", () => { + it("wysiwyg correctly persists state to and from localStorage", async () => { const room = mkStubRoom("!roomId:server", "Room 1", cli); - SettingsStore.setValue("feature_wysiwyg_composer", null, SettingLevel.DEVICE, true); + const messageText = "Test Text"; + await SettingsStore.setValue("feature_wysiwyg_composer", null, SettingLevel.DEVICE, true); + const { renderResult, rawComponent } = wrapAndRender({ room }); + const { unmount, rerender } = renderResult; - wrapAndRender({ room }); - expect(screen.getByTestId("wysiwyg-composer")).toBeInTheDocument(); - }); + await act(async () => { + await flushPromises(); + }); + + const key = `mx_wysiwyg_state_${room.roomId}`; + + await act(async () => { + await userEvent.click(screen.getByRole("textbox")); + }); + fireEvent.input(screen.getByRole("textbox"), { + data: messageText, + inputType: "insertText", + }); + + await waitFor(() => expect(screen.getByRole("textbox")).toHaveTextContent(messageText)); + + // Wait for event dispatch to happen + await act(async () => { + await flushPromises(); + }); + + // assert there is state persisted + expect(localStorage.getItem(key)).toBeNull(); + + // ensure the right state was persisted to localStorage + unmount(); + + // assert the persisted state + expect(JSON.parse(localStorage.getItem(key)!)).toStrictEqual({ + content: messageText, + isRichText: true, + }); + + // ensure the correct state is re-loaded + rerender(rawComponent); + await waitFor(() => expect(screen.getByRole("textbox")).toHaveTextContent(messageText)); + }, 10000); }); function wrapAndRender( @@ -506,14 +528,16 @@ function wrapAndRender( permalinkCreator: new RoomPermalinkCreator(room), }; + const getRawComponent = (props = {}, context = roomContext, client = mockClient) => ( + + + + + + ); return { - renderResult: render( - - - - - , - ), + rawComponent: getRawComponent(props, roomContext, mockClient), + renderResult: render(getRawComponent(props, roomContext, mockClient)), roomContext, }; } From 5e56ce7f1900a0f05fda9172874ab38d272708e8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 22 Aug 2024 14:07:41 +0100 Subject: [PATCH 009/154] Update dependency husky to v9 (#12914) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index a37866dbbe5..2f04f46829a 100644 --- a/package.json +++ b/package.json @@ -216,7 +216,7 @@ "fetch-mock-jest": "^1.5.1", "fs-extra": "^11.0.0", "glob": "^11.0.0", - "husky": "^8.0.3", + "husky": "^9.0.0", "jest": "^29.6.2", "jest-canvas-mock": "^2.5.2", "jest-environment-jsdom": "^29.6.2", diff --git a/yarn.lock b/yarn.lock index ccc28991ddb..143764879d0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5570,10 +5570,10 @@ human-signals@^5.0.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28" integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== -husky@^8.0.3: - version "8.0.3" - resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.3.tgz#4936d7212e46d1dea28fef29bb3a108872cd9184" - integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg== +husky@^9.0.0: + version "9.1.5" + resolved "https://registry.yarnpkg.com/husky/-/husky-9.1.5.tgz#2b6edede53ee1adbbd3a3da490628a23f5243b83" + integrity sha512-rowAVRUBfI0b4+niA4SJMhfQwc107VLkBUgEYYAOQAbqDCnra1nYh83hF/MDmhYs9t9n1E3DuKOrs2LYNC+0Ag== iconv-lite@0.4.24: version "0.4.24" From 9671545f1b36a429579a9d864824d68b9f7932a8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 22 Aug 2024 14:25:14 +0100 Subject: [PATCH 010/154] Update dependency eslint-plugin-unicorn to v55 (#12913) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 2f04f46829a..c8a53364d66 100644 --- a/package.json +++ b/package.json @@ -210,7 +210,7 @@ "eslint-plugin-matrix-org": "1.2.1", "eslint-plugin-react": "^7.28.0", "eslint-plugin-react-hooks": "^4.3.0", - "eslint-plugin-unicorn": "^54.0.0", + "eslint-plugin-unicorn": "^55.0.0", "express": "^4.18.2", "fake-indexeddb": "^6.0.0", "fetch-mock-jest": "^1.5.1", diff --git a/yarn.lock b/yarn.lock index 143764879d0..5329c537bfa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4722,6 +4722,28 @@ eslint-plugin-unicorn@^54.0.0: semver "^7.6.1" strip-indent "^3.0.0" +eslint-plugin-unicorn@^55.0.0: + version "55.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-unicorn/-/eslint-plugin-unicorn-55.0.0.tgz#e2aeb397914799895702480970e7d148df5bcc7b" + integrity sha512-n3AKiVpY2/uDcGrS3+QsYDkjPfaOrNrsfQxU9nt5nitd9KuvVXrfAvgCO9DYPSfap+Gqjw9EOrXIsBp5tlHZjA== + dependencies: + "@babel/helper-validator-identifier" "^7.24.5" + "@eslint-community/eslint-utils" "^4.4.0" + ci-info "^4.0.0" + clean-regexp "^1.0.0" + core-js-compat "^3.37.0" + esquery "^1.5.0" + globals "^15.7.0" + indent-string "^4.0.0" + is-builtin-module "^3.2.1" + jsesc "^3.0.2" + pluralize "^8.0.0" + read-pkg-up "^7.0.1" + regexp-tree "^0.1.27" + regjsparser "^0.10.0" + semver "^7.6.1" + strip-indent "^3.0.0" + eslint-rule-composer@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9" @@ -5390,6 +5412,11 @@ globals@^14.0.0: resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== +globals@^15.7.0: + version "15.9.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-15.9.0.tgz#e9de01771091ffbc37db5714dab484f9f69ff399" + integrity sha512-SmSKyLLKFbSr6rptvP8izbyxJL4ILwqO9Jg23UA0sDlGlu58V59D1//I3vlc0KJphVdUR7vMjHIplYnzBxorQA== + globalthis@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" From 35fb0689f1c7787429f9f3ae44b81cf9d5a52d29 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 22 Aug 2024 15:23:14 +0100 Subject: [PATCH 011/154] Update dependency axe-core to v4.10.0 (#12916) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index c8a53364d66..d9b76ce5744 100644 --- a/package.json +++ b/package.json @@ -197,7 +197,7 @@ "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/parser": "^7.0.0", - "axe-core": "4.9.1", + "axe-core": "4.10.0", "babel-jest": "^29.0.0", "blob-polyfill": "^9.0.0", "eslint": "8.57.0", diff --git a/yarn.lock b/yarn.lock index 5329c537bfa..48783e8af17 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3385,7 +3385,12 @@ await-lock@^2.1.0: resolved "https://registry.yarnpkg.com/await-lock/-/await-lock-2.2.2.tgz#a95a9b269bfd2f69d22b17a321686f551152bcef" integrity sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw== -axe-core@4.9.1, axe-core@^4.9.1, axe-core@~4.9.1: +axe-core@4.10.0: + version "4.10.0" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.10.0.tgz#d9e56ab0147278272739a000880196cdfe113b59" + integrity sha512-Mr2ZakwQ7XUAjp7pAwQWRhhK8mQQ6JAaNWSjmjxil0R8BPioMtQsTLOolGYkji1rcL++3dCqZA3zWqpT+9Ew6g== + +axe-core@^4.9.1, axe-core@~4.9.1: version "4.9.1" resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.9.1.tgz#fcd0f4496dad09e0c899b44f6c4bb7848da912ae" integrity sha512-QbUdXJVTpvUTHU7871ppZkdOLBeGUKBQWHkHrvN2V9IQWGMt61zf3B45BtzjxEJzYuj0JBjBZP/hmYS/R9pmAw== From 670ed8155a45eb8a9affd9ac5a07ab90c59fc339 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 22 Aug 2024 18:59:04 +0000 Subject: [PATCH 012/154] Update dependency @sentry/browser to v8.26.0 (#12915) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 136 +++++++++++++++++++++++++++--------------------------- 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/yarn.lock b/yarn.lock index 48783e8af17..ef42564f420 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2286,76 +2286,76 @@ resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.1.0.tgz#f817d1d3265ac5415dadc67edab30ae196696438" integrity sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg== -"@sentry-internal/browser-utils@8.19.0": - version "8.19.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.19.0.tgz#7a25111c5c3430c07b881d5fc83d1598af6d5676" - integrity sha512-kM/2KlikKuBR63nFi2q7MGS3V9K9hakjvUknhr/jHZqDVfEuBKmp1ZlHFAdJtglKHHJy07gPj/XqDH7BbYh5yg== - dependencies: - "@sentry/core" "8.19.0" - "@sentry/types" "8.19.0" - "@sentry/utils" "8.19.0" - -"@sentry-internal/feedback@8.19.0": - version "8.19.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.19.0.tgz#7efef695fd4a058b36ef5c98145c4a990bbe877c" - integrity sha512-Jc77H8fEaGcBhERc2U/o7Q8CZHvlZLT9vAlzq0ZZR20v/1vwYcJW1ysKfTuvmw22hCR6ukhFNl6pqJocXFVhvA== - dependencies: - "@sentry/core" "8.19.0" - "@sentry/types" "8.19.0" - "@sentry/utils" "8.19.0" - -"@sentry-internal/replay-canvas@8.19.0": - version "8.19.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.19.0.tgz#4afe06acadf14a709e36efe5ad7da350e3ce0815" - integrity sha512-l4pKJDHrXEctxrK7Xme/+fKToXpGwr/G2t77BzeE1WEw9LwSwADz/hi8HoMdZzuKWriM2BNbz20tpVS84sODxA== - dependencies: - "@sentry-internal/replay" "8.19.0" - "@sentry/core" "8.19.0" - "@sentry/types" "8.19.0" - "@sentry/utils" "8.19.0" - -"@sentry-internal/replay@8.19.0": - version "8.19.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.19.0.tgz#7b290b19d6ba5c0ab742c4c48839cce329129b3f" - integrity sha512-EW9e1J6XbqXUXQST1AfSIzT9O8OwPyeFOkhkn9/gqOQv08TJvQEIBtWJEoJS+XFMEUuB8IqIzVWNVko/DnGt9A== - dependencies: - "@sentry-internal/browser-utils" "8.19.0" - "@sentry/core" "8.19.0" - "@sentry/types" "8.19.0" - "@sentry/utils" "8.19.0" +"@sentry-internal/browser-utils@8.26.0": + version "8.26.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.26.0.tgz#3c76015e1bddde6775e6a7e115fbb494f247fed1" + integrity sha512-O2Tj+WK33/ZVp5STnz6ZL0OO+/Idk2KqsH0ITQkQmyZ2z0kdzWOeqK7s7q3/My6rB1GfPcyqPcBBv4dVv92FYQ== + dependencies: + "@sentry/core" "8.26.0" + "@sentry/types" "8.26.0" + "@sentry/utils" "8.26.0" + +"@sentry-internal/feedback@8.26.0": + version "8.26.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.26.0.tgz#c29a2a4d97d9a9b56344521f3dbb16e2c40d799e" + integrity sha512-hQtw1gg8n6ERK1UH47F7ZI1zOsbhu0J2VX+TrnkpaQR2FgxDW1oe9Ja6oCV4CQKuR4w+1ZI/Kj4imSt0K33kEw== + dependencies: + "@sentry/core" "8.26.0" + "@sentry/types" "8.26.0" + "@sentry/utils" "8.26.0" + +"@sentry-internal/replay-canvas@8.26.0": + version "8.26.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.26.0.tgz#005e4ebed631d0e505e117d42ae8bc64748628d1" + integrity sha512-2CFQW6f9aJHIo/DqmqYa9PaYoLn1o36ywc0h8oyGrD4oPCbrnE5F++PmTdc71GBODu41HBn/yoCTLmxOD+UjpA== + dependencies: + "@sentry-internal/replay" "8.26.0" + "@sentry/core" "8.26.0" + "@sentry/types" "8.26.0" + "@sentry/utils" "8.26.0" + +"@sentry-internal/replay@8.26.0": + version "8.26.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.26.0.tgz#7d01b1915343bf8ca3d9ef7500994d4a45f3785e" + integrity sha512-JDY7W2bswlp5c3483lKP4kcb75fHNwGNfwD8x8FsY9xMjv7nxeXjLpR5cCEk1XqPq2+n6w4j7mJOXhEXGiUIKg== + dependencies: + "@sentry-internal/browser-utils" "8.26.0" + "@sentry/core" "8.26.0" + "@sentry/types" "8.26.0" + "@sentry/utils" "8.26.0" "@sentry/browser@^8.0.0": - version "8.19.0" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.19.0.tgz#64551150cda979728297035f9a367ed344c7d586" - integrity sha512-ZC1HxIFm4TIGONyy9MkPG6Dw8IAhzq43t5mq9PqrB1ehuWj8GX6Vk3E26kuc2sydAm4AXbj0562OmvZHsAJpUA== - dependencies: - "@sentry-internal/browser-utils" "8.19.0" - "@sentry-internal/feedback" "8.19.0" - "@sentry-internal/replay" "8.19.0" - "@sentry-internal/replay-canvas" "8.19.0" - "@sentry/core" "8.19.0" - "@sentry/types" "8.19.0" - "@sentry/utils" "8.19.0" - -"@sentry/core@8.19.0": - version "8.19.0" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.19.0.tgz#427d09ca27557ddc7c1bfa5e810b7f802836e0b4" - integrity sha512-MrgjsZCEjOJgQjIznnDSrLEy7qL+4LVpNieAvr49cV1rzBNSwGmWRnt/puVaPsLyCUgupVx/43BPUHB/HtKNUw== - dependencies: - "@sentry/types" "8.19.0" - "@sentry/utils" "8.19.0" - -"@sentry/types@8.19.0": - version "8.19.0" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.19.0.tgz#26a5d56c823c5eabbb7d6f53112da335b6d96dcb" - integrity sha512-52C8X5V7mK2KIxMJt8MV5TxXAFHqrQR1RKm1oPTwKVWm8hKr1ZYJXINymNrWvpAc3oVIKLC/sa9WFYgXQh+YlA== - -"@sentry/utils@8.19.0": - version "8.19.0" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-8.19.0.tgz#f22df2a38327b1cff1e04ba7f11fdf1a32d3ba22" - integrity sha512-8dWJJKaUN6Hf92Oxw2TBmHchGua2W3ZmonrZTTwLvl06jcAigbiQD0MGuF5ytZP8PHx860orV+SbTGKFzfU3Pg== - dependencies: - "@sentry/types" "8.19.0" + version "8.26.0" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.26.0.tgz#749508ca8d1da857930f41430eb3a77102712f46" + integrity sha512-e5s6eKlwLZWzTwQcBwqyAGZMMuQROW9Z677VzwkSyREWAIkKjfH2VBxHATnNGc0IVkNHjD7iH3ixo3C0rLKM3w== + dependencies: + "@sentry-internal/browser-utils" "8.26.0" + "@sentry-internal/feedback" "8.26.0" + "@sentry-internal/replay" "8.26.0" + "@sentry-internal/replay-canvas" "8.26.0" + "@sentry/core" "8.26.0" + "@sentry/types" "8.26.0" + "@sentry/utils" "8.26.0" + +"@sentry/core@8.26.0": + version "8.26.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.26.0.tgz#0673a9e2c5b699cf1bde1ed073a345cc393577da" + integrity sha512-g/tVmTZD4GNbLFf++hKJfBpcCAtduFEMLnbfa9iT/QEZjlmP+EzY+GsH9bafM5VsNe8DiOUp+kJKWtShzlVdBA== + dependencies: + "@sentry/types" "8.26.0" + "@sentry/utils" "8.26.0" + +"@sentry/types@8.26.0": + version "8.26.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.26.0.tgz#c999fdd9e52587570f723d2370244bad8f79b571" + integrity sha512-zKmh6SWsJh630rpt7a9vP4Cm4m1C2gDTUqUiH565CajCL/4cePpNWYrNwalSqsOSL7B9OrczA1+n6a6XvND+ng== + +"@sentry/utils@8.26.0": + version "8.26.0" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-8.26.0.tgz#c6629f0f2bc8cbc4fddd124770e9063b4e2d1c65" + integrity sha512-xvlPU9Hd2BlyT+FhWHGNwnxWqdVRk2AHnDtVcW4Ma0Ri5EwS+uy4Jeik5UkSv8C5RVb9VlxFmS8LN3I1MPJsLw== + dependencies: + "@sentry/types" "8.26.0" "@sinclair/typebox@^0.27.8": version "0.27.8" From 135d94c73d0915389480179f539e4fdc2056a765 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 23 Aug 2024 11:13:50 +0100 Subject: [PATCH 013/154] Update playwright monorepo to v1.46.1 (#12918) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/yarn.lock b/yarn.lock index ef42564f420..bf44dc898c2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1940,11 +1940,11 @@ integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== "@playwright/test@^1.40.1": - version "1.45.3" - resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.45.3.tgz#22e9c38b3081d6674b28c6e22f784087776c72e5" - integrity sha512-UKF4XsBfy+u3MFWEH44hva1Q8Da28G6RFtR2+5saw+jgAFQV5yYnB1fu68Mz7fO+5GJF3wgwAIs0UelU8TxFrA== + version "1.46.1" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.46.1.tgz#a8dfdcd623c4c23bb1b7ea588058aad41055c188" + integrity sha512-Fq6SwLujA/DOIvNC2EL/SojJnkKf/rAwJ//APpJJHRyMi1PdKrY3Az+4XNQ51N4RTbItbIByQ0jgd1tayq1aeA== dependencies: - playwright "1.45.3" + playwright "1.46.1" "@radix-ui/primitive@1.0.1": version "1.0.1" @@ -7565,17 +7565,17 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" -playwright-core@1.45.3, playwright-core@^1.45.1: - version "1.45.3" - resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.45.3.tgz#e77bc4c78a621b96c3e629027534ee1d25faac93" - integrity sha512-+ym0jNbcjikaOwwSZycFbwkWgfruWvYlJfThKYAlImbxUgdWFO2oW70ojPm4OpE4t6TAo2FY/smM+hpVTtkhDA== +playwright-core@1.46.1, playwright-core@^1.45.1: + version "1.46.1" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.46.1.tgz#28f3ab35312135dda75b0c92a3e5c0e7edb9cc8b" + integrity sha512-h9LqIQaAv+CYvWzsZ+h3RsrqCStkBHlgo6/TJlFst3cOTlLghBQlJwPOZKQJTKNaD3QIB7aAVQ+gfWbN3NXB7A== -playwright@1.45.3: - version "1.45.3" - resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.45.3.tgz#75143f73093a6e1467f7097083d2f0846fb8dd2f" - integrity sha512-QhVaS+lpluxCaioejDZ95l4Y4jSFCsBvl2UZkpeXlzxmqS+aABr5c82YmfMHrL6x27nvrvykJAFpkzT2eWdJww== +playwright@1.46.1: + version "1.46.1" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.46.1.tgz#ea562bc48373648e10420a10c16842f0b227c218" + integrity sha512-oPcr1yqoXLCkgKtD5eNUPLiN40rYEM39odNpIb6VE6S7/15gJmA1NzVv6zJYusV0e7tzvkU/utBFNa/Kpxmwng== dependencies: - playwright-core "1.45.3" + playwright-core "1.46.1" optionalDependencies: fsevents "2.3.2" From 084823770673f16fee9e4f5600cfc957c0705ba8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 23 Aug 2024 13:49:07 +0100 Subject: [PATCH 014/154] Update dependency @types/sanitize-html to v2.13.0 (#12921) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index d9b76ce5744..a5479d9ec50 100644 --- a/package.json +++ b/package.json @@ -189,7 +189,7 @@ "@types/react-beautiful-dnd": "^13.0.0", "@types/react-dom": "17.0.25", "@types/react-transition-group": "^4.4.0", - "@types/sanitize-html": "2.11.0", + "@types/sanitize-html": "2.13.0", "@types/sdp-transform": "^2.4.6", "@types/seedrandom": "3.0.8", "@types/tar-js": "^0.3.2", diff --git a/yarn.lock b/yarn.lock index bf44dc898c2..193641bc77e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2812,10 +2812,10 @@ resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== -"@types/sanitize-html@2.11.0": - version "2.11.0" - resolved "https://registry.yarnpkg.com/@types/sanitize-html/-/sanitize-html-2.11.0.tgz#582d8c72215c0228e3af2be136e40e0b531addf2" - integrity sha512-7oxPGNQHXLHE48r/r/qjn7q0hlrs3kL7oZnGj0Wf/h9tj/6ibFyRkNbsDxaBBZ4XUZ0Dx5LGCyDJ04ytSofacQ== +"@types/sanitize-html@2.13.0": + version "2.13.0" + resolved "https://registry.yarnpkg.com/@types/sanitize-html/-/sanitize-html-2.13.0.tgz#ac3620e867b7c68deab79c72bd117e2049cdd98e" + integrity sha512-X31WxbvW9TjIhZZNyNBZ/p5ax4ti7qsNDBDEnH4zAgmEh35YnFD1UiS6z9Cd34kKm0LslFW0KPmTQzu/oGtsqQ== dependencies: htmlparser2 "^8.0.0" From 69da1754cb9034bd4acda7bd9e3fe77d29e245c2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 23 Aug 2024 14:06:56 +0100 Subject: [PATCH 015/154] Update babel monorepo (#12920) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 462 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 262 insertions(+), 200 deletions(-) diff --git a/yarn.lock b/yarn.lock index 193641bc77e..ae6f9b9efc0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -79,10 +79,10 @@ "@babel/highlight" "^7.22.13" chalk "^2.4.2" -"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.24.8": - version "7.24.9" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.9.tgz#53eee4e68f1c1d0282aa0eb05ddb02d033fc43a0" - integrity sha512-e701mcfApCJqMMueQI0Fb68Amflj83+dvAvHawoBpAz+GDjCIyGHzNwnefjsWJ3xiYAqqiQFoWbspGYBdb2/ng== +"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.25.2", "@babel/compat-data@^7.25.4": + version "7.25.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.4.tgz#7d2a80ce229890edcf4cc259d4d696cb4dae2fcb" + integrity sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ== "@babel/compat-data@^7.23.5": version "7.24.7" @@ -90,20 +90,20 @@ integrity sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw== "@babel/core@^7.0.0", "@babel/core@^7.12.10": - version "7.24.9" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.9.tgz#dc07c9d307162c97fa9484ea997ade65841c7c82" - integrity sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg== + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.25.2.tgz#ed8eec275118d7613e77a352894cd12ded8eba77" + integrity sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA== dependencies: "@ampproject/remapping" "^2.2.0" "@babel/code-frame" "^7.24.7" - "@babel/generator" "^7.24.9" - "@babel/helper-compilation-targets" "^7.24.8" - "@babel/helper-module-transforms" "^7.24.9" - "@babel/helpers" "^7.24.8" - "@babel/parser" "^7.24.8" - "@babel/template" "^7.24.7" - "@babel/traverse" "^7.24.8" - "@babel/types" "^7.24.9" + "@babel/generator" "^7.25.0" + "@babel/helper-compilation-targets" "^7.25.2" + "@babel/helper-module-transforms" "^7.25.2" + "@babel/helpers" "^7.25.0" + "@babel/parser" "^7.25.0" + "@babel/template" "^7.25.0" + "@babel/traverse" "^7.25.2" + "@babel/types" "^7.25.2" convert-source-map "^2.0.0" debug "^4.1.0" gensync "^1.0.0-beta.2" @@ -132,18 +132,18 @@ semver "^6.3.1" "@babel/eslint-parser@^7.12.10": - version "7.24.8" - resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.24.8.tgz#bc655255fa4ded3694cc10ef3dbea6d69639c831" - integrity sha512-nYAikI4XTGokU2QX7Jx+v4rxZKhKivaQaREZjuW3mrJrbdWJ5yUfohnoUULge+zEEaKjPYNxhoRgUKktjXtbwA== + version "7.25.1" + resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.25.1.tgz#469cee4bd18a88ff3edbdfbd227bd20e82aa9b82" + integrity sha512-Y956ghgTT4j7rKesabkh5WeqgSFZVFwaPR0IWFm7KFHFmmJ4afbG49SmfW4S+GyRPx0Dy5jxEWA5t0rpxfElWg== dependencies: "@nicolo-ribaudo/eslint-scope-5-internals" "5.1.1-v1" eslint-visitor-keys "^2.1.0" semver "^6.3.1" "@babel/eslint-plugin@^7.12.10": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/eslint-plugin/-/eslint-plugin-7.24.7.tgz#ebdab31638cdcc720f0c788813066e9b5c0b3e29" - integrity sha512-lODNPJnM+OfcxxBvdmI2YmUeC0fBK3k9yET5O+1Eukr8d5VpO19c6ARtNheE2t2i/8XNYTzp3HeGEAAGZH3nnQ== + version "7.25.1" + resolved "https://registry.yarnpkg.com/@babel/eslint-plugin/-/eslint-plugin-7.25.1.tgz#fc7fff590ab566c9d643fdecf346b69064157ef9" + integrity sha512-jF04YOsrCbEeQk4s+FwsuRddwBiAHooMDG9/nrV83HiYQwEuQppbXTeXyydxCoH5oEWmVBI51wHuZrcIXMkPfw== dependencies: eslint-rule-composer "^0.3.0" @@ -167,7 +167,7 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" -"@babel/generator@^7.24.7", "@babel/generator@^7.24.8", "@babel/generator@^7.24.9": +"@babel/generator@^7.24.7": version "7.24.10" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.10.tgz#a4ab681ec2a78bbb9ba22a3941195e28a81d8e76" integrity sha512-o9HBZL1G2129luEUlG1hB4N/nlYNWHnpwlND9eOMclRqqu1YDy2sSYVCFUZwl8I1Gxh+QSRrP2vD7EpUmFVXxg== @@ -177,6 +177,16 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" +"@babel/generator@^7.25.0", "@babel/generator@^7.25.4": + version "7.25.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.5.tgz#b31cf05b3fe8c32d206b6dad03bb0aacbde73450" + integrity sha512-abd43wyLfbWoxC6ahM8xTkqLpGB2iWBVyuKC9/srhFunCd1SDNrV1s72bBpK4hLj8KLzHBBcOblvLQZBNw9r3w== + dependencies: + "@babel/types" "^7.25.4" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^2.5.1" + "@babel/helper-annotate-as-pure@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz#5373c7bc8366b12a033b4be1ac13a206c6656aab" @@ -203,36 +213,34 @@ lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.24.7", "@babel/helper-compilation-targets@^7.24.8": - version "7.24.8" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.8.tgz#b607c3161cd9d1744977d4f97139572fe778c271" - integrity sha512-oU+UoqCHdp+nWVDkpldqIQL/i/bvAv53tRqLG/s+cOXxe66zOYLU7ar/Xs3LdmBihrUMEUhwu6dMZwbNOYDwvw== +"@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.24.7", "@babel/helper-compilation-targets@^7.24.8", "@babel/helper-compilation-targets@^7.25.2": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz#e1d9410a90974a3a5a66e84ff55ef62e3c02d06c" + integrity sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw== dependencies: - "@babel/compat-data" "^7.24.8" + "@babel/compat-data" "^7.25.2" "@babel/helper-validator-option" "^7.24.8" browserslist "^4.23.1" lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.24.7": - version "7.24.8" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.8.tgz#47f546408d13c200c0867f9d935184eaa0851b09" - integrity sha512-4f6Oqnmyp2PP3olgUMmOwC3akxSm5aBYraQ6YDdKy7NcAMkDECHWG0DEnV6M2UAkERgIBhYt8S27rURPg7SxWA== +"@babel/helper-create-class-features-plugin@^7.24.7", "@babel/helper-create-class-features-plugin@^7.25.4": + version "7.25.4" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.4.tgz#57eaf1af38be4224a9d9dd01ddde05b741f50e14" + integrity sha512-ro/bFs3/84MDgDmMwbcHgDa8/E6J3QKNTk4xJJnVeFtGE+tL0K26E3pNxhYz2b67fJpt7Aphw5XcploKXuCvCQ== dependencies: "@babel/helper-annotate-as-pure" "^7.24.7" - "@babel/helper-environment-visitor" "^7.24.7" - "@babel/helper-function-name" "^7.24.7" "@babel/helper-member-expression-to-functions" "^7.24.8" "@babel/helper-optimise-call-expression" "^7.24.7" - "@babel/helper-replace-supers" "^7.24.7" + "@babel/helper-replace-supers" "^7.25.0" "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" - "@babel/helper-split-export-declaration" "^7.24.7" + "@babel/traverse" "^7.25.4" semver "^6.3.1" -"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.24.7.tgz#be4f435a80dc2b053c76eeb4b7d16dd22cfc89da" - integrity sha512-03TCmXy2FtXJEZfbXDTSqq1fRJArk7lX9DOFC/47VthYcxyIOx+eXQmdo6DOQvrbpIix+KfXwvuXdFDZHxt+rA== +"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.24.7", "@babel/helper-create-regexp-features-plugin@^7.25.0", "@babel/helper-create-regexp-features-plugin@^7.25.2": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.2.tgz#24c75974ed74183797ffd5f134169316cd1808d9" + integrity sha512-+wqVGP+DFmqwFD3EH6TMTfUNeqDehV3E/dl+Sd54eaXqm17tEUNbEIn4sVivVowbvUpOtIGxdo3GoXyDH9N/9g== dependencies: "@babel/helper-annotate-as-pure" "^7.24.7" regexpu-core "^5.3.1" @@ -271,7 +279,7 @@ dependencies: "@babel/types" "^7.24.7" -"@babel/helper-member-expression-to-functions@^7.24.7", "@babel/helper-member-expression-to-functions@^7.24.8": +"@babel/helper-member-expression-to-functions@^7.24.8": version "7.24.8" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz#6155e079c913357d24a4c20480db7c712a5c3fb6" integrity sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA== @@ -305,16 +313,15 @@ "@babel/helper-split-export-declaration" "^7.22.6" "@babel/helper-validator-identifier" "^7.22.20" -"@babel/helper-module-transforms@^7.24.7", "@babel/helper-module-transforms@^7.24.8", "@babel/helper-module-transforms@^7.24.9": - version "7.24.9" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.24.9.tgz#e13d26306b89eea569180868e652e7f514de9d29" - integrity sha512-oYbh+rtFKj/HwBQkFlUzvcybzklmVdVV3UU+mN7n2t/q3yGHbuVdNxyFvSBO1tfvjyArpHNcWMAzsSPdyI46hw== +"@babel/helper-module-transforms@^7.24.7", "@babel/helper-module-transforms@^7.24.8", "@babel/helper-module-transforms@^7.25.0", "@babel/helper-module-transforms@^7.25.2": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz#ee713c29768100f2776edf04d4eb23b8d27a66e6" + integrity sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ== dependencies: - "@babel/helper-environment-visitor" "^7.24.7" "@babel/helper-module-imports" "^7.24.7" "@babel/helper-simple-access" "^7.24.7" - "@babel/helper-split-export-declaration" "^7.24.7" "@babel/helper-validator-identifier" "^7.24.7" + "@babel/traverse" "^7.25.2" "@babel/helper-optimise-call-expression@^7.24.7": version "7.24.7" @@ -328,23 +335,23 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz#94ee67e8ec0e5d44ea7baeb51e571bd26af07878" integrity sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg== -"@babel/helper-remap-async-to-generator@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.24.7.tgz#b3f0f203628522713849d49403f1a414468be4c7" - integrity sha512-9pKLcTlZ92hNZMQfGCHImUpDOlAgkkpqalWEeftW5FBya75k8Li2ilerxkM/uBEj01iBZXcCIB/bwvDYgWyibA== +"@babel/helper-remap-async-to-generator@^7.24.7", "@babel/helper-remap-async-to-generator@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.0.tgz#d2f0fbba059a42d68e5e378feaf181ef6055365e" + integrity sha512-NhavI2eWEIz/H9dbrG0TuOicDhNexze43i5z7lEqwYm0WEZVTwnPpA0EafUTP7+6/W79HWIP2cTe3Z5NiSTVpw== dependencies: "@babel/helper-annotate-as-pure" "^7.24.7" - "@babel/helper-environment-visitor" "^7.24.7" - "@babel/helper-wrap-function" "^7.24.7" + "@babel/helper-wrap-function" "^7.25.0" + "@babel/traverse" "^7.25.0" -"@babel/helper-replace-supers@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.24.7.tgz#f933b7eed81a1c0265740edc91491ce51250f765" - integrity sha512-qTAxxBM81VEyoAY0TtLrx1oAEJc09ZK67Q9ljQToqCnA+55eNwCORaxlKyu+rNfX86o8OXRUSNUnrtsAZXM9sg== +"@babel/helper-replace-supers@^7.24.7", "@babel/helper-replace-supers@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz#ff44deac1c9f619523fe2ca1fd650773792000a9" + integrity sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg== dependencies: - "@babel/helper-environment-visitor" "^7.24.7" - "@babel/helper-member-expression-to-functions" "^7.24.7" + "@babel/helper-member-expression-to-functions" "^7.24.8" "@babel/helper-optimise-call-expression" "^7.24.7" + "@babel/traverse" "^7.25.0" "@babel/helper-simple-access@^7.22.5": version "7.24.5" @@ -401,15 +408,14 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz#3725cdeea8b480e86d34df15304806a06975e33d" integrity sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q== -"@babel/helper-wrap-function@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.24.7.tgz#52d893af7e42edca7c6d2c6764549826336aae1f" - integrity sha512-N9JIYk3TD+1vq/wn77YnJOqMtfWhNewNE+DJV4puD2X7Ew9J4JvrzrFDfTfyv5EgEXVy9/Wt8QiOErzEmv5Ifw== +"@babel/helper-wrap-function@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.25.0.tgz#dab12f0f593d6ca48c0062c28bcfb14ebe812f81" + integrity sha512-s6Q1ebqutSiZnEjaofc/UKDyC4SbzV5n5SrA2Gq8UawLycr3i04f1dX4OzoQVnexm6aOCh37SQNYlJ/8Ku+PMQ== dependencies: - "@babel/helper-function-name" "^7.24.7" - "@babel/template" "^7.24.7" - "@babel/traverse" "^7.24.7" - "@babel/types" "^7.24.7" + "@babel/template" "^7.25.0" + "@babel/traverse" "^7.25.0" + "@babel/types" "^7.25.0" "@babel/helpers@^7.22.15": version "7.22.15" @@ -420,13 +426,13 @@ "@babel/traverse" "^7.22.15" "@babel/types" "^7.22.15" -"@babel/helpers@^7.24.8": - version "7.24.8" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.8.tgz#2820d64d5d6686cca8789dd15b074cd862795873" - integrity sha512-gV2265Nkcz7weJJfvDoAEVzC1e2OTDpkGbEsebse8koXUJUXPsCMi7sRo/+SPMuMZ9MtUPnGwITTnQnU5YjyaQ== +"@babel/helpers@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.25.0.tgz#e69beb7841cb93a6505531ede34f34e6a073650a" + integrity sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw== dependencies: - "@babel/template" "^7.24.7" - "@babel/types" "^7.24.8" + "@babel/template" "^7.25.0" + "@babel/types" "^7.25.0" "@babel/highlight@^7.22.13": version "7.23.4" @@ -447,25 +453,34 @@ js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.12.11", "@babel/parser@^7.14.7", "@babel/parser@^7.18.5", "@babel/parser@^7.20.7", "@babel/parser@^7.22.15", "@babel/parser@^7.22.16", "@babel/parser@^7.23.3", "@babel/parser@^7.24.7", "@babel/parser@^7.24.8": - version "7.24.8" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.8.tgz#58a4dbbcad7eb1d48930524a3fd93d93e9084c6f" - integrity sha512-WzfbgXOkGzZiXXCqk43kKwZjzwx4oulxZi3nq2TYL9mOjQv6kYwul9mz6ID36njuL7Xkp6nJEfok848Zj10j/w== +"@babel/parser@^7.1.0", "@babel/parser@^7.12.11", "@babel/parser@^7.14.7", "@babel/parser@^7.18.5", "@babel/parser@^7.20.7", "@babel/parser@^7.22.15", "@babel/parser@^7.22.16", "@babel/parser@^7.23.3", "@babel/parser@^7.24.7", "@babel/parser@^7.25.0", "@babel/parser@^7.25.4": + version "7.25.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.4.tgz#af4f2df7d02440286b7de57b1c21acfb2a6f257a" + integrity sha512-nq+eWrOgdtu3jG5Os4TQP3x3cLA8hR8TvJNjD8vnPa20WGycimcparWnLK4jJhElTK6SDyuJo1weMKO/5LpmLA== + dependencies: + "@babel/types" "^7.25.4" -"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.7.tgz#fd059fd27b184ea2b4c7e646868a9a381bbc3055" - integrity sha512-TiT1ss81W80eQsN+722OaeQMY/G4yTb4G9JrqeiDADs3N8lbPMGldWi9x8tyqCW5NLx1Jh2AvkE6r6QvEltMMQ== +"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.3": + version "7.25.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.3.tgz#dca427b45a6c0f5c095a1c639dfe2476a3daba7f" + integrity sha512-wUrcsxZg6rqBXG05HG1FPYgsP6EvwF4WpBbxIpWIIYnH8wG0gzx3yZY3dtEHas4sTAOGkbTsc9EGPxwff8lRoA== dependencies: - "@babel/helper-environment-visitor" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/traverse" "^7.25.3" -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.7.tgz#468096ca44bbcbe8fcc570574e12eb1950e18107" - integrity sha512-unaQgZ/iRu/By6tsjMZzpeBZjChYfLYry6HrEXPoz3KmfF0sVBQ1l8zKMQ4xRGLWVsjuvB8nQfjNP/DcfEOCsg== +"@babel/plugin-bugfix-safari-class-field-initializer-scope@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.0.tgz#cd0c583e01369ef51676bdb3d7b603e17d2b3f73" + integrity sha512-Bm4bH2qsX880b/3ziJ8KD711LT7z4u8CFudmjqle65AZj/HNUFhEf90dqYv6O86buWvSBmeQDjv0Tn2aF/bIBA== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.8" + +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.0.tgz#749bde80356b295390954643de7635e0dffabe73" + integrity sha512-lXwdNZtTmeVOOFtwM/WDe7yg1PL8sYhRk/XH0FzbR2HDQ0xC+EnQ/JHeoMYSavtU115tnUk0q9CDyq8si+LMAA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.8" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.24.7": version "7.24.7" @@ -476,13 +491,13 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" "@babel/plugin-transform-optional-chaining" "^7.24.7" -"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.7.tgz#71b21bb0286d5810e63a1538aa901c58e87375ec" - integrity sha512-utA4HuR6F4Vvcr+o4DnjL8fCOlgRFGbeeBEGNg3ZTrLFw6VWG5XmUrvcQ0FjIYMU2ST4XcR2Wsp7t9qOAPnxMg== +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.0.tgz#3a82a70e7cb7294ad2559465ebcb871dfbf078fb" + integrity sha512-tggFrk1AIShG/RUQbEwt2Tr/E+ObkfwrPjR6BjbRvsx24+PSjK8zrq0GWPNCjo8qpRx4DuJzlcvWJqlm+0h3kw== dependencies: - "@babel/helper-environment-visitor" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/traverse" "^7.25.0" "@babel/plugin-proposal-export-default-from@^7.12.1": version "7.24.7" @@ -673,15 +688,15 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.7" -"@babel/plugin-transform-async-generator-functions@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.7.tgz#7330a5c50e05181ca52351b8fd01642000c96cfd" - integrity sha512-o+iF77e3u7ZS4AoAuJvapz9Fm001PuD2V3Lp6OSE4FYQke+cSewYtnek+THqGRWyQloRCyvWL1OkyfNEl9vr/g== +"@babel/plugin-transform-async-generator-functions@^7.25.4": + version "7.25.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.4.tgz#2afd4e639e2d055776c9f091b6c0c180ed8cf083" + integrity sha512-jz8cV2XDDTqjKPwVPJBIjORVEmSGYhdRa8e5k5+vN+uwcjSrSxUaebBRa4ko1jqNF2uxyg8G6XYk30Jv285xzg== dependencies: - "@babel/helper-environment-visitor" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/helper-remap-async-to-generator" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-remap-async-to-generator" "^7.25.0" "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/traverse" "^7.25.4" "@babel/plugin-transform-async-to-generator@^7.24.7": version "7.24.7" @@ -699,14 +714,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.7" -"@babel/plugin-transform-block-scoping@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.7.tgz#42063e4deb850c7bd7c55e626bf4e7ab48e6ce02" - integrity sha512-Nd5CvgMbWc+oWzBsuaMcbwjJWAcp5qzrbg69SZdHSP7AMY0AbWFqFO0WTFCA1jxhMCwodRwvRec8k0QUbZk7RQ== +"@babel/plugin-transform-block-scoping@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.0.tgz#23a6ed92e6b006d26b1869b1c91d1b917c2ea2ac" + integrity sha512-yBQjYoOjXlFv9nlXb3f1casSHOZkWr29NX+zChVanLg5Nc157CrbEX9D7hxxtTpuFy7Q0YzmmWfJxzvps4kXrQ== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.8" -"@babel/plugin-transform-class-properties@^7.12.1", "@babel/plugin-transform-class-properties@^7.24.7": +"@babel/plugin-transform-class-properties@^7.12.1": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.7.tgz#256879467b57b0b68c7ddfc5b76584f398cd6834" integrity sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w== @@ -714,6 +729,14 @@ "@babel/helper-create-class-features-plugin" "^7.24.7" "@babel/helper-plugin-utils" "^7.24.7" +"@babel/plugin-transform-class-properties@^7.25.4": + version "7.25.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.4.tgz#bae7dbfcdcc2e8667355cd1fb5eda298f05189fd" + integrity sha512-nZeZHyCWPfjkdU5pA/uHiTaDAFUEqkpzf1YoQT2NeSynCGYq9rxfyI3XpQbfx/a0hSnFH6TGlEXvae5Vi7GD8g== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.25.4" + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/plugin-transform-class-static-block@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz#c82027ebb7010bc33c116d4b5044fbbf8c05484d" @@ -723,18 +746,16 @@ "@babel/helper-plugin-utils" "^7.24.7" "@babel/plugin-syntax-class-static-block" "^7.14.5" -"@babel/plugin-transform-classes@^7.24.8": - version "7.24.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.8.tgz#ad23301fe5bc153ca4cf7fb572a9bc8b0b711cf7" - integrity sha512-VXy91c47uujj758ud9wx+OMgheXm4qJfyhj1P18YvlrQkNOSrwsteHk+EFS3OMGfhMhpZa0A+81eE7G4QC+3CA== +"@babel/plugin-transform-classes@^7.25.4": + version "7.25.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.4.tgz#d29dbb6a72d79f359952ad0b66d88518d65ef89a" + integrity sha512-oexUfaQle2pF/b6E0dwsxQtAol9TLSO88kQvym6HHBWFliV2lGdrPieX+WgMRLSJDVzdYywk7jXbLPuO2KLTLg== dependencies: "@babel/helper-annotate-as-pure" "^7.24.7" - "@babel/helper-compilation-targets" "^7.24.8" - "@babel/helper-environment-visitor" "^7.24.7" - "@babel/helper-function-name" "^7.24.7" + "@babel/helper-compilation-targets" "^7.25.2" "@babel/helper-plugin-utils" "^7.24.8" - "@babel/helper-replace-supers" "^7.24.7" - "@babel/helper-split-export-declaration" "^7.24.7" + "@babel/helper-replace-supers" "^7.25.0" + "@babel/traverse" "^7.25.4" globals "^11.1.0" "@babel/plugin-transform-computed-properties@^7.24.7": @@ -767,6 +788,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.7" +"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.0.tgz#809af7e3339466b49c034c683964ee8afb3e2604" + integrity sha512-YLpb4LlYSc3sCUa35un84poXoraOiQucUTTu8X1j18JV+gNa8E0nyUf/CjZ171IRGr4jEguF+vzJU66QZhn29g== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.25.0" + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/plugin-transform-dynamic-import@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz#4d8b95e3bae2b037673091aa09cd33fecd6419f4" @@ -799,14 +828,14 @@ "@babel/helper-plugin-utils" "^7.24.7" "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" -"@babel/plugin-transform-function-name@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.7.tgz#6d8601fbffe665c894440ab4470bc721dd9131d6" - integrity sha512-U9FcnA821YoILngSmYkW6FjyQe2TyZD5pHt4EVIhmcTkrJw/3KqcrRSxuOo5tFZJi7TE19iDyI1u+weTI7bn2w== +"@babel/plugin-transform-function-name@^7.25.1": + version "7.25.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.1.tgz#b85e773097526c1a4fc4ba27322748643f26fc37" + integrity sha512-TVVJVdW9RKMNgJJlLtHsKDTydjZAbwIsn6ySBPQaEAUU5+gVvlJt/9nRmqVbsV/IBanRjzWoaAQKLoamWVOUuA== dependencies: - "@babel/helper-compilation-targets" "^7.24.7" - "@babel/helper-function-name" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-compilation-targets" "^7.24.8" + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/traverse" "^7.25.1" "@babel/plugin-transform-json-strings@^7.24.7": version "7.24.7" @@ -816,12 +845,12 @@ "@babel/helper-plugin-utils" "^7.24.7" "@babel/plugin-syntax-json-strings" "^7.8.3" -"@babel/plugin-transform-literals@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.7.tgz#36b505c1e655151a9d7607799a9988fc5467d06c" - integrity sha512-vcwCbb4HDH+hWi8Pqenwnjy+UiklO4Kt1vfspcQYFhJdpthSnW8XvWGyDZWKNVrVbVViI/S7K9PDJZiUmP2fYQ== +"@babel/plugin-transform-literals@^7.25.2": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.2.tgz#deb1ad14fc5490b9a65ed830e025bca849d8b5f3" + integrity sha512-HQI+HcTbm9ur3Z2DkO+jgESMAMcYLuN/A7NRw9juzxAezN9AvqvUTnpKP/9kkYANz6u7dFlAyOu44ejuGySlfw== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.8" "@babel/plugin-transform-logical-assignment-operators@^7.24.7": version "7.24.7" @@ -864,15 +893,15 @@ "@babel/helper-plugin-utils" "^7.24.8" "@babel/helper-simple-access" "^7.24.7" -"@babel/plugin-transform-modules-systemjs@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.7.tgz#f8012316c5098f6e8dee6ecd58e2bc6f003d0ce7" - integrity sha512-GYQE0tW7YoaN13qFh3O1NCY4MPkUiAH3fiF7UcV/I3ajmDKEdG3l+UOcbAm4zUE3gnvUU+Eni7XrVKo9eO9auw== +"@babel/plugin-transform-modules-systemjs@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.0.tgz#8f46cdc5f9e5af74f3bd019485a6cbe59685ea33" + integrity sha512-YPJfjQPDXxyQWg/0+jHKj1llnY5f/R6a0p/vP4lPymxLu7Lvl4k2WMitqi08yxwQcCVUUdG9LCUj4TNEgAp3Jw== dependencies: - "@babel/helper-hoist-variables" "^7.24.7" - "@babel/helper-module-transforms" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-module-transforms" "^7.25.0" + "@babel/helper-plugin-utils" "^7.24.8" "@babel/helper-validator-identifier" "^7.24.7" + "@babel/traverse" "^7.25.0" "@babel/plugin-transform-modules-umd@^7.24.7": version "7.24.7" @@ -955,13 +984,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.7" -"@babel/plugin-transform-private-methods@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.7.tgz#e6318746b2ae70a59d023d5cc1344a2ba7a75f5e" - integrity sha512-COTCOkG2hn4JKGEKBADkA8WNb35TGkkRbI5iT845dB+NyqgO8Hn+ajPbSnIQznneJTa3d30scb6iz/DhH8GsJQ== +"@babel/plugin-transform-private-methods@^7.25.4": + version "7.25.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.4.tgz#9bbefbe3649f470d681997e0b64a4b254d877242" + integrity sha512-ao8BG7E2b/URaUQGqN3Tlsg+M3KlHY6rJ1O1gXAEUnZoyNQnvKyH87Kfg+FoxSeyWUB8ISZZsC91C44ZuBFytw== dependencies: - "@babel/helper-create-class-features-plugin" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-create-class-features-plugin" "^7.25.4" + "@babel/helper-plugin-utils" "^7.24.8" "@babel/plugin-transform-private-property-in-object@^7.24.7": version "7.24.7" @@ -1109,27 +1138,28 @@ "@babel/helper-create-regexp-features-plugin" "^7.24.7" "@babel/helper-plugin-utils" "^7.24.7" -"@babel/plugin-transform-unicode-sets-regex@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.7.tgz#d40705d67523803a576e29c63cef6e516b858ed9" - integrity sha512-2G8aAvF4wy1w/AGZkemprdGMRg5o6zPNhbHVImRz3lss55TYCBd6xStN19rt8XJHq20sqV0JbyWjOWwQRwV/wg== +"@babel/plugin-transform-unicode-sets-regex@^7.25.4": + version "7.25.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.4.tgz#be664c2a0697ffacd3423595d5edef6049e8946c" + integrity sha512-qesBxiWkgN1Q+31xUE9RcMk79eOXXDCv6tfyGMRSs4RGlioSg2WVyQAm07k726cSE56pa+Kb0y9epX2qaXzTvA== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-create-regexp-features-plugin" "^7.25.2" + "@babel/helper-plugin-utils" "^7.24.8" "@babel/preset-env@^7.12.11": - version "7.24.8" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.24.8.tgz#e0db94d7f17d6f0e2564e8d29190bc8cdacec2d1" - integrity sha512-vObvMZB6hNWuDxhSaEPTKCwcqkAIuDtE+bQGn4XMXne1DSLzFVY8Vmj1bm+mUQXYNN8NmaQEO+r8MMbzPr1jBQ== + version "7.25.4" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.25.4.tgz#be23043d43a34a2721cd0f676c7ba6f1481f6af6" + integrity sha512-W9Gyo+KmcxjGahtt3t9fb14vFRWvPpu5pT6GBlovAK6BTBcxgjfVMSQCfJl4oi35ODrxP6xx2Wr8LNST57Mraw== dependencies: - "@babel/compat-data" "^7.24.8" - "@babel/helper-compilation-targets" "^7.24.8" + "@babel/compat-data" "^7.25.4" + "@babel/helper-compilation-targets" "^7.25.2" "@babel/helper-plugin-utils" "^7.24.8" "@babel/helper-validator-option" "^7.24.8" - "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.24.7" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.24.7" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.25.3" + "@babel/plugin-bugfix-safari-class-field-initializer-scope" "^7.25.0" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.25.0" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.24.7" - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.24.7" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.25.0" "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" "@babel/plugin-syntax-async-generators" "^7.8.4" "@babel/plugin-syntax-class-properties" "^7.12.13" @@ -1150,29 +1180,30 @@ "@babel/plugin-syntax-top-level-await" "^7.14.5" "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" "@babel/plugin-transform-arrow-functions" "^7.24.7" - "@babel/plugin-transform-async-generator-functions" "^7.24.7" + "@babel/plugin-transform-async-generator-functions" "^7.25.4" "@babel/plugin-transform-async-to-generator" "^7.24.7" "@babel/plugin-transform-block-scoped-functions" "^7.24.7" - "@babel/plugin-transform-block-scoping" "^7.24.7" - "@babel/plugin-transform-class-properties" "^7.24.7" + "@babel/plugin-transform-block-scoping" "^7.25.0" + "@babel/plugin-transform-class-properties" "^7.25.4" "@babel/plugin-transform-class-static-block" "^7.24.7" - "@babel/plugin-transform-classes" "^7.24.8" + "@babel/plugin-transform-classes" "^7.25.4" "@babel/plugin-transform-computed-properties" "^7.24.7" "@babel/plugin-transform-destructuring" "^7.24.8" "@babel/plugin-transform-dotall-regex" "^7.24.7" "@babel/plugin-transform-duplicate-keys" "^7.24.7" + "@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.25.0" "@babel/plugin-transform-dynamic-import" "^7.24.7" "@babel/plugin-transform-exponentiation-operator" "^7.24.7" "@babel/plugin-transform-export-namespace-from" "^7.24.7" "@babel/plugin-transform-for-of" "^7.24.7" - "@babel/plugin-transform-function-name" "^7.24.7" + "@babel/plugin-transform-function-name" "^7.25.1" "@babel/plugin-transform-json-strings" "^7.24.7" - "@babel/plugin-transform-literals" "^7.24.7" + "@babel/plugin-transform-literals" "^7.25.2" "@babel/plugin-transform-logical-assignment-operators" "^7.24.7" "@babel/plugin-transform-member-expression-literals" "^7.24.7" "@babel/plugin-transform-modules-amd" "^7.24.7" "@babel/plugin-transform-modules-commonjs" "^7.24.8" - "@babel/plugin-transform-modules-systemjs" "^7.24.7" + "@babel/plugin-transform-modules-systemjs" "^7.25.0" "@babel/plugin-transform-modules-umd" "^7.24.7" "@babel/plugin-transform-named-capturing-groups-regex" "^7.24.7" "@babel/plugin-transform-new-target" "^7.24.7" @@ -1183,7 +1214,7 @@ "@babel/plugin-transform-optional-catch-binding" "^7.24.7" "@babel/plugin-transform-optional-chaining" "^7.24.8" "@babel/plugin-transform-parameters" "^7.24.7" - "@babel/plugin-transform-private-methods" "^7.24.7" + "@babel/plugin-transform-private-methods" "^7.25.4" "@babel/plugin-transform-private-property-in-object" "^7.24.7" "@babel/plugin-transform-property-literals" "^7.24.7" "@babel/plugin-transform-regenerator" "^7.24.7" @@ -1196,10 +1227,10 @@ "@babel/plugin-transform-unicode-escapes" "^7.24.7" "@babel/plugin-transform-unicode-property-regex" "^7.24.7" "@babel/plugin-transform-unicode-regex" "^7.24.7" - "@babel/plugin-transform-unicode-sets-regex" "^7.24.7" + "@babel/plugin-transform-unicode-sets-regex" "^7.25.4" "@babel/preset-modules" "0.1.6-no-external-plugins" babel-plugin-polyfill-corejs2 "^0.4.10" - babel-plugin-polyfill-corejs3 "^0.10.4" + babel-plugin-polyfill-corejs3 "^0.10.6" babel-plugin-polyfill-regenerator "^0.6.1" core-js-compat "^3.37.1" semver "^6.3.1" @@ -1253,13 +1284,13 @@ integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== "@babel/runtime@^7.0.0", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.9", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": - version "7.24.8" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.8.tgz#5d958c3827b13cc6d05e038c07fb2e5e3420d82e" - integrity sha512-5F7SDGs1T72ZczbRwbGO9lQi0NLjQxzl6i4lJxLxfW9U5UluCSyEJeniWvnhl3/euNiqQVbo8zruhsDfid0esA== + version "7.25.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.4.tgz#6ef37d678428306e7d75f054d5b1bdb8cf8aa8ee" + integrity sha512-DSgLeL/FNcpXuzav5wfYvHCGvynXkJbn3Zvc3823AEe9nPwW9IK4UoCSS5yGymmQzN0pCPvivtgS6/8U2kkm1w== dependencies: regenerator-runtime "^0.14.0" -"@babel/template@^7.22.15", "@babel/template@^7.24.7": +"@babel/template@^7.22.15": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.7.tgz#02efcee317d0609d2c07117cb70ef8fb17ab7315" integrity sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig== @@ -1268,6 +1299,15 @@ "@babel/parser" "^7.24.7" "@babel/types" "^7.24.7" +"@babel/template@^7.24.7", "@babel/template@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.0.tgz#e733dc3134b4fede528c15bc95e89cb98c52592a" + integrity sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q== + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/parser" "^7.25.0" + "@babel/types" "^7.25.0" + "@babel/template@^7.3.3": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" @@ -1309,19 +1349,16 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/traverse@^7.24.7", "@babel/traverse@^7.24.8": - version "7.24.8" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.8.tgz#6c14ed5232b7549df3371d820fbd9abfcd7dfab7" - integrity sha512-t0P1xxAPzEDcEPmjprAQq19NWum4K0EQPjMwZQZbHt+GiZqvjCHjj755Weq1YRPVzBI+3zSfvScfpnuIecVFJQ== +"@babel/traverse@^7.24.7", "@babel/traverse@^7.24.8", "@babel/traverse@^7.25.0", "@babel/traverse@^7.25.1", "@babel/traverse@^7.25.2", "@babel/traverse@^7.25.3", "@babel/traverse@^7.25.4": + version "7.25.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.4.tgz#648678046990f2957407e3086e97044f13c3e18e" + integrity sha512-VJ4XsrD+nOvlXyLzmLzUs/0qjFS4sK30te5yEFlvbbUNEgKaVb2BHZUpAL+ttLPQAHNrsI3zZisbfha5Cvr8vg== dependencies: "@babel/code-frame" "^7.24.7" - "@babel/generator" "^7.24.8" - "@babel/helper-environment-visitor" "^7.24.7" - "@babel/helper-function-name" "^7.24.7" - "@babel/helper-hoist-variables" "^7.24.7" - "@babel/helper-split-export-declaration" "^7.24.7" - "@babel/parser" "^7.24.8" - "@babel/types" "^7.24.8" + "@babel/generator" "^7.25.4" + "@babel/parser" "^7.25.4" + "@babel/template" "^7.25.0" + "@babel/types" "^7.25.4" debug "^4.3.1" globals "^11.1.0" @@ -1352,10 +1389,10 @@ "@babel/helper-validator-identifier" "^7.24.7" to-fast-properties "^2.0.0" -"@babel/types@^7.24.7", "@babel/types@^7.24.8", "@babel/types@^7.24.9", "@babel/types@^7.4.4": - version "7.24.9" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.9.tgz#228ce953d7b0d16646e755acf204f4cf3d08cc73" - integrity sha512-xm8XrMKz0IlUdocVbYJe0Z9xEgidU7msskG8BbhnTPK/HZ2z/7FP7ykqPgrUH+C+r414mNfNWam1f2vqOjqjYQ== +"@babel/types@^7.24.7", "@babel/types@^7.24.8", "@babel/types@^7.24.9", "@babel/types@^7.25.0", "@babel/types@^7.25.2", "@babel/types@^7.25.4", "@babel/types@^7.4.4": + version "7.25.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.4.tgz#6bcb46c72fdf1012a209d016c07f769e10adcb5f" + integrity sha512-zQ1ijeeCXVEh+aNL0RlmkPkG8HUiDcU2pzQQFjtbntgAczRASFzj4H+6+bV+dy1ntKR14I/DypeuRG1uma98iQ== dependencies: "@babel/helper-string-parser" "^7.24.8" "@babel/helper-validator-identifier" "^7.24.7" @@ -3445,7 +3482,7 @@ babel-plugin-polyfill-corejs2@^0.4.10: "@babel/helper-define-polyfill-provider" "^0.6.2" semver "^6.3.1" -babel-plugin-polyfill-corejs3@^0.10.1, babel-plugin-polyfill-corejs3@^0.10.4: +babel-plugin-polyfill-corejs3@^0.10.1: version "0.10.4" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz#789ac82405ad664c20476d0233b485281deb9c77" integrity sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg== @@ -3453,6 +3490,14 @@ babel-plugin-polyfill-corejs3@^0.10.1, babel-plugin-polyfill-corejs3@^0.10.4: "@babel/helper-define-polyfill-provider" "^0.6.1" core-js-compat "^3.36.1" +babel-plugin-polyfill-corejs3@^0.10.6: + version "0.10.6" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz#2deda57caef50f59c525aeb4964d3b2f867710c7" + integrity sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.2" + core-js-compat "^3.38.0" + babel-plugin-polyfill-regenerator@^0.6.1: version "0.6.2" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz#addc47e240edd1da1058ebda03021f382bba785e" @@ -3581,7 +3626,7 @@ braces@^3.0.3, braces@~3.0.2: dependencies: fill-range "^7.1.1" -browserslist@^4.22.2, browserslist@^4.23.0, browserslist@^4.23.1, browserslist@^4.23.2: +browserslist@^4.22.2, browserslist@^4.23.2: version "4.23.2" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.2.tgz#244fe803641f1c19c28c48c4b6ec9736eb3d32ed" integrity sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA== @@ -3591,6 +3636,16 @@ browserslist@^4.22.2, browserslist@^4.23.0, browserslist@^4.23.1, browserslist@^ node-releases "^2.0.14" update-browserslist-db "^1.1.0" +browserslist@^4.23.0, browserslist@^4.23.1, browserslist@^4.23.3: + version "4.23.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.3.tgz#debb029d3c93ebc97ffbc8d9cbb03403e227c800" + integrity sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA== + dependencies: + caniuse-lite "^1.0.30001646" + electron-to-chromium "^1.5.4" + node-releases "^2.0.18" + update-browserslist-db "^1.1.0" + bs58@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/bs58/-/bs58-6.0.0.tgz#a2cda0130558535dd281a2f8697df79caaf425d8" @@ -3646,7 +3701,7 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@1.0.30001643, caniuse-lite@^1.0.30001640: +caniuse-lite@1.0.30001643, caniuse-lite@^1.0.30001640, caniuse-lite@^1.0.30001646: version "1.0.30001643" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz#9c004caef315de9452ab970c3da71085f8241dbd" integrity sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg== @@ -3898,13 +3953,20 @@ cookie@0.6.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== -core-js-compat@^3.36.1, core-js-compat@^3.37.0, core-js-compat@^3.37.1: +core-js-compat@^3.36.1, core-js-compat@^3.37.0: version "3.37.1" resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.37.1.tgz#c844310c7852f4bdf49b8d339730b97e17ff09ee" integrity sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg== dependencies: browserslist "^4.23.0" +core-js-compat@^3.37.1, core-js-compat@^3.38.0: + version "3.38.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.38.1.tgz#2bc7a298746ca5a7bcb9c164bcb120f2ebc09a09" + integrity sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw== + dependencies: + browserslist "^4.23.3" + core-js@^3.0.0: version "3.30.0" resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.30.0.tgz#64ac6f83bc7a49fd42807327051701d4b1478dea" @@ -4107,20 +4169,20 @@ debug@^3.2.7: dependencies: ms "^2.1.1" -debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4, debug@^4.3.5: - version "4.3.5" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" - integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== - dependencies: - ms "2.1.2" - -debug@~4.3.4: +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@~4.3.4: version "4.3.6" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.6.tgz#2ab2c38fbaffebf8aa95fdfe6d88438c7a13c52b" integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg== dependencies: ms "2.1.2" +debug@^4.3.4, debug@^4.3.5: + version "4.3.5" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" + integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== + dependencies: + ms "2.1.2" + decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -4334,7 +4396,7 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -electron-to-chromium@1.5.2, electron-to-chromium@^1.4.820, electron-to-chromium@^1.5.2: +electron-to-chromium@1.5.2, electron-to-chromium@^1.4.820, electron-to-chromium@^1.5.2, electron-to-chromium@^1.5.4: version "1.5.2" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.2.tgz#6126ad229ce45e781ec54ca40db0504787f23d19" integrity sha512-kc4r3U3V3WLaaZqThjYz/Y6z8tJe+7K0bbjUVo3i+LWIypVdMx5nXCkwRe6SWbY6ILqLdc1rKcKmr3HoH7wjSQ== @@ -5791,9 +5853,9 @@ is-core-module@^2.11.0: has "^1.0.3" is-core-module@^2.13.0: - version "2.15.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.0.tgz#71c72ec5442ace7e76b306e9d48db361f22699ea" - integrity sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA== + version "2.15.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.1.tgz#a7363a25bee942fefab0de13bf6aa372c82dcc37" + integrity sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ== dependencies: hasown "^2.0.2" @@ -7199,7 +7261,7 @@ node-int64@^0.4.0: resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== -node-releases@^2.0.14: +node-releases@^2.0.14, node-releases@^2.0.18: version "2.0.18" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== From 5a9d7ba2d756f635f23e1aa058aa79a59c55fa26 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Date: Fri, 23 Aug 2024 15:00:18 +0100 Subject: [PATCH 016/154] Remove unused CryptoCallbacks implementations (#12919) * Remove unused `onSecretRequested` callback This thing is unused with the rust crypto stack (which is lucky, because it uses methods that only work with the legacy stack). * Remove unused `getDehydrationKey` method This callback is no longer used, so there is no need for an implementation. * Remove unused `dehydrationCache` This is no longer written to, so is redundant. * Remove another write to `CryptoCallbacks.getDehydrationKey` As before: this hook is no longer used by the js-sdk, so writing to it is pointless. --- src/MatrixClientPeg.ts | 6 -- src/SecurityManager.ts | 106 +---------------------------------- test/MatrixClientPeg-test.ts | 77 ------------------------- 3 files changed, 1 insertion(+), 188 deletions(-) diff --git a/src/MatrixClientPeg.ts b/src/MatrixClientPeg.ts index 646f9eec629..775897bb1c6 100644 --- a/src/MatrixClientPeg.ts +++ b/src/MatrixClientPeg.ts @@ -41,7 +41,6 @@ import MatrixClientBackedSettingsHandler from "./settings/handlers/MatrixClientB import * as StorageManager from "./utils/StorageManager"; import IdentityAuthClient from "./IdentityAuthClient"; import { crossSigningCallbacks } from "./SecurityManager"; -import { ModuleRunner } from "./modules/ModuleRunner"; import { SlidingSyncManager } from "./SlidingSyncManager"; import { _t, UserFriendlyError } from "./languageHandler"; import { SettingLevel } from "./settings/SettingLevel"; @@ -452,11 +451,6 @@ class MatrixClientPegClass implements IMatrixClientPeg { }, }; - const dehydrationKeyCallback = ModuleRunner.instance.extensions.cryptoSetup.getDehydrationKeyCallback(); - if (dehydrationKeyCallback) { - opts.cryptoCallbacks!.getDehydrationKey = dehydrationKeyCallback; - } - this.matrixClient = createMatrixClient(opts); this.matrixClient.setGuest(Boolean(creds.guest)); diff --git a/src/SecurityManager.ts b/src/SecurityManager.ts index c1c4e7a72b3..faf915e3c85 100644 --- a/src/SecurityManager.ts +++ b/src/SecurityManager.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { Crypto, ICryptoCallbacks, encodeBase64, SecretStorage } from "matrix-js-sdk/src/matrix"; +import { ICryptoCallbacks, SecretStorage } from "matrix-js-sdk/src/matrix"; import { deriveKey } from "matrix-js-sdk/src/crypto/key_passphrase"; import { decodeRecoveryKey } from "matrix-js-sdk/src/crypto/recoverykey"; import { logger } from "matrix-js-sdk/src/logger"; @@ -39,11 +39,6 @@ let secretStorageKeys: Record = {}; let secretStorageKeyInfo: Record = {}; let secretStorageBeingAccessed = false; -let dehydrationCache: { - key?: Uint8Array; - keyInfo?: SecretStorage.SecretStorageKeyDescription; -} = {}; - /** * This can be used by other components to check if secret storage access is in * progress, so that we can e.g. avoid intermittently showing toasts during @@ -119,14 +114,6 @@ async function getSecretStorageKey({ return [keyId, secretStorageKeys[keyId]]; } - if (dehydrationCache.key) { - if (await MatrixClientPeg.safeGet().checkSecretStorageKey(dehydrationCache.key, keyInfo)) { - logger.debug("getSecretStorageKey: returning key from dehydration cache"); - cacheSecretStorageKey(keyId, keyInfo, dehydrationCache.key); - return [keyId, dehydrationCache.key]; - } - } - const keyFromCustomisations = ModuleRunner.instance.extensions.cryptoSetup.getSecretStorageKey(); if (keyFromCustomisations) { logger.log("getSecretStorageKey: Using secret storage key from CryptoSetupExtension"); @@ -171,56 +158,6 @@ async function getSecretStorageKey({ return [keyId, key]; } -export async function getDehydrationKey( - keyInfo: SecretStorage.SecretStorageKeyDescription, - checkFunc: (data: Uint8Array) => void, -): Promise { - const keyFromCustomisations = ModuleRunner.instance.extensions.cryptoSetup.getSecretStorageKey(); - if (keyFromCustomisations) { - logger.log("CryptoSetupExtension: Using key from extension (dehydration)"); - return keyFromCustomisations; - } - - const inputToKey = makeInputToKey(keyInfo); - const { finished } = Modal.createDialog( - AccessSecretStorageDialog, - /* props= */ - { - keyInfo, - checkPrivateKey: async (input: KeyParams): Promise => { - const key = await inputToKey(input); - try { - checkFunc(key); - return true; - } catch (e) { - return false; - } - }, - }, - /* className= */ undefined, - /* isPriorityModal= */ false, - /* isStaticModal= */ false, - /* options= */ { - onBeforeClose: async (reason): Promise => { - if (reason === "backgroundClick") { - return confirmToDismiss(); - } - return true; - }, - }, - ); - const [input] = await finished; - if (!input) { - throw new AccessCancelledError(); - } - const key = await inputToKey(input); - - // need to copy the key because rehydration (unpickling) will clobber it - dehydrationCache = { key: new Uint8Array(key), keyInfo }; - - return key; -} - function cacheSecretStorageKey( keyId: string, keyInfo: SecretStorage.SecretStorageKeyDescription, @@ -232,50 +169,9 @@ function cacheSecretStorageKey( } } -async function onSecretRequested( - userId: string, - deviceId: string, - requestId: string, - name: string, - deviceTrust: Crypto.DeviceVerificationStatus, -): Promise { - logger.log("onSecretRequested", userId, deviceId, requestId, name, deviceTrust); - const client = MatrixClientPeg.safeGet(); - if (userId !== client.getUserId()) { - return; - } - if (!deviceTrust?.isVerified()) { - logger.log(`Ignoring secret request from untrusted device ${deviceId}`); - return; - } - if ( - name === "m.cross_signing.master" || - name === "m.cross_signing.self_signing" || - name === "m.cross_signing.user_signing" - ) { - const callbacks = client.getCrossSigningCacheCallbacks(); - if (!callbacks?.getCrossSigningKeyCache) return; - const keyId = name.replace("m.cross_signing.", ""); - const key = await callbacks.getCrossSigningKeyCache(keyId); - if (!key) { - logger.log(`${keyId} requested by ${deviceId}, but not found in cache`); - } - return key ? encodeBase64(key) : undefined; - } else if (name === "m.megolm_backup.v1") { - const key = await client.crypto?.getSessionBackupPrivateKey(); - if (!key) { - logger.log(`session backup key requested by ${deviceId}, but not found in cache`); - } - return key ? encodeBase64(key) : undefined; - } - logger.warn("onSecretRequested didn't recognise the secret named ", name); -} - export const crossSigningCallbacks: ICryptoCallbacks = { getSecretStorageKey, cacheSecretStorageKey, - onSecretRequested, - getDehydrationKey, }; /** diff --git a/test/MatrixClientPeg-test.ts b/test/MatrixClientPeg-test.ts index 121da2a154e..99e9195422d 100644 --- a/test/MatrixClientPeg-test.ts +++ b/test/MatrixClientPeg-test.ts @@ -16,16 +16,11 @@ limitations under the License. import { logger } from "matrix-js-sdk/src/logger"; import fetchMockJest from "fetch-mock-jest"; -import { - ProvideCryptoSetupExtensions, - SecretStorageKeyDescription, -} from "@matrix-org/react-sdk-module-api/lib/lifecycles/CryptoSetupExtensions"; import { advanceDateAndTime, stubClient } from "./test-utils"; import { IMatrixClientPeg, MatrixClientPeg as peg } from "../src/MatrixClientPeg"; import SettingsStore from "../src/settings/SettingsStore"; import { SettingLevel } from "../src/settings/SettingLevel"; -import { ModuleRunner } from "../src/modules/ModuleRunner"; jest.useFakeTimers(); @@ -78,78 +73,6 @@ describe("MatrixClientPeg", () => { expect(peg.userRegisteredWithinLastHours(24)).toBe(false); }); - describe(".start extensions", () => { - let testPeg: IMatrixClientPeg; - - beforeEach(() => { - // instantiate a MatrixClientPegClass instance, with a new MatrixClient - testPeg = new PegClass(); - fetchMockJest.get("http://example.com/_matrix/client/versions", {}); - }); - - describe("cryptoSetup extension", () => { - it("should call default cryptoSetup.getDehydrationKeyCallback", async () => { - const mockCryptoSetup = { - SHOW_ENCRYPTION_SETUP_UI: true, - examineLoginResponse: jest.fn(), - persistCredentials: jest.fn(), - getSecretStorageKey: jest.fn(), - createSecretStorageKey: jest.fn(), - catchAccessSecretStorageError: jest.fn(), - setupEncryptionNeeded: jest.fn(), - getDehydrationKeyCallback: jest.fn().mockReturnValue(null), - } as ProvideCryptoSetupExtensions; - - // Ensure we have an instance before we set up spies - const instance = ModuleRunner.instance; - jest.spyOn(instance.extensions, "cryptoSetup", "get").mockReturnValue(mockCryptoSetup); - - testPeg.replaceUsingCreds({ - accessToken: "SEKRET", - homeserverUrl: "http://example.com", - userId: "@user:example.com", - deviceId: "TEST_DEVICE_ID", - }); - - expect(mockCryptoSetup.getDehydrationKeyCallback).toHaveBeenCalledTimes(1); - }); - - it("should call overridden cryptoSetup.getDehydrationKeyCallback", async () => { - const mockDehydrationKeyCallback = () => Uint8Array.from([0x11, 0x22, 0x33]); - - const mockCryptoSetup = { - SHOW_ENCRYPTION_SETUP_UI: true, - examineLoginResponse: jest.fn(), - persistCredentials: jest.fn(), - getSecretStorageKey: jest.fn(), - createSecretStorageKey: jest.fn(), - catchAccessSecretStorageError: jest.fn(), - setupEncryptionNeeded: jest.fn(), - getDehydrationKeyCallback: jest.fn().mockReturnValue(mockDehydrationKeyCallback), - } as ProvideCryptoSetupExtensions; - - // Ensure we have an instance before we set up spies - const instance = ModuleRunner.instance; - jest.spyOn(instance.extensions, "cryptoSetup", "get").mockReturnValue(mockCryptoSetup); - - testPeg.replaceUsingCreds({ - accessToken: "SEKRET", - homeserverUrl: "http://example.com", - userId: "@user:example.com", - deviceId: "TEST_DEVICE_ID", - }); - expect(mockCryptoSetup.getDehydrationKeyCallback).toHaveBeenCalledTimes(1); - - const client = testPeg.get(); - const dehydrationKey = await client?.cryptoCallbacks.getDehydrationKey!( - {} as SecretStorageKeyDescription, - (key: Uint8Array) => true, - ); - expect(dehydrationKey).toEqual(Uint8Array.from([0x11, 0x22, 0x33])); - }); - }); - }); - describe(".start", () => { let testPeg: IMatrixClientPeg; From 8381e13338d74c93302b9b50ca8adb2a1390d235 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 23 Aug 2024 16:24:02 +0100 Subject: [PATCH 017/154] Update stylelint (#12922) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 123 +++++++++++++++++++++++++++++------------------------- 1 file changed, 65 insertions(+), 58 deletions(-) diff --git a/yarn.lock b/yarn.lock index ae6f9b9efc0..9859fcc5aaf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1419,25 +1419,25 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@csstools/css-parser-algorithms@^2.7.1": - version "2.7.1" - resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.7.1.tgz#6d93a8f7d8aeb7cd9ed0868f946e46f021b6aa70" - integrity sha512-2SJS42gxmACHgikc1WGesXLIT8d/q2l0UFM7TaEeIzdFCE/FPMtTiizcPGGJtlPo2xuQzY09OhrLTzRxqJqwGw== +"@csstools/css-parser-algorithms@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.1.tgz#f14ade63bae5f6025ac85c7d03fe47a7ca0e58af" + integrity sha512-lSquqZCHxDfuTg/Sk2hiS0mcSFCEBuj49JfzPHJogDBT0mGCyY5A1AQzBWngitrp7i1/HAZpIgzF/VjhOEIJIg== -"@csstools/css-tokenizer@^2.4.1": - version "2.4.1" - resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-2.4.1.tgz#1d8b2e200197cf5f35ceb07ca2dade31f3a00ae8" - integrity sha512-eQ9DIktFJBhGjioABJRtUucoWR2mwllurfnM8LuNGAqX3ViZXaUchqk+1s7jjtkFiT9ySdACsFEA3etErkALUg== +"@csstools/css-tokenizer@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-3.0.1.tgz#9dd9b10084f3011290f96789598091e5bcb3c29a" + integrity sha512-UBqaiu7kU0lfvaP982/o3khfXccVlHPWp0/vwwiIgDF0GmqqqxoiXC/6FCjlS9u92f7CoEz6nXKQnrn1kIAkOw== -"@csstools/media-query-list-parser@^2.1.13": - version "2.1.13" - resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.13.tgz#f00be93f6bede07c14ddf51a168ad2748e4fe9e5" - integrity sha512-XaHr+16KRU9Gf8XLi3q8kDlI18d5vzKSKCY510Vrtc9iNR0NJzbY9hhTmwhzYZj/ZwGL4VmB3TA9hJW0Um2qFA== +"@csstools/media-query-list-parser@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-3.0.1.tgz#9474e08e6d7767cf68c56bf1581b59d203360cb0" + integrity sha512-HNo8gGD02kHmcbX6PvCoUuOQvn4szyB9ca63vZHKX5A81QytgDG4oxG4IaEfHTlEZSZ6MjPEMWIVU+zF2PZcgw== -"@csstools/selector-specificity@^3.1.1": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-3.1.1.tgz#63085d2995ca0f0e55aa8b8a07d69bfd48b844fe" - integrity sha512-a7cxGcJ2wIlMFLlh8z2ONm+715QkPHiyJcxwQlKOz/03GPw1COpfhcmC9wm4xlZfp//jWHNNMwzjtqHXVWU9KA== +"@csstools/selector-specificity@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-4.0.0.tgz#7dfccb9df5499e627e7bfdbb4021a06813a45dba" + integrity sha512-189nelqtPd8++phaHNwYovKZI0FOzH1vQEE3QhHHkNIGrg5fSs9CbYP3RvfEH5geztnIA9Jwq91wyOIwAW5JIQ== "@dual-bundle/import-meta-resolve@^4.1.0": version "4.1.0" @@ -4047,7 +4047,7 @@ css-functions-list@^3.2.2: resolved "https://registry.yarnpkg.com/css-functions-list/-/css-functions-list-3.2.2.tgz#9a54c6dd8416ed25c1079cd88234e927526c1922" integrity sha512-c+N0v6wbKVxTu5gOBBFkr9BEdBWaqqjQeiJ8QvSRIJOf+UxlJh930m8e6/WNeODIK0mYLFkoONrnj16i2EcvfQ== -css-tree@^2.3.1: +css-tree@2.3.1, css-tree@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.3.1.tgz#10264ce1e5442e8572fc82fbe490644ff54b5c20" integrity sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw== @@ -4169,14 +4169,14 @@ debug@^3.2.7: dependencies: ms "^2.1.1" -debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@~4.3.4: +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.6, debug@~4.3.4: version "4.3.6" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.6.tgz#2ab2c38fbaffebf8aa95fdfe6d88438c7a13c52b" integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg== dependencies: ms "2.1.2" -debug@^4.3.4, debug@^4.3.5: +debug@^4.3.4: version "4.3.5" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== @@ -5688,7 +5688,12 @@ ieee754@^1.1.12: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -ignore@^5.2.0, ignore@^5.3.1: +ignore@^5.2.0, ignore@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +ignore@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== @@ -5962,6 +5967,11 @@ is-path-inside@^3.0.3: resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== +is-plain-object@5.0.0, is-plain-object@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" + integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== + is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" @@ -5969,11 +5979,6 @@ is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" -is-plain-object@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" - integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== - is-potential-custom-element-name@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" @@ -7673,10 +7678,10 @@ postcss-media-query-parser@^0.2.3: resolved "https://registry.yarnpkg.com/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz#27b39c6f4d94f81b1a73b8f76351c609e5cef244" integrity sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig== -postcss-resolve-nested-selector@^0.1.1: - version "0.1.4" - resolved "https://registry.yarnpkg.com/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.4.tgz#0068767902fb40f0e6cd7b24faee4fa4bc14a5da" - integrity sha512-R6vHqZWgVnTAPq0C+xjyHfEZqfIYboCBVSy24MjxEDm+tIh1BU4O6o7DP7AA7kHzf136d+Qc5duI4tlpHjixDw== +postcss-resolve-nested-selector@^0.1.4, postcss-resolve-nested-selector@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.6.tgz#3d84dec809f34de020372c41b039956966896686" + integrity sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw== postcss-safe-parser@^7.0.0: version "7.0.0" @@ -7688,10 +7693,10 @@ postcss-scss@^4.0.4: resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-4.0.9.tgz#a03c773cd4c9623cb04ce142a52afcec74806685" integrity sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A== -postcss-selector-parser@^6.1.0: - version "6.1.1" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.1.tgz#5be94b277b8955904476a2400260002ce6c56e38" - integrity sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg== +postcss-selector-parser@^6.1.1, postcss-selector-parser@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz#27ecb41fb0e3b6ba7a1ec84fff347f734c7929de" + integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg== dependencies: cssesc "^3.0.0" util-deprecate "^1.0.2" @@ -7710,10 +7715,10 @@ postcss@^8.3.11: picocolors "^1.0.0" source-map-js "^1.0.2" -postcss@^8.4.39: - version "8.4.39" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.39.tgz#aa3c94998b61d3a9c259efa51db4b392e1bde0e3" - integrity sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw== +postcss@^8.4.41: + version "8.4.41" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.41.tgz#d6104d3ba272d882fe18fc07d15dc2da62fa2681" + integrity sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ== dependencies: nanoid "^3.3.7" picocolors "^1.0.1" @@ -8837,32 +8842,34 @@ stylelint-config-standard@^36.0.0: stylelint-config-recommended "^14.0.1" stylelint-scss@^6.0.0: - version "6.4.1" - resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-6.4.1.tgz#78a197bbcdf9a61b7365769a9a42dddc722a24c5" - integrity sha512-+clI2bQC2FPOt06ZwUlXZZ95IO2C5bKTP0GLN1LNQPVvISfSNcgMKv/VTwym1mK9vnqhHbOk8lO4rj4nY7L9pw== + version "6.5.0" + resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-6.5.0.tgz#c5495f254195c41b97f9bc995e4d3725b375447a" + integrity sha512-yOnYlr71wrTPT3rYyUurgTj6Rw7JUtzsZQsiPEjvs+k/yqoYHdweqpw6XN/ARpxjAuvJpddoMUvV8aAIpvUwTg== dependencies: + css-tree "2.3.1" + is-plain-object "5.0.0" known-css-properties "^0.34.0" postcss-media-query-parser "^0.2.3" - postcss-resolve-nested-selector "^0.1.1" - postcss-selector-parser "^6.1.0" + postcss-resolve-nested-selector "^0.1.4" + postcss-selector-parser "^6.1.1" postcss-value-parser "^4.2.0" stylelint@^16.1.0: - version "16.7.0" - resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-16.7.0.tgz#5f6acf516aedecba7a6472ba0cc1ffc20e2be86b" - integrity sha512-Q1ATiXlz+wYr37a7TGsfvqYn2nSR3T/isw3IWlZQzFzCNoACHuGBb6xBplZXz56/uDRJHIygxjh7jbV/8isewA== - dependencies: - "@csstools/css-parser-algorithms" "^2.7.1" - "@csstools/css-tokenizer" "^2.4.1" - "@csstools/media-query-list-parser" "^2.1.13" - "@csstools/selector-specificity" "^3.1.1" + version "16.8.2" + resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-16.8.2.tgz#7fda18b919a36e206e897417d4720baceb3af122" + integrity sha512-fInKATippQhcSm7AB+T32GpI+626yohrg33GkFT/5jzliUw5qhlwZq2UQQwgl3HsHrf09oeARi0ZwgY/UWEv9A== + dependencies: + "@csstools/css-parser-algorithms" "^3.0.0" + "@csstools/css-tokenizer" "^3.0.0" + "@csstools/media-query-list-parser" "^3.0.0" + "@csstools/selector-specificity" "^4.0.0" "@dual-bundle/import-meta-resolve" "^4.1.0" balanced-match "^2.0.0" colord "^2.9.3" cosmiconfig "^9.0.0" css-functions-list "^3.2.2" css-tree "^2.3.1" - debug "^4.3.5" + debug "^4.3.6" fast-glob "^3.3.2" fastest-levenshtein "^1.0.16" file-entry-cache "^9.0.0" @@ -8870,7 +8877,7 @@ stylelint@^16.1.0: globby "^11.1.0" globjoin "^0.1.4" html-tags "^3.3.1" - ignore "^5.3.1" + ignore "^5.3.2" imurmurhash "^0.1.4" is-plain-object "^5.0.0" known-css-properties "^0.34.0" @@ -8879,10 +8886,10 @@ stylelint@^16.1.0: micromatch "^4.0.7" normalize-path "^3.0.0" picocolors "^1.0.1" - postcss "^8.4.39" - postcss-resolve-nested-selector "^0.1.1" + postcss "^8.4.41" + postcss-resolve-nested-selector "^0.1.6" postcss-safe-parser "^7.0.0" - postcss-selector-parser "^6.1.0" + postcss-selector-parser "^6.1.2" postcss-value-parser "^4.2.0" resolve-from "^5.0.0" string-width "^4.2.3" @@ -8921,9 +8928,9 @@ supports-color@^8.0.0: has-flag "^4.0.0" supports-hyperlinks@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-3.0.0.tgz#c711352a5c89070779b4dad54c05a2f14b15c94b" - integrity sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA== + version "3.1.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-3.1.0.tgz#b56150ff0173baacc15f21956450b61f2b18d3ac" + integrity sha512-2rn0BZ+/f7puLOHZm1HOJfwBggfaHXUpPUSSG/SWM4TWp5KCfmNYwnC3hruy2rZlMnmWZ+QAGpZfchu3f3695A== dependencies: has-flag "^4.0.0" supports-color "^7.0.0" From 1b70b22c27f3674a13cc213e7a10bacc2d747df6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 23 Aug 2024 21:11:56 +0100 Subject: [PATCH 018/154] Update typescript-eslint monorepo to v7.18.0 (#12924) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 114 ++++++++++++++++++++++++------------------------------ 1 file changed, 51 insertions(+), 63 deletions(-) diff --git a/yarn.lock b/yarn.lock index 9859fcc5aaf..69f508fa23a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2926,29 +2926,29 @@ "@types/yargs-parser" "*" "@typescript-eslint/eslint-plugin@^7.0.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.17.0.tgz#c8ed1af1ad2928ede5cdd207f7e3090499e1f77b" - integrity sha512-pyiDhEuLM3PuANxH7uNYan1AaFs5XE0zw1hq69JBvGvE7gSuEoQl1ydtEe/XQeoC3GQxLXyOVa5kNOATgM638A== + version "7.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz#b16d3cf3ee76bf572fdf511e79c248bdec619ea3" + integrity sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw== dependencies: "@eslint-community/regexpp" "^4.10.0" - "@typescript-eslint/scope-manager" "7.17.0" - "@typescript-eslint/type-utils" "7.17.0" - "@typescript-eslint/utils" "7.17.0" - "@typescript-eslint/visitor-keys" "7.17.0" + "@typescript-eslint/scope-manager" "7.18.0" + "@typescript-eslint/type-utils" "7.18.0" + "@typescript-eslint/utils" "7.18.0" + "@typescript-eslint/visitor-keys" "7.18.0" graphemer "^1.4.0" ignore "^5.3.1" natural-compare "^1.4.0" ts-api-utils "^1.3.0" "@typescript-eslint/parser@^7.0.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.17.0.tgz#be8e32c159190cd40a305a2121220eadea5a88e7" - integrity sha512-puiYfGeg5Ydop8eusb/Hy1k7QmOU6X3nvsqCgzrB2K4qMavK//21+PzNE8qeECgNOIoertJPUC1SpegHDI515A== - dependencies: - "@typescript-eslint/scope-manager" "7.17.0" - "@typescript-eslint/types" "7.17.0" - "@typescript-eslint/typescript-estree" "7.17.0" - "@typescript-eslint/visitor-keys" "7.17.0" + version "7.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.18.0.tgz#83928d0f1b7f4afa974098c64b5ce6f9051f96a0" + integrity sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg== + dependencies: + "@typescript-eslint/scope-manager" "7.18.0" + "@typescript-eslint/types" "7.18.0" + "@typescript-eslint/typescript-estree" "7.18.0" + "@typescript-eslint/visitor-keys" "7.18.0" debug "^4.3.4" "@typescript-eslint/scope-manager@7.13.0": @@ -2959,21 +2959,21 @@ "@typescript-eslint/types" "7.13.0" "@typescript-eslint/visitor-keys" "7.13.0" -"@typescript-eslint/scope-manager@7.17.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.17.0.tgz#e072d0f914662a7bfd6c058165e3c2b35ea26b9d" - integrity sha512-0P2jTTqyxWp9HiKLu/Vemr2Rg1Xb5B7uHItdVZ6iAenXmPo4SZ86yOPCJwMqpCyaMiEHTNqizHfsbmCFT1x9SA== +"@typescript-eslint/scope-manager@7.18.0": + version "7.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz#c928e7a9fc2c0b3ed92ab3112c614d6bd9951c83" + integrity sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA== dependencies: - "@typescript-eslint/types" "7.17.0" - "@typescript-eslint/visitor-keys" "7.17.0" + "@typescript-eslint/types" "7.18.0" + "@typescript-eslint/visitor-keys" "7.18.0" -"@typescript-eslint/type-utils@7.17.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.17.0.tgz#c5da78feb134c9c9978cbe89e2b1a589ed22091a" - integrity sha512-XD3aaBt+orgkM/7Cei0XNEm1vwUxQ958AOLALzPlbPqb8C1G8PZK85tND7Jpe69Wualri81PLU+Zc48GVKIMMA== +"@typescript-eslint/type-utils@7.18.0": + version "7.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz#2165ffaee00b1fbbdd2d40aa85232dab6998f53b" + integrity sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA== dependencies: - "@typescript-eslint/typescript-estree" "7.17.0" - "@typescript-eslint/utils" "7.17.0" + "@typescript-eslint/typescript-estree" "7.18.0" + "@typescript-eslint/utils" "7.18.0" debug "^4.3.4" ts-api-utils "^1.3.0" @@ -2982,10 +2982,10 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.13.0.tgz#0cca95edf1f1fdb0cfe1bb875e121b49617477c5" integrity sha512-QWuwm9wcGMAuTsxP+qz6LBBd3Uq8I5Nv8xb0mk54jmNoCyDspnMvVsOxI6IsMmway5d1S9Su2+sCKv1st2l6eA== -"@typescript-eslint/types@7.17.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.17.0.tgz#7ce8185bdf06bc3494e73d143dbf3293111b9cff" - integrity sha512-a29Ir0EbyKTKHnZWbNsrc/gqfIBqYPwj3F2M+jWE/9bqfEHg0AMtXzkbUkOG6QgEScxh2+Pz9OXe11jHDnHR7A== +"@typescript-eslint/types@7.18.0": + version "7.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.18.0.tgz#b90a57ccdea71797ffffa0321e744f379ec838c9" + integrity sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ== "@typescript-eslint/typescript-estree@7.13.0": version "7.13.0" @@ -3001,13 +3001,13 @@ semver "^7.6.0" ts-api-utils "^1.3.0" -"@typescript-eslint/typescript-estree@7.17.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.17.0.tgz#dcab3fea4c07482329dd6107d3c6480e228e4130" - integrity sha512-72I3TGq93t2GoSBWI093wmKo0n6/b7O4j9o8U+f65TVD0FS6bI2180X5eGEr8MA8PhKMvYe9myZJquUT2JkCZw== +"@typescript-eslint/typescript-estree@7.18.0": + version "7.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz#b5868d486c51ce8f312309ba79bdb9f331b37931" + integrity sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA== dependencies: - "@typescript-eslint/types" "7.17.0" - "@typescript-eslint/visitor-keys" "7.17.0" + "@typescript-eslint/types" "7.18.0" + "@typescript-eslint/visitor-keys" "7.18.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" @@ -3015,15 +3015,15 @@ semver "^7.6.0" ts-api-utils "^1.3.0" -"@typescript-eslint/utils@7.17.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.17.0.tgz#815cd85b9001845d41b699b0ce4f92d6dfb84902" - integrity sha512-r+JFlm5NdB+JXc7aWWZ3fKSm1gn0pkswEwIYsrGPdsT2GjsRATAKXiNtp3vgAAO1xZhX8alIOEQnNMl3kbTgJw== +"@typescript-eslint/utils@7.18.0": + version "7.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.18.0.tgz#bca01cde77f95fc6a8d5b0dbcbfb3d6ca4be451f" + integrity sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw== dependencies: "@eslint-community/eslint-utils" "^4.4.0" - "@typescript-eslint/scope-manager" "7.17.0" - "@typescript-eslint/types" "7.17.0" - "@typescript-eslint/typescript-estree" "7.17.0" + "@typescript-eslint/scope-manager" "7.18.0" + "@typescript-eslint/types" "7.18.0" + "@typescript-eslint/typescript-estree" "7.18.0" "@typescript-eslint/utils@^6.0.0 || ^7.0.0": version "7.13.0" @@ -3043,12 +3043,12 @@ "@typescript-eslint/types" "7.13.0" eslint-visitor-keys "^3.4.3" -"@typescript-eslint/visitor-keys@7.17.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.17.0.tgz#680465c734be30969e564b4647f38d6cdf49bfb0" - integrity sha512-RVGC9UhPOCsfCdI9pU++K4nD7to+jTcMIbXTSOcrLqUEW6gF2pU1UUbYJKc9cvcRSK1UDeMJ7pdMxf4bhMpV/A== +"@typescript-eslint/visitor-keys@7.18.0": + version "7.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz#0564629b6124d67607378d0f0332a0495b25e7d7" + integrity sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg== dependencies: - "@typescript-eslint/types" "7.17.0" + "@typescript-eslint/types" "7.18.0" eslint-visitor-keys "^3.4.3" "@ungap/structured-clone@^1.2.0": @@ -4169,20 +4169,13 @@ debug@^3.2.7: dependencies: ms "^2.1.1" -debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.6, debug@~4.3.4: +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4, debug@^4.3.6, debug@~4.3.4: version "4.3.6" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.6.tgz#2ab2c38fbaffebf8aa95fdfe6d88438c7a13c52b" integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg== dependencies: ms "2.1.2" -debug@^4.3.4: - version "4.3.5" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" - integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== - dependencies: - ms "2.1.2" - decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -5688,16 +5681,11 @@ ieee754@^1.1.12: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -ignore@^5.2.0, ignore@^5.3.2: +ignore@^5.2.0, ignore@^5.3.1, ignore@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== -ignore@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" - integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== - immediate@~3.0.5: version "3.0.6" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" From e599428b74f22d1ad4b65ba8fb143369f4a03ef5 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Tue, 27 Aug 2024 11:41:49 +0200 Subject: [PATCH 019/154] Ignore desktop for minimum browser support. (#12928) --- package.json | 4 +--- src/SupportedBrowser.ts | 15 ++++++--------- yarn.lock | 7 ++++++- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index a5479d9ec50..8ebdfa5b444 100644 --- a/package.json +++ b/package.json @@ -68,8 +68,7 @@ "jwt-decode": "4.0.0", "@floating-ui/react": "0.26.11", "@radix-ui/react-id": "1.1.0", - "caniuse-lite": "1.0.30001643", - "electron-to-chromium": "1.5.2" + "caniuse-lite": "1.0.30001643" }, "dependencies": { "@babel/runtime": "^7.12.5", @@ -95,7 +94,6 @@ "css-tree": "^2.3.1", "diff-dom": "^5.0.0", "diff-match-patch": "^1.0.5", - "electron-to-chromium": "^1.5.2", "emojibase-regex": "15.3.2", "escape-html": "^1.0.3", "file-saver": "^2.0.5", diff --git a/src/SupportedBrowser.ts b/src/SupportedBrowser.ts index 071a64f4fcb..debdee122ab 100644 --- a/src/SupportedBrowser.ts +++ b/src/SupportedBrowser.ts @@ -16,7 +16,6 @@ limitations under the License. import { logger } from "matrix-js-sdk/src/logger"; import browserlist from "browserslist"; -import electronToChromium from "electron-to-chromium/versions"; import PopOutIcon from "@vector-im/compound-design-tokens/assets/web/icons/pop-out"; import { DeviceType, parseUserAgent } from "./utils/device/parseUserAgent"; @@ -46,14 +45,6 @@ function onDismissClick(): void { function getBrowserNameVersion(browser: string): [name: string, version: number] { const [browserName, browserVersion] = browser.split(" "); const browserNameLc = browserName.toLowerCase(); - if (browserNameLc === "electron") { - // The electron-to-chromium map is keyed by the major and minor version of Electron - const chromiumVersion = electronToChromium[browserVersion.split(".").slice(0, 2).join(".")]; - if (chromiumVersion) { - return ["chrome", parseInt(chromiumVersion, 10)]; - } - } - return [browserNameLc, parseInt(browserVersion, 10)]; } @@ -80,6 +71,12 @@ export function getBrowserSupport(): boolean { } if (details.client) { + // We don't care about the browser version for desktop devices + // We ship our own browser (electron) for desktop devices + if (details.deviceType === DeviceType.Desktop) { + return supported; + } + const [browserName, browserVersion] = getBrowserNameVersion(details.client); const minimumVersion = minimumBrowserVersions.get(browserName); // Check both with the sub-version cut off and without as some browsers have less granular versioning e.g. Safari diff --git a/yarn.lock b/yarn.lock index 69f508fa23a..19b8529fa29 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4389,11 +4389,16 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -electron-to-chromium@1.5.2, electron-to-chromium@^1.4.820, electron-to-chromium@^1.5.2, electron-to-chromium@^1.5.4: +electron-to-chromium@^1.4.820: version "1.5.2" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.2.tgz#6126ad229ce45e781ec54ca40db0504787f23d19" integrity sha512-kc4r3U3V3WLaaZqThjYz/Y6z8tJe+7K0bbjUVo3i+LWIypVdMx5nXCkwRe6SWbY6ILqLdc1rKcKmr3HoH7wjSQ== +electron-to-chromium@^1.5.4: + version "1.5.13" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz#1abf0410c5344b2b829b7247e031f02810d442e6" + integrity sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q== + emittery@^0.13.1: version "0.13.1" resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" From f0a75d8ad57e3633712b61b2bb78cc9e9317f087 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 27 Aug 2024 10:59:54 +0100 Subject: [PATCH 020/154] Add a config option to control the default widget container height (#12893) * Add a config option to control the default widget container height * Oops: need to remember to git checkout --- src/IConfigOptions.ts | 1 + src/components/views/rooms/AppsDrawer.tsx | 3 +- .../views/rooms/AppsDrawer-test.tsx | 84 +++++++++++++++++++ 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 test/components/views/rooms/AppsDrawer-test.tsx diff --git a/src/IConfigOptions.ts b/src/IConfigOptions.ts index 69c83499651..c305b17ca50 100644 --- a/src/IConfigOptions.ts +++ b/src/IConfigOptions.ts @@ -98,6 +98,7 @@ export interface IConfigOptions { integrations_ui_url?: string; integrations_rest_url?: string; integrations_widgets_urls?: string[]; + default_widget_container_height?: number; // height in pixels show_labs_settings: boolean; features?: Record; // diff --git a/src/components/views/rooms/AppsDrawer.tsx b/src/components/views/rooms/AppsDrawer.tsx index 2247edce045..6da77499fa8 100644 --- a/src/components/views/rooms/AppsDrawer.tsx +++ b/src/components/views/rooms/AppsDrawer.tsx @@ -35,6 +35,7 @@ import { clamp, percentageOf, percentageWithin } from "../../../utils/numbers"; import UIStore from "../../../stores/UIStore"; import { ActionPayload } from "../../../dispatcher/payloads"; import Spinner from "../elements/Spinner"; +import SdkConfig from "../../../SdkConfig"; interface IProps { userId: string; @@ -335,7 +336,7 @@ const PersistentVResizer: React.FC = ({ defaultHeight = clamp(defaultHeight, 0, 100); defaultHeight = percentageWithin(defaultHeight / 100, minHeight, maxHeight); } else { - defaultHeight = 280; + defaultHeight = SdkConfig.get().default_widget_container_height ?? 280; } return ( diff --git a/test/components/views/rooms/AppsDrawer-test.tsx b/test/components/views/rooms/AppsDrawer-test.tsx new file mode 100644 index 00000000000..3da0f0560f0 --- /dev/null +++ b/test/components/views/rooms/AppsDrawer-test.tsx @@ -0,0 +1,84 @@ +/* +Copyright 2024 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 { MatrixClient, PendingEventOrdering, Room } from "matrix-js-sdk/src/matrix"; +import { render } from "@testing-library/react"; + +import { stubClient } from "../../../test-utils"; +import AppsDrawer from "../../../../src/components/views/rooms/AppsDrawer"; +import SdkConfig from "../../../../src/SdkConfig"; +import ResizeNotifier from "../../../../src/utils/ResizeNotifier"; +import { WidgetLayoutStore } from "../../../../src/stores/widgets/WidgetLayoutStore"; +import MatrixClientContext from "../../../../src/contexts/MatrixClientContext"; + +const ROOM_ID = "!room:id"; + +describe("AppsDrawer", () => { + let client: MatrixClient; + let room: Room; + let dummyResizeNotifier: ResizeNotifier; + + beforeEach(async () => { + client = stubClient(); + room = new Room(ROOM_ID, client, client.getUserId()!, { + pendingEventOrdering: PendingEventOrdering.Detached, + }); + dummyResizeNotifier = new ResizeNotifier(); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it("honours default_widget_container_height", () => { + jest.spyOn(SdkConfig, "get").mockImplementation((key) => { + if (!key) { + return { + default_widget_container_height: 500, + }; + } + }); + jest.spyOn(WidgetLayoutStore.instance, "getContainerWidgets").mockImplementation((room, container) => { + if (container === "top") { + return [ + { + id: "testwidget", + creatorUserId: client.getUserId()!, + type: "test", + url: "https://nowhere.dummy/notawidget", + }, + ]; + } + return []; + }); + + const { container } = render( + , + { + wrapper: ({ ...rest }) => , + }, + ); + + const appsDrawerResizer = container.getElementsByClassName("mx_AppsDrawer_resizer")[0] as HTMLElement; + expect(appsDrawerResizer.style.height).toBe("500px"); + }); +}); From 30f84cd5a9714dad9ca0e84625700864591c2e9b Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Tue, 27 Aug 2024 14:09:57 +0200 Subject: [PATCH 021/154] Update playwright image (#12930) --- playwright/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright/Dockerfile b/playwright/Dockerfile index 29e7f83f1fc..1a463c67811 100644 --- a/playwright/Dockerfile +++ b/playwright/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/playwright:v1.45.3-jammy +FROM mcr.microsoft.com/playwright:v1.46.1-jammy WORKDIR /work/matrix-react-sdk VOLUME ["/work/element-web/node_modules"] From 5b91dd88e1dee6b7a72f97cef77051663ece0894 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 27 Aug 2024 12:44:59 +0000 Subject: [PATCH 022/154] Reset matrix-js-sdk back to develop branch --- package.json | 5 ++--- yarn.lock | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 3ef0db05518..65e33b75a44 100644 --- a/package.json +++ b/package.json @@ -114,7 +114,7 @@ "maplibre-gl": "^2.0.0", "matrix-encrypt-attachment": "^1.0.3", "matrix-events-sdk": "0.0.1", - "matrix-js-sdk": "34.4.0", + "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", "matrix-widget-api": "^1.8.2", "memoize-one": "^6.0.0", "minimist": "^1.2.5", @@ -246,6 +246,5 @@ "outputDirectory": "coverage", "outputName": "jest-sonar-report.xml", "relativePaths": true - }, - "typings": "./lib/index.d.ts" + } } diff --git a/yarn.lock b/yarn.lock index 333a36ea1f4..561e35a5e14 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7005,10 +7005,9 @@ matrix-events-sdk@0.0.1: resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd" integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA== -matrix-js-sdk@34.4.0: +"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": version "34.4.0" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-34.4.0.tgz#ceb3403c92dbff3b37e776745a2997ee78fa1eac" - integrity sha512-bI5xJZS3/qhjPQqQL5HhOQ1iBvnHxiqhS2zgzk9SarEuXiH08wbVl9gAAuDqOYE3miNGs4WQQJ19MoaUEOnNwg== + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/2a6612c73aa7ae8d7f10f426239b2850c86a1ea5" dependencies: "@babel/runtime" "^7.12.5" "@matrix-org/matrix-sdk-crypto-wasm" "^7.0.0" From 8421022841185940c534a3ad2c8054237cdcfbc7 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 27 Aug 2024 15:46:50 +0100 Subject: [PATCH 023/154] Rename all the slow reporter stuff to cjs (#12933) To hopefully fix tests on develop --- jest.config.ts | 2 +- test/{slowReporter.js => slowReporter.cjs} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename test/{slowReporter.js => slowReporter.cjs} (89%) diff --git a/jest.config.ts b/jest.config.ts index 7293e5b3be9..3203fb20251 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -53,7 +53,7 @@ if (env["GITHUB_ACTIONS"] !== undefined) { // if we're running against the develop branch, also enable the slow test reporter if (env["GITHUB_REF"] == "refs/heads/develop") { - reporters.push("/test/slowReporter.js"); + reporters.push("/test/slowReporter.cjs"); } config.reporters = reporters; } diff --git a/test/slowReporter.js b/test/slowReporter.cjs similarity index 89% rename from test/slowReporter.js rename to test/slowReporter.cjs index f63f218c36d..455b9ba025d 100644 --- a/test/slowReporter.js +++ b/test/slowReporter.cjs @@ -14,4 +14,4 @@ See the License for the specific language governing permissions and limitations under the License. */ -module.exports = require("matrix-js-sdk/spec/slowReporter"); +module.exports = require("matrix-js-sdk/spec/slowReporter.cjs"); From 71c31bbed88f821023826944d262be20543774aa Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Wed, 28 Aug 2024 01:22:32 -0500 Subject: [PATCH 024/154] [create-pull-request] automated change (#12926) Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com> --- playwright/plugins/homeserver/synapse/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright/plugins/homeserver/synapse/index.ts b/playwright/plugins/homeserver/synapse/index.ts index 4da7c8a3261..1340304ade8 100644 --- a/playwright/plugins/homeserver/synapse/index.ts +++ b/playwright/plugins/homeserver/synapse/index.ts @@ -28,7 +28,7 @@ import { randB64Bytes } from "../../utils/rand"; // Docker tag to use for synapse docker image. // We target a specific digest as every now and then a Synapse update will break our CI. // This digest is updated by the playwright-image-updates.yaml workflow periodically. -const DOCKER_TAG = "develop@sha256:4c891449943a2e7413a47784f3319caabfa185ad2caf96959f2175df34047917"; +const DOCKER_TAG = "develop@sha256:2144d1c36cee53f82b0636640c944d6c024c05ec8a302c6268ec252e12ea2e99"; async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise> { const templateDir = path.join(__dirname, "templates", opts.template); From 43941efbdb4f8e5785bbf04ad1801dc8e08fff1e Mon Sep 17 00:00:00 2001 From: R Midhun Suresh Date: Wed, 28 Aug 2024 12:17:56 +0530 Subject: [PATCH 025/154] Install deja-vu font in docker image (#12932) * Install deja-vu font * Add comment --- playwright/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/playwright/Dockerfile b/playwright/Dockerfile index 1a463c67811..f054d410028 100644 --- a/playwright/Dockerfile +++ b/playwright/Dockerfile @@ -3,7 +3,8 @@ FROM mcr.microsoft.com/playwright:v1.46.1-jammy WORKDIR /work/matrix-react-sdk VOLUME ["/work/element-web/node_modules"] -RUN apt-get update && apt-get -y install docker.io +# fonts-dejavu is needed for the same RTL rendering as on CI +RUN apt-get update && apt-get -y install docker.io fonts-dejavu COPY docker-entrypoint.sh /opt/docker-entrypoint.sh ENTRYPOINT ["bash", "/opt/docker-entrypoint.sh"] From ea3c5cf7870f66949605850f0be3f18f8385e1ea Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Wed, 28 Aug 2024 10:56:46 +0200 Subject: [PATCH 026/154] Fix pin/unpin slowness and non refresh from the message action bar (#12934) * Improve PinningUtils.ts doc and use common methods to check pin or unpin. Removed unused methods. * Send room account data and state event in parallel * Rerender MessageActionBar.tsx if there is a room pinned event * Update pinning util tests * Add test for room pinned events in MessageActionBar-test.tsx --- .../views/messages/MessageActionBar.tsx | 17 ++++++++++++ src/utils/EventUtils.ts | 4 --- src/utils/PinningUtils.ts | 19 +++++++++----- .../views/messages/MessageActionBar-test.tsx | 26 ++++++++++++++++++- test/utils/PinningUtils-test.ts | 7 ++--- 5 files changed, 57 insertions(+), 16 deletions(-) diff --git a/src/components/views/messages/MessageActionBar.tsx b/src/components/views/messages/MessageActionBar.tsx index eebcb96b1e2..9130183e84e 100644 --- a/src/components/views/messages/MessageActionBar.tsx +++ b/src/components/views/messages/MessageActionBar.tsx @@ -24,6 +24,9 @@ import { MsgType, RelationType, M_BEACON_INFO, + EventTimeline, + RoomStateEvent, + EventType, } from "matrix-js-sdk/src/matrix"; import classNames from "classnames"; import { Icon as PinIcon } from "@vector-im/compound-design-tokens/icons/pin.svg"; @@ -278,12 +281,20 @@ export default class MessageActionBar extends React.PureComponent { @@ -297,6 +308,12 @@ export default class MessageActionBar extends React.PureComponent { + // If the event is pinned or unpinned, rerender the component. + if (!event || event.getType() !== EventType.RoomPinnedEvents) return; + this.forceUpdate(); + }; + private onSent = (): void => { // When an event is sent and echoed the possible actions change. this.forceUpdate(); diff --git a/src/utils/EventUtils.ts b/src/utils/EventUtils.ts index 373bcf49a7b..dc5c89379b8 100644 --- a/src/utils/EventUtils.ts +++ b/src/utils/EventUtils.ts @@ -286,10 +286,6 @@ export function hasThreadSummary(event: MatrixEvent): boolean { return event.isThreadRoot && !!event.getThread()?.length && !!event.getThread()!.replyToEvent; } -export function canPinEvent(event: MatrixEvent): boolean { - return !M_BEACON_INFO.matches(event.getType()); -} - export const highlightEvent = (roomId: string, eventId: string): void => { defaultDispatcher.dispatch({ action: Action.ViewRoom, diff --git a/src/utils/PinningUtils.ts b/src/utils/PinningUtils.ts index 0d3323d50be..22db64a6f17 100644 --- a/src/utils/PinningUtils.ts +++ b/src/utils/PinningUtils.ts @@ -16,7 +16,7 @@ limitations under the License. import { MatrixEvent, EventType, M_POLL_START, MatrixClient, EventTimeline } from "matrix-js-sdk/src/matrix"; -import { canPinEvent, isContentActionable } from "./EventUtils"; +import { isContentActionable } from "./EventUtils"; import SettingsStore from "../settings/SettingsStore"; import { ReadPinsEventId } from "../components/views/right_panel/types"; @@ -31,7 +31,8 @@ export default class PinningUtils { ]; /** - * Determines if the given event may be pinned. + * Determines if the given event can be pinned. + * This is a simple check to see if the event is of a type that can be pinned. * @param {MatrixEvent} event The event to check. * @return {boolean} True if the event may be pinned, false otherwise. */ @@ -62,7 +63,8 @@ export default class PinningUtils { } /** - * Determines if the given event may be pinned or unpinned. + * Determines if the given event may be pinned or unpinned by the current user. + * This checks if the user has the necessary permissions to pin or unpin the event, and if the event is pinnable. * @param matrixClient * @param mxEvent */ @@ -77,7 +79,7 @@ export default class PinningUtils { room .getLiveTimeline() .getState(EventTimeline.FORWARDS) - ?.mayClientSendStateEvent(EventType.RoomPinnedEvents, matrixClient) && canPinEvent(mxEvent), + ?.mayClientSendStateEvent(EventType.RoomPinnedEvents, matrixClient) && PinningUtils.isPinnable(mxEvent), ); } @@ -101,16 +103,21 @@ export default class PinningUtils { ?.getStateEvents(EventType.RoomPinnedEvents, "") ?.getContent().pinned || []; + let roomAccountDataPromise: Promise<{} | void> = Promise.resolve(); // If the event is already pinned, unpin it if (pinnedIds.includes(eventId)) { pinnedIds.splice(pinnedIds.indexOf(eventId), 1); } else { // Otherwise, pin it pinnedIds.push(eventId); - await matrixClient.setRoomAccountData(room.roomId, ReadPinsEventId, { + // We don't want to wait for the roomAccountDataPromise to resolve before sending the state event + roomAccountDataPromise = matrixClient.setRoomAccountData(room.roomId, ReadPinsEventId, { event_ids: [...(room.getAccountData(ReadPinsEventId)?.getContent()?.event_ids || []), eventId], }); } - await matrixClient.sendStateEvent(room.roomId, EventType.RoomPinnedEvents, { pinned: pinnedIds }, ""); + await Promise.all([ + matrixClient.sendStateEvent(room.roomId, EventType.RoomPinnedEvents, { pinned: pinnedIds }, ""), + roomAccountDataPromise, + ]); } } diff --git a/test/components/views/messages/MessageActionBar-test.tsx b/test/components/views/messages/MessageActionBar-test.tsx index ad184314a25..9b5da31802f 100644 --- a/test/components/views/messages/MessageActionBar-test.tsx +++ b/test/components/views/messages/MessageActionBar-test.tsx @@ -15,7 +15,7 @@ limitations under the License. */ import React from "react"; -import { act, render, fireEvent } from "@testing-library/react"; +import { act, render, fireEvent, screen, waitFor } from "@testing-library/react"; import { EventType, EventStatus, @@ -26,6 +26,7 @@ import { FeatureSupport, Thread, EventTimeline, + RoomStateEvent, } from "matrix-js-sdk/src/matrix"; import MessageActionBar from "../../../../src/components/views/messages/MessageActionBar"; @@ -41,6 +42,7 @@ import { IRoomState } from "../../../../src/components/structures/RoomView"; import dispatcher from "../../../../src/dispatcher/dispatcher"; import SettingsStore from "../../../../src/settings/SettingsStore"; import { Action } from "../../../../src/dispatcher/actions"; +import PinningUtils from "../../../../src/utils/PinningUtils"; jest.mock("../../../../src/dispatcher/dispatcher"); @@ -119,6 +121,7 @@ describe("", () => { timelineRenderingType: TimelineRenderingType.Room, canSendMessages: true, canReact: true, + room, } as unknown as IRoomState; const getComponent = (props = {}, roomContext: Partial = {}) => render( @@ -476,6 +479,7 @@ describe("", () => { beforeEach(() => { // enable pin button jest.spyOn(SettingsStore, "getValue").mockReturnValue(true); + jest.spyOn(PinningUtils, "isPinned").mockReturnValue(false); }); afterEach(() => { @@ -499,5 +503,25 @@ describe("", () => { const { queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent }); expect(queryByLabelText("Pin")).toBeTruthy(); }); + + it("should listen to room pinned events", async () => { + getComponent({ mxEvent: alicesMessageEvent }); + expect(screen.getByLabelText("Pin")).toBeInTheDocument(); + + // Event is considered pinned + jest.spyOn(PinningUtils, "isPinned").mockReturnValue(true); + // Emit that the room pinned events have changed + const roomState = room.getLiveTimeline().getState(EventTimeline.FORWARDS)!; + roomState.emit( + RoomStateEvent.Events, + { + getType: () => EventType.RoomPinnedEvents, + } as MatrixEvent, + roomState, + null, + ); + + await waitFor(() => expect(screen.getByLabelText("Unpin")).toBeInTheDocument()); + }); }); }); diff --git a/test/utils/PinningUtils-test.ts b/test/utils/PinningUtils-test.ts index 07b26adb8a9..47434c4fca3 100644 --- a/test/utils/PinningUtils-test.ts +++ b/test/utils/PinningUtils-test.ts @@ -20,7 +20,7 @@ import { mocked } from "jest-mock"; import { createTestClient } from "../test-utils"; import PinningUtils from "../../src/utils/PinningUtils"; import SettingsStore from "../../src/settings/SettingsStore"; -import { canPinEvent, isContentActionable } from "../../src/utils/EventUtils"; +import { isContentActionable } from "../../src/utils/EventUtils"; import { ReadPinsEventId } from "../../src/components/views/right_panel/types"; jest.mock("../../src/utils/EventUtils", () => { @@ -35,7 +35,6 @@ describe("PinningUtils", () => { const userId = "@alice:example.org"; const mockedIsContentActionable = mocked(isContentActionable); - const mockedCanPinEvent = mocked(canPinEvent); let matrixClient: MatrixClient; let room: Room; @@ -63,7 +62,6 @@ describe("PinningUtils", () => { // Enable feature pinning jest.spyOn(SettingsStore, "getValue").mockReturnValue(true); mockedIsContentActionable.mockImplementation(() => true); - mockedCanPinEvent.mockImplementation(() => true); matrixClient = createTestClient(); room = new Room(roomId, matrixClient, userId); @@ -171,8 +169,7 @@ describe("PinningUtils", () => { }); test("should return false if event is not pinnable", () => { - mockedCanPinEvent.mockReturnValue(false); - const event = makePinEvent(); + const event = makePinEvent({ type: EventType.RoomCreate }); expect(PinningUtils.canPinOrUnpin(matrixClient, event)).toBe(false); }); From c6922c912e0b86e7df0a9d35e581caeb91b14447 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Wed, 28 Aug 2024 11:45:07 +0200 Subject: [PATCH 027/154] Fix reply message truncation on 2 lines (#12929) * Fix reply message truncation on 2 lines * Add e2e tests for reply --- playwright/e2e/messages/messages.spec.ts | 61 +++++++++++++++++- ...reply-message-ltr-ltrdisplayname-linux.png | Bin 0 -> 6508 bytes ...reply-message-ltr-rtldisplayname-linux.png | Bin 0 -> 6548 bytes ...reply-message-trl-ltrdisplayname-linux.png | Bin 0 -> 6005 bytes ...reply-message-trl-rtldisplayname-linux.png | Bin 0 -> 6036 bytes res/css/views/elements/_ReplyChain.pcss | 2 +- res/css/views/rooms/_EventTile.pcss | 5 +- res/css/views/rooms/_ReplyTile.pcss | 5 ++ 8 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 playwright/snapshots/messages/messages.spec.ts/reply-message-ltr-ltrdisplayname-linux.png create mode 100644 playwright/snapshots/messages/messages.spec.ts/reply-message-ltr-rtldisplayname-linux.png create mode 100644 playwright/snapshots/messages/messages.spec.ts/reply-message-trl-ltrdisplayname-linux.png create mode 100644 playwright/snapshots/messages/messages.spec.ts/reply-message-trl-rtldisplayname-linux.png diff --git a/playwright/e2e/messages/messages.spec.ts b/playwright/e2e/messages/messages.spec.ts index f34fa15e82c..569d295b9e9 100644 --- a/playwright/e2e/messages/messages.spec.ts +++ b/playwright/e2e/messages/messages.spec.ts @@ -24,7 +24,34 @@ async function sendMessage(page: Page, message: string): Promise { await page.getByRole("textbox", { name: "Send a message…" }).fill(message); await page.getByRole("button", { name: "Send message" }).click(); - const msgTile = await page.locator(".mx_EventTile_last"); + const msgTile = page.locator(".mx_EventTile_last"); + await msgTile.locator(".mx_EventTile_receiptSent").waitFor(); + return msgTile; +} + +async function sendMultilineMessages(page: Page, messages: string[]) { + await page.getByRole("textbox", { name: "Send a message…" }).focus(); + for (let i = 0; i < messages.length; i++) { + await page.keyboard.type(messages[i]); + if (i < messages.length - 1) await page.keyboard.press("Shift+Enter"); + } + + await page.getByRole("button", { name: "Send message" }).click(); + + const msgTile = page.locator(".mx_EventTile_last"); + await msgTile.locator(".mx_EventTile_receiptSent").waitFor(); + return msgTile; +} + +async function replyMessage(page: Page, message: Locator, replyMessage: string): Promise { + const line = message.locator(".mx_EventTile_line"); + await line.hover(); + await line.getByRole("button", { name: "Reply", exact: true }).click(); + + await page.getByRole("textbox", { name: "Send a reply…" }).fill(replyMessage); + await page.getByRole("button", { name: "Send message" }).click(); + + const msgTile = page.locator(".mx_EventTile_last"); await msgTile.locator(".mx_EventTile_receiptSent").waitFor(); return msgTile; } @@ -88,6 +115,22 @@ test.describe("Message rendering", () => { }); }); + test("should render a reply of a LTR message", async ({ page, user, app, room }) => { + await page.goto(`#/room/${room.roomId}`); + + const msgTile = await sendMultilineMessages(page, [ + "Fist line", + "Second line", + "Third line", + "Fourth line", + ]); + + await replyMessage(page, msgTile, "response to multiline message"); + await expect(msgTile).toMatchScreenshot(`reply-message-ltr-${direction}displayname.png`, { + mask: [page.locator(".mx_MessageTimestamp")], + }); + }); + test("should render a basic RTL text message", async ({ page, user, app, room }) => { await page.goto(`#/room/${room.roomId}`); @@ -122,6 +165,22 @@ test.describe("Message rendering", () => { mask: [page.locator(".mx_MessageTimestamp")], }); }); + + test("should render a reply of a RTL message", async ({ page, user, app, room }) => { + await page.goto(`#/room/${room.roomId}`); + + const msgTile = await sendMultilineMessages(page, [ + "مرحبا بالعالم!", + "مرحبا بالعالم!", + "مرحبا بالعالم!", + "مرحبا بالعالم!", + ]); + + await replyMessage(page, msgTile, "مرحبا بالعالم!"); + await expect(msgTile).toMatchScreenshot(`reply-message-trl-${direction}displayname.png`, { + mask: [page.locator(".mx_MessageTimestamp")], + }); + }); }); }); }); diff --git a/playwright/snapshots/messages/messages.spec.ts/reply-message-ltr-ltrdisplayname-linux.png b/playwright/snapshots/messages/messages.spec.ts/reply-message-ltr-ltrdisplayname-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..eebd5330793f8e7e77913472ccd918d3f4533451 GIT binary patch literal 6508 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 literal 0 HcmV?d00001 diff --git a/playwright/snapshots/messages/messages.spec.ts/reply-message-ltr-rtldisplayname-linux.png b/playwright/snapshots/messages/messages.spec.ts/reply-message-ltr-rtldisplayname-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..ed0c69fb8dd2e77adf030982d105f436eed10ea9 GIT binary patch literal 6548 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 PHw9Bw)KsW|y!!B8-c+DY literal 0 HcmV?d00001 diff --git a/playwright/snapshots/messages/messages.spec.ts/reply-message-trl-ltrdisplayname-linux.png b/playwright/snapshots/messages/messages.spec.ts/reply-message-trl-ltrdisplayname-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..1a0f5577b68f8e7079b9c1164fbc6fd867c9229c GIT binary patch literal 6005 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 literal 0 HcmV?d00001 diff --git a/playwright/snapshots/messages/messages.spec.ts/reply-message-trl-rtldisplayname-linux.png b/playwright/snapshots/messages/messages.spec.ts/reply-message-trl-rtldisplayname-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..587170ee1cef02cacd74eef12b2634442714b3c5 GIT binary patch literal 6036 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 Date: Wed, 28 Aug 2024 16:14:26 +0100 Subject: [PATCH 028/154] Update all non-major dependencies (#12909) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 4 +- yarn.lock | 181 ++++++++++++++++++++++++++------------------------- 2 files changed, 93 insertions(+), 92 deletions(-) diff --git a/package.json b/package.json index 65e33b75a44..5bae5246dec 100644 --- a/package.json +++ b/package.json @@ -122,8 +122,8 @@ "opus-recorder": "^8.0.3", "pako": "^2.0.3", "png-chunks-extract": "^1.0.0", - "posthog-js": "1.149.1", - "qrcode": "1.5.3", + "posthog-js": "1.157.2", + "qrcode": "1.5.4", "re-resizable": "^6.9.0", "react": "17.0.2", "react-beautiful-dnd": "^13.1.0", diff --git a/yarn.lock b/yarn.lock index 561e35a5e14..506384c3840 100644 --- a/yarn.lock +++ b/yarn.lock @@ -33,11 +33,11 @@ "@jridgewell/trace-mapping" "^0.3.24" "@axe-core/playwright@^4.8.1": - version "4.9.1" - resolved "https://registry.yarnpkg.com/@axe-core/playwright/-/playwright-4.9.1.tgz#9c17c2976464d31d96d310c56df8ba100d87d772" - integrity sha512-8m4WZbZq7/aq7ZY5IG8GqV+ZdvtGn/iJdom+wBg+iv/3BAOBIfNQtIu697a41438DzEEyptXWmC3Xl5Kx/o9/g== + version "4.10.0" + resolved "https://registry.yarnpkg.com/@axe-core/playwright/-/playwright-4.10.0.tgz#c9e8d0d694e8c0bd18c4636516dc22496fc03bfe" + integrity sha512-kEr3JPEVUSnKIYp/egV2jvFj+chIjCjPp3K3zlpJMza/CB3TFw8UZNbI9agEC2uMz4YbgAOyzlbUy0QS+OofFA== dependencies: - axe-core "~4.9.1" + axe-core "~4.10.0" "@babel/cli@^7.12.10": version "7.24.8" @@ -2951,14 +2951,6 @@ "@typescript-eslint/visitor-keys" "7.18.0" debug "^4.3.4" -"@typescript-eslint/scope-manager@7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.13.0.tgz#6927d6451537ce648c6af67a2327378d4cc18462" - integrity sha512-ZrMCe1R6a01T94ilV13egvcnvVJ1pxShkE0+NDjDzH4nvG1wXpwsVI5bZCvE7AEDH1mXEx5tJSVR68bLgG7Dng== - dependencies: - "@typescript-eslint/types" "7.13.0" - "@typescript-eslint/visitor-keys" "7.13.0" - "@typescript-eslint/scope-manager@7.18.0": version "7.18.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz#c928e7a9fc2c0b3ed92ab3112c614d6bd9951c83" @@ -2967,6 +2959,14 @@ "@typescript-eslint/types" "7.18.0" "@typescript-eslint/visitor-keys" "7.18.0" +"@typescript-eslint/scope-manager@8.3.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.3.0.tgz#834301d2e70baf924c26818b911bdc40086f7468" + integrity sha512-mz2X8WcN2nVu5Hodku+IR8GgCOl4C0G/Z1ruaWN4dgec64kDBabuXyPAr+/RgJtumv8EEkqIzf3X2U5DUKB2eg== + dependencies: + "@typescript-eslint/types" "8.3.0" + "@typescript-eslint/visitor-keys" "8.3.0" + "@typescript-eslint/type-utils@7.18.0": version "7.18.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz#2165ffaee00b1fbbdd2d40aa85232dab6998f53b" @@ -2977,29 +2977,15 @@ debug "^4.3.4" ts-api-utils "^1.3.0" -"@typescript-eslint/types@7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.13.0.tgz#0cca95edf1f1fdb0cfe1bb875e121b49617477c5" - integrity sha512-QWuwm9wcGMAuTsxP+qz6LBBd3Uq8I5Nv8xb0mk54jmNoCyDspnMvVsOxI6IsMmway5d1S9Su2+sCKv1st2l6eA== - "@typescript-eslint/types@7.18.0": version "7.18.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.18.0.tgz#b90a57ccdea71797ffffa0321e744f379ec838c9" integrity sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ== -"@typescript-eslint/typescript-estree@7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.13.0.tgz#4cc24fc155088ebf3b3adbad62c7e60f72c6de1c" - integrity sha512-cAvBvUoobaoIcoqox1YatXOnSl3gx92rCZoMRPzMNisDiM12siGilSM4+dJAekuuHTibI2hVC2fYK79iSFvWjw== - dependencies: - "@typescript-eslint/types" "7.13.0" - "@typescript-eslint/visitor-keys" "7.13.0" - debug "^4.3.4" - globby "^11.1.0" - is-glob "^4.0.3" - minimatch "^9.0.4" - semver "^7.6.0" - ts-api-utils "^1.3.0" +"@typescript-eslint/types@8.3.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.3.0.tgz#378e62447c2d7028236e55a81d3391026600563b" + integrity sha512-y6sSEeK+facMaAyixM36dQ5NVXTnKWunfD1Ft4xraYqxP0lC0POJmIaL/mw72CUMqjY9qfyVfXafMeaUj0noWw== "@typescript-eslint/typescript-estree@7.18.0": version "7.18.0" @@ -3015,6 +3001,20 @@ semver "^7.6.0" ts-api-utils "^1.3.0" +"@typescript-eslint/typescript-estree@8.3.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.3.0.tgz#3e3d38af101ba61a8568f034733b72bfc9f176b9" + integrity sha512-Mq7FTHl0R36EmWlCJWojIC1qn/ZWo2YiWYc1XVtasJ7FIgjo0MVv9rZWXEE7IK2CGrtwe1dVOxWwqXUdNgfRCA== + dependencies: + "@typescript-eslint/types" "8.3.0" + "@typescript-eslint/visitor-keys" "8.3.0" + debug "^4.3.4" + fast-glob "^3.3.2" + is-glob "^4.0.3" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^1.3.0" + "@typescript-eslint/utils@7.18.0": version "7.18.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.18.0.tgz#bca01cde77f95fc6a8d5b0dbcbfb3d6ca4be451f" @@ -3025,23 +3025,15 @@ "@typescript-eslint/types" "7.18.0" "@typescript-eslint/typescript-estree" "7.18.0" -"@typescript-eslint/utils@^6.0.0 || ^7.0.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.13.0.tgz#f84e7e8aeceae945a9a3f40d077fd95915308004" - integrity sha512-jceD8RgdKORVnB4Y6BqasfIkFhl4pajB1wVxrF4akxD2QPM8GNYjgGwEzYS+437ewlqqrg7Dw+6dhdpjMpeBFQ== +"@typescript-eslint/utils@^6.0.0 || ^7.0.0 || ^8.0.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.3.0.tgz#b10972319deac5959c7a7075d0cf2b5e1de7ec08" + integrity sha512-F77WwqxIi/qGkIGOGXNBLV7nykwfjLsdauRB/DOFPdv6LTF3BHHkBpq81/b5iMPSF055oO2BiivDJV4ChvNtXA== dependencies: "@eslint-community/eslint-utils" "^4.4.0" - "@typescript-eslint/scope-manager" "7.13.0" - "@typescript-eslint/types" "7.13.0" - "@typescript-eslint/typescript-estree" "7.13.0" - -"@typescript-eslint/visitor-keys@7.13.0": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.13.0.tgz#2eb7ce8eb38c2b0d4a494d1fe1908e7071a1a353" - integrity sha512-nxn+dozQx+MK61nn/JP+M4eCkHDSxSLDpgE3WcQo0+fkjEolnaB5jswvIKC4K56By8MMgIho7f1PVxERHEo8rw== - dependencies: - "@typescript-eslint/types" "7.13.0" - eslint-visitor-keys "^3.4.3" + "@typescript-eslint/scope-manager" "8.3.0" + "@typescript-eslint/types" "8.3.0" + "@typescript-eslint/typescript-estree" "8.3.0" "@typescript-eslint/visitor-keys@7.18.0": version "7.18.0" @@ -3051,6 +3043,14 @@ "@typescript-eslint/types" "7.18.0" eslint-visitor-keys "^3.4.3" +"@typescript-eslint/visitor-keys@8.3.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.3.0.tgz#320d747d107af1eef1eb43fbc4ccdbddda13068b" + integrity sha512-RmZwrTbQ9QveF15m/Cl28n0LXD6ea2CjkhH5rQ55ewz3H24w+AMCJHPVYaZ8/0HoG8Z3cLLFFycRXxeO2tz9FA== + dependencies: + "@typescript-eslint/types" "8.3.0" + eslint-visitor-keys "^3.4.3" + "@ungap/structured-clone@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" @@ -3422,12 +3422,12 @@ await-lock@^2.1.0: resolved "https://registry.yarnpkg.com/await-lock/-/await-lock-2.2.2.tgz#a95a9b269bfd2f69d22b17a321686f551152bcef" integrity sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw== -axe-core@4.10.0: +axe-core@4.10.0, axe-core@~4.10.0: version "4.10.0" resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.10.0.tgz#d9e56ab0147278272739a000880196cdfe113b59" integrity sha512-Mr2ZakwQ7XUAjp7pAwQWRhhK8mQQ6JAaNWSjmjxil0R8BPioMtQsTLOolGYkji1rcL++3dCqZA3zWqpT+9Ew6g== -axe-core@^4.9.1, axe-core@~4.9.1: +axe-core@^4.9.1: version "4.9.1" resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.9.1.tgz#fcd0f4496dad09e0c899b44f6c4bb7848da912ae" integrity sha512-QbUdXJVTpvUTHU7871ppZkdOLBeGUKBQWHkHrvN2V9IQWGMt61zf3B45BtzjxEJzYuj0JBjBZP/hmYS/R9pmAw== @@ -3907,14 +3907,13 @@ commondir@^1.0.1: integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== commonmark@^0.31.0: - version "0.31.0" - resolved "https://registry.yarnpkg.com/commonmark/-/commonmark-0.31.0.tgz#4ac57c61f0d7f5ef82d79447a972c61226ef5abc" - integrity sha512-nuDsQ34gjmgAqjyIz6mbRWBW/XPE9wsBempAMBk2V/AA88ekztjTM46oi07J6c6Y/2Y8TdYCZi9L0pIBt/oMZw== + version "0.31.1" + resolved "https://registry.yarnpkg.com/commonmark/-/commonmark-0.31.1.tgz#5c8b1b5eaaca00a0912cad68d1f0f00c836cecd3" + integrity sha512-M6pbc3sRU96iiOK7rmjv/TNrXvTaOscvthUCq7YOrlvZWbqAA36fyEtBvyI3nCcEK4u+JAy9sAdtftIeXwIWig== dependencies: entities "~3.0.1" mdurl "~1.0.1" minimist "~1.2.5" - string.prototype.repeat "^1.0.0" concat-map@0.0.1: version "0.0.1" @@ -4169,7 +4168,7 @@ debug@^3.2.7: dependencies: ms "^2.1.1" -debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4, debug@^4.3.6, debug@~4.3.4: +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4, debug@^4.3.6, debug@~4.3.6: version "4.3.6" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.6.tgz#2ab2c38fbaffebf8aa95fdfe6d88438c7a13c52b" integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg== @@ -4274,9 +4273,9 @@ detect-node-es@^1.1.0: integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ== diff-dom@^5.0.0: - version "5.1.3" - resolved "https://registry.yarnpkg.com/diff-dom/-/diff-dom-5.1.3.tgz#1a00ac023bdc9b7a6de702a51fd6566a34b8d9a4" - integrity sha512-cIQQSQywwn98yIEt1B7BhRRRH+M3tDEbJeV+j7NY0nSM4o3LdP7D3r/kRZDzT2tINX5r9Dpzvk7+RkeSMhzjhA== + version "5.1.4" + resolved "https://registry.yarnpkg.com/diff-dom/-/diff-dom-5.1.4.tgz#20d0f6e39558f59dae0e2943b69360fc15e371cb" + integrity sha512-TSEaVdVGictY1KHg7VpVw2nuM02YKC9C8/qBkGiCnkiAybVbu1zQTMj2/dnVLRO7Z62UsqzHGpXweiOj5/jaZg== diff-match-patch@^1.0.5: version "1.0.5" @@ -4405,9 +4404,9 @@ emittery@^0.13.1: integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== emoji-regex@^10.3.0: - version "10.3.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.3.0.tgz#76998b9268409eb3dae3de989254d456e70cfe23" - integrity sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw== + version "10.4.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.4.0.tgz#03553afea80b3975749cfcb36f776ca268e413d4" + integrity sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw== emoji-regex@^8.0.0: version "8.0.0" @@ -4439,11 +4438,6 @@ emojis-list@^3.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== -encode-utf8@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/encode-utf8/-/encode-utf8-1.0.3.tgz#f30fdd31da07fb596f281beb2f6b027851994cda" - integrity sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw== - encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -4703,11 +4697,11 @@ eslint-plugin-import@^2.25.4: tsconfig-paths "^3.15.0" eslint-plugin-jest@^28.0.0: - version "28.6.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-28.6.0.tgz#8410588d60bcafa68a91b6ec272e4a415502302a" - integrity sha512-YG28E1/MIKwnz+e2H7VwYPzHUYU4aMa19w0yGcwXnnmJH6EfgHahTJ2un3IyraUxNfnz/KUhJAFXNNwWPo12tg== + version "28.8.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-28.8.0.tgz#54f597b5a3295ad04ec946baa245ad02b9b2bca0" + integrity sha512-Tubj1hooFxCl52G4qQu0edzV/+EZzPUeN8p2NnW5uu4fbDs+Yo7+qDVDc4/oG3FbCqEBmu/OC3LSsyiU22oghw== dependencies: - "@typescript-eslint/utils" "^6.0.0 || ^7.0.0" + "@typescript-eslint/utils" "^6.0.0 || ^7.0.0 || ^8.0.0" eslint-plugin-jsx-a11y@^6.5.1: version "6.9.0" @@ -6745,7 +6739,7 @@ lie@~3.3.0: dependencies: immediate "~3.0.5" -lilconfig@~3.1.1: +lilconfig@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.2.tgz#e4a7c3cb549e3a606c8dcc32e5ae1005e62c05cb" integrity sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow== @@ -6776,22 +6770,22 @@ linkifyjs@4.1.3: integrity sha512-auMesunaJ8yfkHvK4gfg1K0SaKX/6Wn9g2Aac/NwX+l5VdmFZzo/hdPGxEOETj+ryRa4/fiOPjeeKURSAJx1sg== lint-staged@^15.0.2: - version "15.2.7" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-15.2.7.tgz#97867e29ed632820c0fb90be06cd9ed384025649" - integrity sha512-+FdVbbCZ+yoh7E/RosSdqKJyUM2OEjTciH0TFNkawKgvFp1zbGlEC39RADg+xKBG1R4mhoH2j85myBQZ5wR+lw== + version "15.2.9" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-15.2.9.tgz#bf70d40b6b192df6ad756fb89822211615e0f4da" + integrity sha512-BZAt8Lk3sEnxw7tfxM7jeZlPRuT4M68O0/CwZhhaw6eeWu0Lz5eERE3m386InivXB64fp/mDID452h48tvKlRQ== dependencies: chalk "~5.3.0" commander "~12.1.0" - debug "~4.3.4" + debug "~4.3.6" execa "~8.0.1" - lilconfig "~3.1.1" - listr2 "~8.2.1" + lilconfig "~3.1.2" + listr2 "~8.2.4" micromatch "~4.0.7" pidtree "~0.6.0" string-argv "~0.3.2" - yaml "~2.4.2" + yaml "~2.5.0" -listr2@~8.2.1: +listr2@~8.2.4: version "8.2.4" resolved "https://registry.yarnpkg.com/listr2/-/listr2-8.2.4.tgz#486b51cbdb41889108cb7e2c90eeb44519f5a77f" integrity sha512-opevsywziHd3zHCVQGAj8zu+Z3yHNkkoYhWIGnq54RrCVwLz0MozotJEDnKsIBLvkfLGN6BLOyAeRrYI0pKA4g== @@ -7103,7 +7097,15 @@ methods@~1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== -micromatch@^4.0.4, micromatch@^4.0.7, micromatch@~4.0.7: +micromatch@^4.0.4, micromatch@~4.0.7: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +micromatch@^4.0.7: version "4.0.7" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.7.tgz#33e8190d9fe474a9895525f5618eee136d46c2e5" integrity sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q== @@ -7717,10 +7719,10 @@ postcss@^8.4.41: picocolors "^1.0.1" source-map-js "^1.2.0" -posthog-js@1.149.1: - version "1.149.1" - resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.149.1.tgz#8c15ca4fa2b9261abbfd4977921b42cc68ffe585" - integrity sha512-n3mkDlV0vJ1QhkDkWwUzY9RIFTPbzDzbKRyjzRE4D6H2PoH3vsrR05DNujoCr3t0hqgsaO4RLXO3VlctpdkGKQ== +posthog-js@1.157.2: + version "1.157.2" + resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.157.2.tgz#dc2515818ead408aefb900e90c535fb57beb1f59" + integrity sha512-ATYKGs+Q51u26nHHhrhWNh1whqFm7j/rwQQYw+y6/YzNmRlo+YsqrGZji9nqXb9/4fo0ModDr+ZmuOI3hKkUXA== dependencies: fflate "^0.4.8" preact "^10.19.3" @@ -7831,13 +7833,12 @@ pvutils@^1.1.3: resolved "https://registry.yarnpkg.com/pvutils/-/pvutils-1.1.3.tgz#f35fc1d27e7cd3dfbd39c0826d173e806a03f5a3" integrity sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ== -qrcode@1.5.3: - version "1.5.3" - resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.3.tgz#03afa80912c0dccf12bc93f615a535aad1066170" - integrity sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg== +qrcode@1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.4.tgz#5cb81d86eb57c675febb08cf007fff963405da88" + integrity sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg== dependencies: dijkstrajs "^1.0.1" - encode-utf8 "^1.0.3" pngjs "^5.0.0" yargs "^15.3.1" @@ -9651,10 +9652,10 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml@~2.4.2: - version "2.4.5" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.4.5.tgz#60630b206dd6d84df97003d33fc1ddf6296cca5e" - integrity sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg== +yaml@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.5.0.tgz#c6165a721cf8000e91c36490a41d7be25176cf5d" + integrity sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw== yargs-parser@^18.1.2: version "18.1.3" From 8b2ded8a0eec2fe5b86dbfc70d6c6766995d0717 Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Thu, 29 Aug 2024 01:22:58 -0500 Subject: [PATCH 029/154] [create-pull-request] automated change (#12935) Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com> --- playwright/plugins/homeserver/synapse/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright/plugins/homeserver/synapse/index.ts b/playwright/plugins/homeserver/synapse/index.ts index 1340304ade8..ea66f97b5df 100644 --- a/playwright/plugins/homeserver/synapse/index.ts +++ b/playwright/plugins/homeserver/synapse/index.ts @@ -28,7 +28,7 @@ import { randB64Bytes } from "../../utils/rand"; // Docker tag to use for synapse docker image. // We target a specific digest as every now and then a Synapse update will break our CI. // This digest is updated by the playwright-image-updates.yaml workflow periodically. -const DOCKER_TAG = "develop@sha256:2144d1c36cee53f82b0636640c944d6c024c05ec8a302c6268ec252e12ea2e99"; +const DOCKER_TAG = "develop@sha256:74cf97b6d17909ac9dca1b728aa834a7a5fd3642695a96d63bda72098e338246"; async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise> { const templateDir = path.join(__dirname, "templates", opts.template); From d16ab09866dae02538903ec63b457ef92ad1fec5 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Thu, 29 Aug 2024 16:26:10 +0200 Subject: [PATCH 030/154] Display pinned messages on a banner at the top of a room (#12917) * Move pinned message hooks to a dedicated file * Add a banner at the top of a room to display the pinned messages * Put the pinning banner behind labs pinning labs flag * Add redacted event support * Handle UTD in pinning message banner * Add tests for redaction * Make all the banner clickable * Add tests for PinnedMessageBanner.tsx * Add e2e tests for the pinned message banner * Review changes --- playwright/e2e/pinned-messages/index.ts | 34 ++- .../pinned-messages/pinned-messages.spec.ts | 63 +++++ .../pinned-message-banner-1-Msg1-linux.png | Bin 0 -> 1325 bytes .../pinned-message-banner-2-Msg1-linux.png | Bin 0 -> 4174 bytes .../pinned-message-banner-2-Msg2-linux.png | Bin 0 -> 4290 bytes .../pinned-message-banner-2-linux.png | Bin 0 -> 13394 bytes .../pinned-message-banner-4-Msg1-linux.png | Bin 0 -> 4142 bytes .../pinned-message-banner-4-Msg2-linux.png | Bin 0 -> 4410 bytes .../pinned-message-banner-4-Msg3-linux.png | Bin 0 -> 4449 bytes .../pinned-message-banner-4-Msg4-linux.png | Bin 0 -> 4142 bytes .../pinned-messages-list-messages-2-linux.png | Bin 13654 -> 0 bytes .../pinned-messages-list-messages-3-linux.png | Bin 16907 -> 0 bytes .../pinned-messages-list-pin-3-linux.png | Bin 0 -> 16914 bytes .../pinned-messages-list-unpin-2-linux.png | Bin 0 -> 13658 bytes res/css/_components.pcss | 1 + res/css/views/rooms/_PinnedMessageBanner.pcss | 119 +++++++++ src/components/structures/RoomView.tsx | 10 + .../views/context_menus/RoomContextMenu.tsx | 2 +- .../right_panel/LegacyRoomHeaderButtons.tsx | 2 +- .../views/right_panel/PinnedMessagesCard.tsx | 166 +----------- .../views/right_panel/RoomSummaryCard.tsx | 2 +- .../views/rooms/PinnedMessageBanner.tsx | 252 ++++++++++++++++++ src/hooks/usePinnedEvents.ts | 212 +++++++++++++++ src/i18n/strings/en_EN.json | 7 + src/utils/PinningUtils.ts | 14 +- .../right_panel/PinnedMessagesCard-test.tsx | 7 +- .../views/rooms/PinnedMessageBanner-test.tsx | 235 ++++++++++++++++ .../PinnedMessageBanner-test.tsx.snap | 166 ++++++++++++ test/utils/PinningUtils-test.ts | 18 +- 29 files changed, 1130 insertions(+), 180 deletions(-) create mode 100644 playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-1-Msg1-linux.png create mode 100644 playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-2-Msg1-linux.png create mode 100644 playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-2-Msg2-linux.png create mode 100644 playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-2-linux.png create mode 100644 playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-4-Msg1-linux.png create mode 100644 playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-4-Msg2-linux.png create mode 100644 playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-4-Msg3-linux.png create mode 100644 playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-4-Msg4-linux.png delete mode 100644 playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-messages-list-messages-2-linux.png delete mode 100644 playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-messages-list-messages-3-linux.png create mode 100644 playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-messages-list-pin-3-linux.png create mode 100644 playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-messages-list-unpin-2-linux.png create mode 100644 res/css/views/rooms/_PinnedMessageBanner.pcss create mode 100644 src/components/views/rooms/PinnedMessageBanner.tsx create mode 100644 src/hooks/usePinnedEvents.ts create mode 100644 test/components/views/rooms/PinnedMessageBanner-test.tsx create mode 100644 test/components/views/rooms/__snapshots__/PinnedMessageBanner-test.tsx.snap diff --git a/playwright/e2e/pinned-messages/index.ts b/playwright/e2e/pinned-messages/index.ts index 5e61b11e857..bb7705ba988 100644 --- a/playwright/e2e/pinned-messages/index.ts +++ b/playwright/e2e/pinned-messages/index.ts @@ -168,9 +168,8 @@ export class Helpers { /** * Return the right panel - * @private */ - private getRightPanel() { + public getRightPanel() { return this.page.locator("#mx_RightPanel"); } @@ -183,7 +182,6 @@ export class Helpers { await expect(rightPanel.getByRole("heading", { name: "Pinned messages" })).toHaveText( `${messages.length} Pinned messages`, ); - await expect(rightPanel).toMatchScreenshot(`pinned-messages-list-messages-${messages.length}.png`); const list = rightPanel.getByRole("list"); await expect(list.getByRole("listitem")).toHaveCount(messages.length); @@ -243,6 +241,36 @@ export class Helpers { await item.getByRole("button").click(); await this.page.getByRole("menu", { name: "Open menu" }).getByRole("menuitem", { name: "Unpin" }).click(); } + + /** + * Return the banner + * @private + */ + public getBanner() { + return this.page.getByTestId("pinned-message-banner"); + } + + /** + * Assert that the banner contains the given message + * @param msg + */ + async assertMessageInBanner(msg: string) { + await expect(this.getBanner().getByText(msg)).toBeVisible(); + } + + /** + * Return the view all button + */ + public getViewAllButton() { + return this.page.getByRole("button", { name: "View all" }); + } + + /** + * Return the close list button + */ + public getCloseListButton() { + return this.page.getByRole("button", { name: "Close list" }); + } } export { expect }; diff --git a/playwright/e2e/pinned-messages/pinned-messages.spec.ts b/playwright/e2e/pinned-messages/pinned-messages.spec.ts index 53f657ea7fa..339c3b1f0ec 100644 --- a/playwright/e2e/pinned-messages/pinned-messages.spec.ts +++ b/playwright/e2e/pinned-messages/pinned-messages.spec.ts @@ -48,6 +48,7 @@ test.describe("Pinned messages", () => { await util.openRoomInfo(); await util.openPinnedMessagesList(); await util.assertPinnedMessagesList(["Msg1", "Msg2", "Msg4"]); + await expect(util.getRightPanel()).toMatchScreenshot(`pinned-messages-list-pin-3.png`); }); test("should unpin one message", async ({ page, app, room1, util }) => { @@ -59,6 +60,7 @@ test.describe("Pinned messages", () => { await util.openPinnedMessagesList(); await util.unpinMessageFromMessageList("Msg2"); await util.assertPinnedMessagesList(["Msg1", "Msg4"]); + await expect(util.getRightPanel()).toMatchScreenshot(`pinned-messages-list-unpin-2.png`); await util.backPinnedMessagesList(); await util.assertPinnedCountInRoomInfo(2); }); @@ -87,4 +89,65 @@ test.describe("Pinned messages", () => { await util.pinMessagesFromQuickActions(["Msg1"], true); await util.assertPinnedCountInRoomInfo(0); }); + + test("should display one message in the banner", async ({ page, app, room1, util }) => { + await util.goTo(room1); + await util.receiveMessages(room1, ["Msg1"]); + await util.pinMessages(["Msg1"]); + await util.assertMessageInBanner("Msg1"); + await expect(util.getBanner()).toMatchScreenshot("pinned-message-banner-1-Msg1.png"); + }); + + test("should display 2 messages in the banner", async ({ page, app, room1, util }) => { + await util.goTo(room1); + await util.receiveMessages(room1, ["Msg1", "Msg2"]); + await util.pinMessages(["Msg1", "Msg2"]); + + await util.assertMessageInBanner("Msg1"); + await expect(util.getBanner()).toMatchScreenshot("pinned-message-banner-2-Msg1.png"); + + await util.getBanner().click(); + await util.assertMessageInBanner("Msg2"); + await expect(util.getBanner()).toMatchScreenshot("pinned-message-banner-2-Msg2.png"); + + await util.getBanner().click(); + await util.assertMessageInBanner("Msg1"); + await expect(util.getBanner()).toMatchScreenshot("pinned-message-banner-2-Msg1.png"); + }); + + test("should display 4 messages in the banner", async ({ page, app, room1, util }) => { + await util.goTo(room1); + await util.receiveMessages(room1, ["Msg1", "Msg2", "Msg3", "Msg4"]); + await util.pinMessages(["Msg1", "Msg2", "Msg3", "Msg4"]); + + for (const msg of ["Msg1", "Msg4", "Msg3", "Msg2"]) { + await util.assertMessageInBanner(msg); + await expect(util.getBanner()).toMatchScreenshot(`pinned-message-banner-4-${msg}.png`); + await util.getBanner().click(); + } + }); + + test("should open the pinned messages list from the banner", async ({ page, app, room1, util }) => { + await util.goTo(room1); + await util.receiveMessages(room1, ["Msg1", "Msg2"]); + await util.pinMessages(["Msg1", "Msg2"]); + + await util.getViewAllButton().click(); + await util.assertPinnedMessagesList(["Msg1", "Msg2"]); + await expect(util.getRightPanel()).toMatchScreenshot("pinned-message-banner-2.png"); + + await expect(util.getCloseListButton()).toBeVisible(); + }); + + test("banner should listen to pinned message list", async ({ page, app, room1, util }) => { + await util.goTo(room1); + await util.receiveMessages(room1, ["Msg1", "Msg2"]); + await util.pinMessages(["Msg1", "Msg2"]); + + await expect(util.getViewAllButton()).toBeVisible(); + + await util.openRoomInfo(); + await util.openPinnedMessagesList(); + await expect(util.getCloseListButton()).toBeVisible(); + }); }); diff --git a/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-1-Msg1-linux.png b/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-1-Msg1-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..d6892c5bbff22d3ae7c10bd62f3de57ebbc44189 GIT binary patch literal 1325 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 literal 0 HcmV?d00001 diff --git a/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-2-linux.png b/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-2-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..f583649aaa95f77fb5d5072f6b83e36f771a78b6 GIT binary patch literal 13394 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 literal 0 HcmV?d00001 diff --git a/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-4-Msg1-linux.png b/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-4-Msg1-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..8169ca9cfd14b9ae5ab628bc8fd8c2c281e3086e GIT binary patch literal 4142 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 literal 0 HcmV?d00001 diff --git a/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-4-Msg2-linux.png b/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-4-Msg2-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..7b6c9948212df8b8cdcd71027b06ae0b4debdcd4 GIT binary patch literal 4410 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 literal 0 HcmV?d00001 diff --git a/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-4-Msg3-linux.png b/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-4-Msg3-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..dfec23cb59097a8272ed2f0b0bb50bada67f921c GIT binary patch literal 4449 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 literal 0 HcmV?d00001 diff --git a/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-4-Msg4-linux.png b/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-4-Msg4-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..72abbbb2606cd0b0b5670ae7e146ff8e7d436bd9 GIT binary patch literal 4142 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 literal 0 HcmV?d00001 diff --git a/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-messages-list-messages-2-linux.png b/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-messages-list-messages-2-linux.png deleted file mode 100644 index 82666b0d95ff5d0728af128f5f1e66757199a8df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13654 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 qftEl1i*t(qu991O6xldi>DfLB)Ohm;VD*8PDke diff --git a/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-messages-list-messages-3-linux.png b/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-messages-list-messages-3-linux.png deleted file mode 100644 index 98e804d897d1de7194d1c924ef1f3170f44e81f2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16907 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 literal 0 HcmV?d00001 diff --git a/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-messages-list-unpin-2-linux.png b/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-messages-list-unpin-2-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..4eede005d886b1dc170f92b78cc32ae3692362ab GIT binary patch literal 13658 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 WTVS)$10I%QdiqHBVfh2=*Z%{J+P+Bu literal 0 HcmV?d00001 diff --git a/res/css/_components.pcss b/res/css/_components.pcss index 96c285bc0a6..bfcab19879d 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -298,6 +298,7 @@ @import "./views/rooms/_NewRoomIntro.pcss"; @import "./views/rooms/_NotificationBadge.pcss"; @import "./views/rooms/_PinnedEventTile.pcss"; +@import "./views/rooms/_PinnedMessageBanner.pcss"; @import "./views/rooms/_PresenceLabel.pcss"; @import "./views/rooms/_ReadReceiptGroup.pcss"; @import "./views/rooms/_ReplyPreview.pcss"; diff --git a/res/css/views/rooms/_PinnedMessageBanner.pcss b/res/css/views/rooms/_PinnedMessageBanner.pcss new file mode 100644 index 00000000000..c6889aba757 --- /dev/null +++ b/res/css/views/rooms/_PinnedMessageBanner.pcss @@ -0,0 +1,119 @@ +/* + * Copyright 2024 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. + */ + +.mx_PinnedMessageBanner { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--cpd-space-4x); + /* 80px = 79px + 1px from the bottom border */ + height: 79px; + padding: 0 var(--cpd-space-4x); + + background-color: var(--cpd-color-bg-canvas-default); + border-bottom: 1px solid var(--cpd-color-gray-400); + + /* From figma */ + box-shadow: 0 var(--cpd-space-2x) var(--cpd-space-6x) calc(var(--cpd-space-2x) * -1) rgba(27, 29, 34, 0.1); + + .mx_PinnedMessageBanner_main { + background: transparent; + border: none; + text-align: start; + cursor: pointer; + + height: 100%; + flex-grow: 1; + display: flex; + align-items: center; + + .mx_PinnedMessageBanner_content { + display: grid; + grid-template: + "indicators pinIcon title" auto + "indicators pinIcon message" auto; + column-gap: var(--cpd-space-2x); + } + + .mx_PinnedMessageBanner_Indicators { + grid-area: indicators; + display: flex; + flex-direction: column; + gap: var(--cpd-space-0-5x); + height: 100%; + + .mx_PinnedMessageBanner_Indicator { + width: var(--cpd-space-0-5x); + background-color: var(--cpd-color-gray-600); + height: 100%; + } + + .mx_PinnedMessageBanner_Indicator--active { + background-color: var(--cpd-color-icon-accent-primary); + } + + .mx_PinnedMessageBanner_Indicator--hidden { + background-color: transparent; + } + } + + .mx_PinnedMessageBanner_PinIcon { + grid-area: pinIcon; + align-self: center; + fill: var(--cpd-color-icon-secondary-alpha); + } + + .mx_PinnedMessageBanner_title { + grid-area: title; + font: var(--cpd-font-body-sm-regular); + color: var(--cpd-color-text-action-accent); + height: 20px; + + .mx_PinnedMessageBanner_title_counter { + font: var(--cpd-font-body-sm-semibold); + } + } + + .mx_PinnedMessageBanner_message { + grid-area: message; + font: var(--cpd-font-body-sm-regular); + height: 20px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .mx_PinnedMessageBanner_redactedMessage { + grid-area: message; + height: 20px; + display: flex; + align-items: center; + } + } + + .mx_PinnedMessageBanner_actions { + white-space: nowrap; + } +} + +.mx_PinnedMessageBanner[data-single-message="true"] { + /* 64px = 63px + 1px from the bottom border */ + height: 63px; + + .mx_PinnedMessageBanner_content { + grid-template: "pinIcon message" auto; + } +} diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 70ee16b542a..9c7469346dc 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -133,6 +133,7 @@ import { SubmitAskToJoinPayload } from "../../dispatcher/payloads/SubmitAskToJoi import RightPanelStore from "../../stores/right-panel/RightPanelStore"; import { onView3pidInvite } from "../../stores/right-panel/action-handlers"; import RoomSearchAuxPanel from "../views/rooms/RoomSearchAuxPanel"; +import { PinnedMessageBanner } from "../views/rooms/PinnedMessageBanner"; const DEBUG = false; const PREVENT_MULTIPLE_JITSI_WITHIN = 30_000; @@ -2409,6 +2410,14 @@ export class RoomView extends React.Component { ); + const isPinningEnabled = SettingsStore.getValue("feature_pinning"); + let pinnedMessageBanner; + if (isPinningEnabled) { + pinnedMessageBanner = ( + + ); + } + let messageComposer; const showComposer = // joined and not showing search results @@ -2537,6 +2546,7 @@ export class RoomView extends React.Component { )} {auxPanel} + {pinnedMessageBanner}
{topUnreadMessagesBar} diff --git a/src/components/views/context_menus/RoomContextMenu.tsx b/src/components/views/context_menus/RoomContextMenu.tsx index 0dbd4b5395a..6c105dd0ccd 100644 --- a/src/components/views/context_menus/RoomContextMenu.tsx +++ b/src/components/views/context_menus/RoomContextMenu.tsx @@ -35,7 +35,6 @@ import { RoomNotifState } from "../../../RoomNotifs"; import Modal from "../../../Modal"; import ExportDialog from "../dialogs/ExportDialog"; import { useFeatureEnabled } from "../../../hooks/useSettings"; -import { usePinnedEvents } from "../right_panel/PinnedMessagesCard"; import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases"; import { RoomSettingsTab } from "../dialogs/RoomSettingsDialog"; import { useEventEmitterState } from "../../../hooks/useEventEmitter"; @@ -53,6 +52,7 @@ import { UIComponent } from "../../../settings/UIFeature"; import { DeveloperToolsOption } from "./DeveloperToolsOption"; import { tagRoom } from "../../../utils/room/tagRoom"; import { useIsVideoRoom } from "../../../utils/video-rooms"; +import { usePinnedEvents } from "../../../hooks/usePinnedEvents"; interface IProps extends IContextMenuProps { room: Room; diff --git a/src/components/views/right_panel/LegacyRoomHeaderButtons.tsx b/src/components/views/right_panel/LegacyRoomHeaderButtons.tsx index 207c97ec7b3..c3fc1fe95d7 100644 --- a/src/components/views/right_panel/LegacyRoomHeaderButtons.tsx +++ b/src/components/views/right_panel/LegacyRoomHeaderButtons.tsx @@ -28,7 +28,6 @@ import HeaderButtons, { HeaderKind } from "./HeaderButtons"; import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases"; import { ActionPayload } from "../../../dispatcher/payloads"; import RightPanelStore from "../../../stores/right-panel/RightPanelStore"; -import { useReadPinnedEvents, usePinnedEvents } from "./PinnedMessagesCard"; import { showThreadPanel } from "../../../dispatcher/dispatch-actions/threads"; import SettingsStore from "../../../settings/SettingsStore"; import { @@ -40,6 +39,7 @@ import { SummarizedNotificationState } from "../../../stores/notifications/Summa import PosthogTrackers from "../../../PosthogTrackers"; import { ButtonEvent } from "../elements/AccessibleButton"; import { doesRoomOrThreadHaveUnreadMessages } from "../../../Unread"; +import { usePinnedEvents, useReadPinnedEvents } from "../../../hooks/usePinnedEvents"; const ROOM_INFO_PHASES = [ RightPanelPhases.RoomSummary, diff --git a/src/components/views/right_panel/PinnedMessagesCard.tsx b/src/components/views/right_panel/PinnedMessagesCard.tsx index 85be2e6d034..0f1f856786e 100644 --- a/src/components/views/right_panel/PinnedMessagesCard.tsx +++ b/src/components/views/right_panel/PinnedMessagesCard.tsx @@ -14,17 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useCallback, useEffect, useState, JSX } from "react"; -import { - Room, - RoomEvent, - RoomStateEvent, - MatrixEvent, - EventType, - RelationType, - EventTimeline, -} from "matrix-js-sdk/src/matrix"; -import { logger } from "matrix-js-sdk/src/logger"; +import React, { useCallback, useEffect, JSX } from "react"; +import { Room, MatrixEvent, EventType } from "matrix-js-sdk/src/matrix"; import { Button, Separator } from "@vector-im/compound-web"; import classNames from "classnames"; import PinIcon from "@vector-im/compound-design-tokens/assets/web/icons/pin"; @@ -33,9 +24,6 @@ import { _t } from "../../../languageHandler"; import BaseCard from "./BaseCard"; import Spinner from "../elements/Spinner"; import { useMatrixClientContext } from "../../../contexts/MatrixClientContext"; -import { useTypedEventEmitter } from "../../../hooks/useEventEmitter"; -import PinningUtils from "../../../utils/PinningUtils"; -import { useAsyncMemo } from "../../../hooks/useAsyncMemo"; import { PinnedEventTile } from "../rooms/PinnedEventTile"; import { useRoomState } from "../../../hooks/useRoomState"; import RoomContext, { TimelineRenderingType, useRoomContext } from "../../../contexts/RoomContext"; @@ -46,155 +34,7 @@ import { filterBoolean } from "../../../utils/arrays"; import Modal from "../../../Modal"; import { UnpinAllDialog } from "../dialogs/UnpinAllDialog"; import EmptyState from "./EmptyState"; - -/** - * Get the pinned event IDs from a room. - * @param room - */ -function getPinnedEventIds(room?: Room): string[] { - return ( - room - ?.getLiveTimeline() - .getState(EventTimeline.FORWARDS) - ?.getStateEvents(EventType.RoomPinnedEvents, "") - ?.getContent()?.pinned ?? [] - ); -} - -/** - * Get the pinned event IDs from a room. - * @param room - */ -export const usePinnedEvents = (room?: Room): string[] => { - const [pinnedEvents, setPinnedEvents] = useState(getPinnedEventIds(room)); - - // Update the pinned events when the room state changes - // Filter out events that are not pinned events - const update = useCallback( - (ev?: MatrixEvent) => { - if (ev && ev.getType() !== EventType.RoomPinnedEvents) return; - setPinnedEvents(getPinnedEventIds(room)); - }, - [room], - ); - - useTypedEventEmitter(room?.getLiveTimeline().getState(EventTimeline.FORWARDS), RoomStateEvent.Events, update); - useEffect(() => { - setPinnedEvents(getPinnedEventIds(room)); - return () => { - setPinnedEvents([]); - }; - }, [room]); - return pinnedEvents; -}; - -/** - * Get the read pinned event IDs from a room. - * @param room - */ -function getReadPinnedEventIds(room?: Room): Set { - return new Set(room?.getAccountData(ReadPinsEventId)?.getContent()?.event_ids ?? []); -} - -/** - * Get the read pinned event IDs from a room. - * @param room - */ -export const useReadPinnedEvents = (room?: Room): Set => { - const [readPinnedEvents, setReadPinnedEvents] = useState>(new Set()); - - // Update the read pinned events when the room state changes - // Filter out events that are not read pinned events - const update = useCallback( - (ev?: MatrixEvent) => { - if (ev && ev.getType() !== ReadPinsEventId) return; - setReadPinnedEvents(getReadPinnedEventIds(room)); - }, - [room], - ); - - useTypedEventEmitter(room, RoomEvent.AccountData, update); - useEffect(() => { - setReadPinnedEvents(getReadPinnedEventIds(room)); - return () => { - setReadPinnedEvents(new Set()); - }; - }, [room]); - return readPinnedEvents; -}; - -/** - * Fetch the pinned events - * @param room - * @param pinnedEventIds - */ -function useFetchedPinnedEvents(room: Room, pinnedEventIds: string[]): Array | null { - const cli = useMatrixClientContext(); - - return useAsyncMemo( - () => { - const promises = pinnedEventIds.map(async (eventId): Promise => { - const timelineSet = room.getUnfilteredTimelineSet(); - // Get the event from the local timeline - const localEvent = timelineSet - ?.getTimelineForEvent(eventId) - ?.getEvents() - .find((e) => e.getId() === eventId); - - // Decrypt the event if it's encrypted - // Can happen when the tab is refreshed and the pinned events card is opened directly - if (localEvent?.isEncrypted()) { - await cli.decryptEventIfNeeded(localEvent); - } - - // If the event is available locally, return it if it's pinnable - // Otherwise, return null - if (localEvent) return PinningUtils.isPinnable(localEvent) ? localEvent : null; - - try { - // The event is not available locally, so we fetch the event and latest edit in parallel - const [ - evJson, - { - events: [edit], - }, - ] = await Promise.all([ - cli.fetchRoomEvent(room.roomId, eventId), - cli.relations(room.roomId, eventId, RelationType.Replace, null, { limit: 1 }), - ]); - - const event = new MatrixEvent(evJson); - - // Decrypt the event if it's encrypted - if (event.isEncrypted()) { - await cli.decryptEventIfNeeded(event); - } - - // Handle poll events - await room.processPollEvents([event]); - - const senderUserId = event.getSender(); - if (senderUserId && PinningUtils.isPinnable(event)) { - // Inject sender information - event.sender = room.getMember(senderUserId); - // Also inject any edits we've found - if (edit) event.makeReplaced(edit); - - return event; - } - } catch (err) { - logger.error("Error looking up pinned event " + eventId + " in room " + room.roomId); - logger.error(err); - } - return null; - }); - - return Promise.all(promises); - }, - [cli, room, pinnedEventIds], - null, - ); -} +import { useFetchedPinnedEvents, usePinnedEvents, useReadPinnedEvents } from "../../../hooks/usePinnedEvents"; /** * List the pinned messages in a room inside a Card. diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index 9dfe049a52d..ece150495e4 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -58,7 +58,6 @@ import { E2EStatus } from "../../../utils/ShieldUtils"; import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"; import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext"; import { useFeatureEnabled } from "../../../hooks/useSettings"; -import { usePinnedEvents } from "./PinnedMessagesCard"; import RoomName from "../elements/RoomName"; import ExportDialog from "../dialogs/ExportDialog"; import RightPanelStore from "../../../stores/right-panel/RightPanelStore"; @@ -81,6 +80,7 @@ import { Action } from "../../../dispatcher/actions"; import { Key } from "../../../Keyboard"; import { useTransition } from "../../../hooks/useTransition"; import { useIsVideoRoom } from "../../../utils/video-rooms"; +import { usePinnedEvents } from "../../../hooks/usePinnedEvents"; interface IProps { room: Room; diff --git a/src/components/views/rooms/PinnedMessageBanner.tsx b/src/components/views/rooms/PinnedMessageBanner.tsx new file mode 100644 index 00000000000..f7010b88380 --- /dev/null +++ b/src/components/views/rooms/PinnedMessageBanner.tsx @@ -0,0 +1,252 @@ +/* + * Copyright 2024 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, { JSX, useEffect, useMemo, useState } from "react"; +import { Icon as PinIcon } from "@vector-im/compound-design-tokens/icons/pin-solid.svg"; +import { Button } from "@vector-im/compound-web"; +import { Room } from "matrix-js-sdk/src/matrix"; +import classNames from "classnames"; + +import { usePinnedEvents, useSortedFetchedPinnedEvents } from "../../../hooks/usePinnedEvents"; +import { _t } from "../../../languageHandler"; +import RightPanelStore from "../../../stores/right-panel/RightPanelStore"; +import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases"; +import { useEventEmitter } from "../../../hooks/useEventEmitter"; +import { UPDATE_EVENT } from "../../../stores/AsyncStore"; +import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"; +import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore"; +import dis from "../../../dispatcher/dispatcher"; +import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; +import { Action } from "../../../dispatcher/actions"; +import MessageEvent from "../messages/MessageEvent"; + +/** + * The props for the {@link PinnedMessageBanner} component. + */ +interface PinnedMessageBannerProps { + /** + * The permalink creator to use. + */ + permalinkCreator: RoomPermalinkCreator; + /** + * The room where the banner is displayed + */ + room: Room; +} + +/** + * A banner that displays the pinned messages in a room. + */ +export function PinnedMessageBanner({ room, permalinkCreator }: PinnedMessageBannerProps): JSX.Element | null { + const pinnedEventIds = usePinnedEvents(room); + const pinnedEvents = useSortedFetchedPinnedEvents(room, pinnedEventIds); + const eventCount = pinnedEvents.length; + const isSinglePinnedEvent = eventCount === 1; + + const [currentEventIndex, setCurrentEventIndex] = useState(eventCount - 1); + // If the list of pinned events changes, we need to make sure the current index isn't out of bound + useEffect(() => { + setCurrentEventIndex((currentEventIndex) => { + // If the current index is out of bound, we set it to the last index + if (currentEventIndex < 0 || currentEventIndex >= eventCount) return eventCount - 1; + return currentEventIndex; + }); + }, [eventCount]); + + const pinnedEvent = pinnedEvents[currentEventIndex]; + // Generate a preview for the pinned event + const eventPreview = useMemo(() => { + if (!pinnedEvent || pinnedEvent.isRedacted() || pinnedEvent.isDecryptionFailure()) return null; + return MessagePreviewStore.instance.generatePreviewForEvent(pinnedEvent); + }, [pinnedEvent]); + + if (!pinnedEvent) return null; + + const shouldUseMessageEvent = pinnedEvent.isRedacted() || pinnedEvent.isDecryptionFailure(); + + const onBannerClick = (): void => { + // Scroll to the pinned message + dis.dispatch({ + action: Action.ViewRoom, + event_id: pinnedEvent.getId(), + highlighted: true, + room_id: room.roomId, + metricsTrigger: undefined, // room doesn't change + }); + + // Cycle through the pinned messages + // When we reach the first message, we go back to the last message + setCurrentEventIndex((currentEventIndex) => (--currentEventIndex === -1 ? eventCount - 1 : currentEventIndex)); + }; + + return ( +
+ + {!isSinglePinnedEvent && } +
+ ); +} + +const MAX_INDICATORS = 3; + +/** + * The props for the {@link IndicatorsProps} component. + */ +interface IndicatorsProps { + /** + * The number of messages pinned + */ + count: number; + /** + * The current index of the pinned message + */ + currentIndex: number; +} + +/** + * A component that displays vertical indicators for the pinned messages. + */ +function Indicators({ count, currentIndex }: IndicatorsProps): JSX.Element { + // We only display a maximum of 3 indicators at one time. + // When there is more than 3 messages pinned, we will cycle through the indicators + + // If there is only 2 messages pinned, we will display 2 indicators + // In case of 1 message pinned, the indicators are not displayed, see {@link PinnedMessageBanner} logic. + const numberOfIndicators = Math.min(count, MAX_INDICATORS); + // The index of the active indicator + const index = currentIndex % numberOfIndicators; + + // We hide the indicators when we are on the last cycle and there are less than 3 remaining messages pinned + const numberOfCycles = Math.ceil(count / numberOfIndicators); + // If the current index is greater than the last cycle index, we are on the last cycle + const isLastCycle = currentIndex >= (numberOfCycles - 1) * MAX_INDICATORS; + // The index of the last message in the last cycle + const lastCycleIndex = numberOfIndicators - (numberOfCycles * numberOfIndicators - count); + + return ( +
+ {Array.from({ length: numberOfIndicators }).map((_, i) => ( +
+ ); +} + +/** + * The props for the {@link Indicator} component. + */ +interface IndicatorProps { + /** + * Whether the indicator is active + */ + active: boolean; + /** + * Whether the indicator is hidden + */ + hidden: boolean; +} + +/** + * A component that displays a vertical indicator for a pinned message. + */ +function Indicator({ active, hidden }: IndicatorProps): JSX.Element { + return ( +
+ ); +} + +function getRightPanelPhase(roomId: string): RightPanelPhases | null { + if (!RightPanelStore.instance.isOpenForRoom(roomId)) return null; + return RightPanelStore.instance.currentCard.phase; +} + +/** + * The props for the {@link BannerButton} component. + */ +interface BannerButtonProps { + /** + * The room where the banner is displayed + */ + room: Room; +} + +/** + * A button that allows the user to view or close the list of pinned messages. + */ +function BannerButton({ room }: BannerButtonProps): JSX.Element { + const [currentPhase, setCurrentPhase] = useState(getRightPanelPhase(room.roomId)); + useEventEmitter(RightPanelStore.instance, UPDATE_EVENT, () => setCurrentPhase(getRightPanelPhase(room.roomId))); + const isPinnedMessagesPhase = currentPhase === RightPanelPhases.PinnedMessages; + + return ( + + ); +} diff --git a/src/hooks/usePinnedEvents.ts b/src/hooks/usePinnedEvents.ts new file mode 100644 index 00000000000..eb531511385 --- /dev/null +++ b/src/hooks/usePinnedEvents.ts @@ -0,0 +1,212 @@ +/* + * Copyright 2024 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 { useCallback, useEffect, useMemo, useState } from "react"; +import { + Room, + RoomEvent, + RoomStateEvent, + MatrixEvent, + EventType, + RelationType, + EventTimeline, + MatrixClient, +} from "matrix-js-sdk/src/matrix"; +import { logger } from "matrix-js-sdk/src/logger"; + +import { useTypedEventEmitter } from "./useEventEmitter"; +import { ReadPinsEventId } from "../components/views/right_panel/types"; +import { useMatrixClientContext } from "../contexts/MatrixClientContext"; +import { useAsyncMemo } from "./useAsyncMemo"; +import PinningUtils from "../utils/PinningUtils"; + +/** + * Get the pinned event IDs from a room. + * @param room + */ +function getPinnedEventIds(room?: Room): string[] { + return ( + room + ?.getLiveTimeline() + .getState(EventTimeline.FORWARDS) + ?.getStateEvents(EventType.RoomPinnedEvents, "") + ?.getContent()?.pinned ?? [] + ); +} + +/** + * Get the pinned event IDs from a room. + * @param room + */ +export const usePinnedEvents = (room?: Room): string[] => { + const [pinnedEvents, setPinnedEvents] = useState(getPinnedEventIds(room)); + + // Update the pinned events when the room state changes + // Filter out events that are not pinned events + const update = useCallback( + (ev?: MatrixEvent) => { + if (ev && ev.getType() !== EventType.RoomPinnedEvents) return; + setPinnedEvents(getPinnedEventIds(room)); + }, + [room], + ); + + useTypedEventEmitter(room?.getLiveTimeline().getState(EventTimeline.FORWARDS), RoomStateEvent.Events, update); + useEffect(() => { + setPinnedEvents(getPinnedEventIds(room)); + return () => { + setPinnedEvents([]); + }; + }, [room]); + return pinnedEvents; +}; + +/** + * Get the read pinned event IDs from a room. + * @param room + */ +function getReadPinnedEventIds(room?: Room): Set { + return new Set(room?.getAccountData(ReadPinsEventId)?.getContent()?.event_ids ?? []); +} + +/** + * Get the read pinned event IDs from a room. + * @param room + */ +export const useReadPinnedEvents = (room?: Room): Set => { + const [readPinnedEvents, setReadPinnedEvents] = useState>(new Set()); + + // Update the read pinned events when the room state changes + // Filter out events that are not read pinned events + const update = useCallback( + (ev?: MatrixEvent) => { + if (ev && ev.getType() !== ReadPinsEventId) return; + setReadPinnedEvents(getReadPinnedEventIds(room)); + }, + [room], + ); + + useTypedEventEmitter(room, RoomEvent.AccountData, update); + useEffect(() => { + setReadPinnedEvents(getReadPinnedEventIds(room)); + return () => { + setReadPinnedEvents(new Set()); + }; + }, [room]); + return readPinnedEvents; +}; + +/** + * Fetch the pinned event + * @param room + * @param pinnedEventId + * @param cli + */ +async function fetchPinnedEvent(room: Room, pinnedEventId: string, cli: MatrixClient): Promise { + const timelineSet = room.getUnfilteredTimelineSet(); + // Get the event from the local timeline + const localEvent = timelineSet + ?.getTimelineForEvent(pinnedEventId) + ?.getEvents() + .find((e) => e.getId() === pinnedEventId); + + // Decrypt the event if it's encrypted + // Can happen when the tab is refreshed and the pinned events card is opened directly + if (localEvent?.isEncrypted()) { + await cli.decryptEventIfNeeded(localEvent); + } + + // If the event is available locally, return it if it's pinnable + // or if it's redacted (to show the redacted event and to be able to unpin it) + // Otherwise, return null + if (localEvent) return PinningUtils.isUnpinnable(localEvent) ? localEvent : null; + + try { + // The event is not available locally, so we fetch the event and latest edit in parallel + const [ + evJson, + { + events: [edit], + }, + ] = await Promise.all([ + cli.fetchRoomEvent(room.roomId, pinnedEventId), + cli.relations(room.roomId, pinnedEventId, RelationType.Replace, null, { limit: 1 }), + ]); + + const event = new MatrixEvent(evJson); + + // Decrypt the event if it's encrypted + if (event.isEncrypted()) { + await cli.decryptEventIfNeeded(event); + } + + // Handle poll events + await room.processPollEvents([event]); + + const senderUserId = event.getSender(); + if (senderUserId && PinningUtils.isUnpinnable(event)) { + // Inject sender information + event.sender = room.getMember(senderUserId); + // Also inject any edits we've found + if (edit) event.makeReplaced(edit); + + return event; + } + } catch (err) { + logger.error(`Error looking up pinned event ${pinnedEventId} in room ${room.roomId}`); + logger.error(err); + } + return null; +} + +/** + * Fetch the pinned events + * @param room + * @param pinnedEventIds + */ +export function useFetchedPinnedEvents(room: Room, pinnedEventIds: string[]): Array | null { + const cli = useMatrixClientContext(); + + return useAsyncMemo( + () => + Promise.all( + pinnedEventIds.map( + async (eventId): Promise => fetchPinnedEvent(room, eventId, cli), + ), + ), + [cli, room, pinnedEventIds], + null, + ); +} + +/** + * Fetch the pinned events and sort them by from the oldest to the newest + * The order is determined by the event timestamp + * @param room + * @param pinnedEventIds + */ +export function useSortedFetchedPinnedEvents(room: Room, pinnedEventIds: string[]): Array { + const pinnedEvents = useFetchedPinnedEvents(room, pinnedEventIds); + return useMemo(() => { + if (!pinnedEvents) return []; + + return pinnedEvents.sort((a, b) => { + if (!a) return -1; + if (!b) return 1; + return a.getTs() - b.getTs(); + }); + }, [pinnedEvents]); +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 889fc157e9e..352ab437629 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2048,6 +2048,13 @@ "not_found_title": "This room or space does not exist.", "not_found_title_name": "%(roomName)s does not exist.", "peek_join_prompt": "You're previewing %(roomName)s. Want to join it?", + "pinned_message_banner": { + "button_close_list": "Close list", + "button_view_all": "View all", + "description": "This room has pinned messages. Click to view them.", + "go_to_message": "View the pinned message in the timeline.", + "title": "%(index)s of %(length)s Pinned messages" + }, "read_topic": "Click to read topic", "rejecting": "Rejecting invite…", "rejoin_button": "Re-join", diff --git a/src/utils/PinningUtils.ts b/src/utils/PinningUtils.ts index 22db64a6f17..9a20a721b96 100644 --- a/src/utils/PinningUtils.ts +++ b/src/utils/PinningUtils.ts @@ -37,11 +37,19 @@ export default class PinningUtils { * @return {boolean} True if the event may be pinned, false otherwise. */ public static isPinnable(event: MatrixEvent): boolean { - if (!event) return false; - if (!this.PINNABLE_EVENT_TYPES.includes(event.getType())) return false; if (event.isRedacted()) return false; + return PinningUtils.isUnpinnable(event); + } - return true; + /** + * Determines if the given event may be unpinned. + * @param {MatrixEvent} event The event to check. + * @return {boolean} True if the event may be unpinned, false otherwise. + */ + public static isUnpinnable(event: MatrixEvent): boolean { + if (!event) return false; + if (event.isRedacted()) return true; + return this.PINNABLE_EVENT_TYPES.includes(event.getType()); } /** diff --git a/test/components/views/right_panel/PinnedMessagesCard-test.tsx b/test/components/views/right_panel/PinnedMessagesCard-test.tsx index 64961ca1447..cfa32fa4900 100644 --- a/test/components/views/right_panel/PinnedMessagesCard-test.tsx +++ b/test/components/views/right_panel/PinnedMessagesCard-test.tsx @@ -20,7 +20,6 @@ import { mocked, MockedObject } from "jest-mock"; import { MatrixEvent, RoomStateEvent, - IEvent, Room, IMinimalEvent, EventType, @@ -266,9 +265,8 @@ describe("", () => { // Redacted messages are unpinnable const pin = mkEvent({ event: true, - type: EventType.RoomMessage, + type: EventType.RoomCreate, content: {}, - unsigned: { redacted_because: {} as unknown as IEvent }, room: "!room:example.org", user: "@alice:example.org", }); @@ -280,9 +278,8 @@ describe("", () => { // Redacted messages are unpinnable const pin = mkEvent({ event: true, - type: EventType.RoomMessage, + type: EventType.RoomCreate, content: {}, - unsigned: { redacted_because: {} as unknown as IEvent }, room: "!room:example.org", user: "@alice:example.org", }); diff --git a/test/components/views/rooms/PinnedMessageBanner-test.tsx b/test/components/views/rooms/PinnedMessageBanner-test.tsx new file mode 100644 index 00000000000..4df0127d824 --- /dev/null +++ b/test/components/views/rooms/PinnedMessageBanner-test.tsx @@ -0,0 +1,235 @@ +/* + * Copyright 2024 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 { act, screen, render } from "@testing-library/react"; +import React from "react"; +import { EventType, IEvent, MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; +import userEvent from "@testing-library/user-event"; + +import * as pinnedEventHooks from "../../../../src/hooks/usePinnedEvents"; +import { PinnedMessageBanner } from "../../../../src/components/views/rooms/PinnedMessageBanner"; +import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks"; +import { stubClient } from "../../../test-utils"; +import dis from "../../../../src/dispatcher/dispatcher"; +import RightPanelStore from "../../../../src/stores/right-panel/RightPanelStore"; +import { RightPanelPhases } from "../../../../src/stores/right-panel/RightPanelStorePhases"; +import { UPDATE_EVENT } from "../../../../src/stores/AsyncStore"; +import { Action } from "../../../../src/dispatcher/actions"; + +describe("", () => { + const userId = "@alice:server.org"; + const roomId = "!room:server.org"; + + let mockClient: MatrixClient; + let room: Room; + let permalinkCreator: RoomPermalinkCreator; + beforeEach(() => { + mockClient = stubClient(); + room = new Room(roomId, mockClient, userId); + permalinkCreator = new RoomPermalinkCreator(room); + jest.spyOn(dis, "dispatch").mockReturnValue(undefined); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + /** + * Create a pinned event with the given content. + * @param content + */ + function makePinEvent(content?: Partial) { + return new MatrixEvent({ + type: EventType.RoomMessage, + sender: userId, + content: { + body: "First pinned message", + msgtype: "m.text", + }, + room_id: roomId, + origin_server_ts: 0, + event_id: "$eventId", + ...content, + }); + } + + const event1 = makePinEvent(); + const event2 = makePinEvent({ + event_id: "$eventId2", + content: { body: "Second pinned message" }, + }); + const event3 = makePinEvent({ + event_id: "$eventId3", + content: { body: "Third pinned message" }, + }); + const event4 = makePinEvent({ + event_id: "$eventId4", + content: { body: "Fourth pinned message" }, + }); + + /** + * Render the banner + */ + function renderBanner() { + return render(); + } + + it("should render nothing when there are no pinned events", async () => { + jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([]); + jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([]); + const { container } = renderBanner(); + expect(container).toBeEmptyDOMElement(); + }); + + it("should render a single pinned event", async () => { + jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([event1.getId()!]); + jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event1]); + + const { asFragment } = renderBanner(); + + expect(screen.getByText("First pinned message")).toBeVisible(); + expect(screen.queryByRole("button", { name: "View all" })).toBeNull(); + expect(asFragment()).toMatchSnapshot(); + }); + + it("should render 2 pinned event", async () => { + jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([event1.getId()!, event2.getId()!]); + jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event1, event2]); + + const { asFragment } = renderBanner(); + + expect(screen.getByText("Second pinned message")).toBeVisible(); + expect(screen.getByTestId("banner-counter")).toHaveTextContent("2 of 2 Pinned messages"); + expect(screen.getAllByTestId("banner-indicator")).toHaveLength(2); + expect(screen.queryByRole("button", { name: "View all" })).toBeVisible(); + expect(asFragment()).toMatchSnapshot(); + }); + + it("should render 4 pinned event", async () => { + jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([ + event1.getId()!, + event2.getId()!, + event3.getId()!, + event4.getId()!, + ]); + jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event1, event2, event3, event4]); + + const { asFragment } = renderBanner(); + + expect(screen.getByText("Fourth pinned message")).toBeVisible(); + expect(screen.getByTestId("banner-counter")).toHaveTextContent("4 of 4 Pinned messages"); + expect(screen.getAllByTestId("banner-indicator")).toHaveLength(3); + expect(screen.queryByRole("button", { name: "View all" })).toBeVisible(); + expect(asFragment()).toMatchSnapshot(); + }); + + it("should rotate the pinned events when the banner is clicked", async () => { + jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([event1.getId()!, event2.getId()!]); + jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event1, event2]); + + renderBanner(); + expect(screen.getByText("Second pinned message")).toBeVisible(); + + await userEvent.click(screen.getByRole("button", { name: "View the pinned message in the timeline." })); + expect(screen.getByText("First pinned message")).toBeVisible(); + expect(screen.getByTestId("banner-counter")).toHaveTextContent("1 of 2 Pinned messages"); + expect(dis.dispatch).toHaveBeenCalledWith({ + action: Action.ViewRoom, + event_id: event2.getId(), + highlighted: true, + room_id: room.roomId, + metricsTrigger: undefined, // room doesn't change + }); + + await userEvent.click(screen.getByRole("button", { name: "View the pinned message in the timeline." })); + expect(screen.getByText("Second pinned message")).toBeVisible(); + expect(screen.getByTestId("banner-counter")).toHaveTextContent("2 of 2 Pinned messages"); + expect(dis.dispatch).toHaveBeenCalledWith({ + action: Action.ViewRoom, + event_id: event1.getId(), + highlighted: true, + room_id: room.roomId, + metricsTrigger: undefined, // room doesn't change + }); + }); + + describe("Right button", () => { + beforeEach(() => { + jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([event1.getId()!, event2.getId()!]); + jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event1, event2]); + }); + + it("should display View all button if the right panel is closed", async () => { + // The Right panel is closed + jest.spyOn(RightPanelStore.instance, "isOpenForRoom").mockReturnValue(false); + + renderBanner(); + expect(screen.getByRole("button", { name: "View all" })).toBeVisible(); + }); + + it("should display View all button if the right panel is not opened on the pinned message list", async () => { + // The Right panel is opened on another card + jest.spyOn(RightPanelStore.instance, "isOpenForRoom").mockReturnValue(true); + jest.spyOn(RightPanelStore.instance, "currentCard", "get").mockReturnValue({ + phase: RightPanelPhases.RoomMemberList, + }); + + renderBanner(); + expect(screen.getByRole("button", { name: "View all" })).toBeVisible(); + }); + + it("should display Close list button if the message pinning list is displayed", async () => { + // The Right panel is closed + jest.spyOn(RightPanelStore.instance, "isOpenForRoom").mockReturnValue(true); + jest.spyOn(RightPanelStore.instance, "currentCard", "get").mockReturnValue({ + phase: RightPanelPhases.PinnedMessages, + }); + + renderBanner(); + expect(screen.getByRole("button", { name: "Close list" })).toBeVisible(); + }); + + it("should open or close the message pinning list", async () => { + // The Right panel is closed + jest.spyOn(RightPanelStore.instance, "isOpenForRoom").mockReturnValue(true); + jest.spyOn(RightPanelStore.instance, "currentCard", "get").mockReturnValue({ + phase: RightPanelPhases.PinnedMessages, + }); + jest.spyOn(RightPanelStore.instance, "showOrHidePhase").mockReturnValue(); + + renderBanner(); + await userEvent.click(screen.getByRole("button", { name: "Close list" })); + expect(RightPanelStore.instance.showOrHidePhase).toHaveBeenCalledWith(RightPanelPhases.PinnedMessages); + }); + + it("should listen to the right panel", async () => { + // The Right panel is closed + jest.spyOn(RightPanelStore.instance, "isOpenForRoom").mockReturnValue(true); + jest.spyOn(RightPanelStore.instance, "currentCard", "get").mockReturnValue({ + phase: RightPanelPhases.PinnedMessages, + }); + + renderBanner(); + expect(screen.getByRole("button", { name: "Close list" })).toBeVisible(); + + jest.spyOn(RightPanelStore.instance, "isOpenForRoom").mockReturnValue(false); + act(() => { + RightPanelStore.instance.emit(UPDATE_EVENT); + }); + expect(screen.getByRole("button", { name: "View all" })).toBeVisible(); + }); + }); +}); diff --git a/test/components/views/rooms/__snapshots__/PinnedMessageBanner-test.tsx.snap b/test/components/views/rooms/__snapshots__/PinnedMessageBanner-test.tsx.snap new file mode 100644 index 00000000000..fa4c793d909 --- /dev/null +++ b/test/components/views/rooms/__snapshots__/PinnedMessageBanner-test.tsx.snap @@ -0,0 +1,166 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` should render 2 pinned event 1`] = ` + +
+ + +
+
+`; + +exports[` should render 4 pinned event 1`] = ` + +
+ + +
+
+`; + +exports[` should render a single pinned event 1`] = ` + +
+ +
+
+`; diff --git a/test/utils/PinningUtils-test.ts b/test/utils/PinningUtils-test.ts index 47434c4fca3..adfd268bf1b 100644 --- a/test/utils/PinningUtils-test.ts +++ b/test/utils/PinningUtils-test.ts @@ -73,15 +73,27 @@ describe("PinningUtils", () => { ).mockReturnValue(true); }); - describe("isPinnable", () => { + describe("isUnpinnable", () => { test.each(PinningUtils.PINNABLE_EVENT_TYPES)("should return true for pinnable event types", (eventType) => { const event = makePinEvent({ type: eventType }); - expect(PinningUtils.isPinnable(event)).toBe(true); + expect(PinningUtils.isUnpinnable(event)).toBe(true); }); test("should return false for a non pinnable event type", () => { const event = makePinEvent({ type: EventType.RoomCreate }); - expect(PinningUtils.isPinnable(event)).toBe(false); + expect(PinningUtils.isUnpinnable(event)).toBe(false); + }); + + test("should return true for a redacted event", () => { + const event = makePinEvent({ unsigned: { redacted_because: "because" as unknown as IEvent } }); + expect(PinningUtils.isUnpinnable(event)).toBe(true); + }); + }); + + describe("isPinnable", () => { + test.each(PinningUtils.PINNABLE_EVENT_TYPES)("should return true for pinnable event types", (eventType) => { + const event = makePinEvent({ type: eventType }); + expect(PinningUtils.isPinnable(event)).toBe(true); }); test("should return false for a redacted event", () => { From 2a450c095c000c15d6850893eaf529c67638be1c Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Fri, 30 Aug 2024 15:54:14 +0200 Subject: [PATCH 031/154] Add `allowImportingTsExtensions` to tsconfig (#12939) --- playwright/tsconfig.json | 3 ++- tsconfig.json | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/playwright/tsconfig.json b/playwright/tsconfig.json index 5f3083fd57b..442e2527b3c 100644 --- a/playwright/tsconfig.json +++ b/playwright/tsconfig.json @@ -6,7 +6,8 @@ "resolveJsonModule": true, "esModuleInterop": true, "moduleResolution": "node", - "module": "es2022" + "module": "es2022", + "allowImportingTsExtensions": true }, "include": [ "**/*.ts", diff --git a/tsconfig.json b/tsconfig.json index 55e83d95b97..6c0c92a504e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "allowImportingTsExtensions": true, "experimentalDecorators": false, "emitDecoratorMetadata": false, "resolveJsonModule": true, From 19f8b44745242baeafaf53965fb2f6c49e11cd32 Mon Sep 17 00:00:00 2001 From: Michael Weimann Date: Fri, 30 Aug 2024 16:45:25 +0200 Subject: [PATCH 032/154] Implement download_file in widget driver (#12931) * Implement download_file in widget driver Signed-off-by: Michael Weimann * Fix test URIs Signed-off-by: Michael Weimann * Use download-file branch as widget-api source Signed-off-by: Michael Weimann * bump matrix-widget-api to 1.9.0 Signed-off-by: Kim Brose * prettier Signed-off-by: Kim Brose --------- Signed-off-by: Michael Weimann Signed-off-by: Kim Brose Co-authored-by: Kim Brose --- package.json | 2 +- src/stores/widgets/StopGapWidgetDriver.ts | 15 ++++++++++ .../widgets/StopGapWidgetDriver-test.ts | 29 +++++++++++++++++++ yarn.lock | 8 +++++ 4 files changed, 53 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 5bae5246dec..02beac725cc 100644 --- a/package.json +++ b/package.json @@ -115,7 +115,7 @@ "matrix-encrypt-attachment": "^1.0.3", "matrix-events-sdk": "0.0.1", "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", - "matrix-widget-api": "^1.8.2", + "matrix-widget-api": "^1.9.0", "memoize-one": "^6.0.0", "minimist": "^1.2.5", "oidc-client-ts": "^3.0.1", diff --git a/src/stores/widgets/StopGapWidgetDriver.ts b/src/stores/widgets/StopGapWidgetDriver.ts index 6bb3e887da8..b61fafbd520 100644 --- a/src/stores/widgets/StopGapWidgetDriver.ts +++ b/src/stores/widgets/StopGapWidgetDriver.ts @@ -73,6 +73,7 @@ import { navigateToPermalink } from "../../utils/permalinks/navigator"; import { SdkContextClass } from "../../contexts/SDKContext"; import { ModuleRunner } from "../../modules/ModuleRunner"; import SettingsStore from "../../settings/SettingsStore"; +import { Media } from "../../customisations/Media"; // TODO: Purge this from the universe @@ -679,4 +680,18 @@ export class StopGapWidgetDriver extends WidgetDriver { return { contentUri: uploadResult.content_uri }; } + + /** + * Download a file from the media repository on the homeserver. + * + * @param contentUri - the MXC URI of the file to download + * @returns an object with: file - response contents as Blob + */ + public async downloadFile(contentUri: string): Promise<{ file: XMLHttpRequestBodyInit }> { + const client = MatrixClientPeg.safeGet(); + const media = new Media({ mxc: contentUri }, client); + const response = await media.downloadSource(); + const blob = await response.blob(); + return { file: blob }; + } } diff --git a/test/stores/widgets/StopGapWidgetDriver-test.ts b/test/stores/widgets/StopGapWidgetDriver-test.ts index 282ce23d6fa..960852e6065 100644 --- a/test/stores/widgets/StopGapWidgetDriver-test.ts +++ b/test/stores/widgets/StopGapWidgetDriver-test.ts @@ -15,6 +15,7 @@ limitations under the License. */ import { mocked, MockedObject } from "jest-mock"; +import fetchMockJest from "fetch-mock-jest"; import { MatrixClient, ClientEvent, @@ -616,4 +617,32 @@ describe("StopGapWidgetDriver", () => { expect(client.uploadContent).toHaveBeenCalledWith("data"); }); }); + + describe("downloadFile", () => { + let driver: WidgetDriver; + + beforeEach(() => { + driver = mkDefaultDriver(); + }); + + it("should download a file and return the blob", async () => { + // eslint-disable-next-line no-restricted-properties + client.mxcUrlToHttp.mockImplementation((mxcUrl) => { + if (mxcUrl === "mxc://example.com/test_file") { + return "https://example.com/_matrix/media/v3/download/example.com/test_file"; + } + + return null; + }); + + fetchMockJest.get("https://example.com/_matrix/media/v3/download/example.com/test_file", "test contents"); + + const result = await driver.downloadFile("mxc://example.com/test_file"); + // A type test is impossible here because of + // https://github.com/jefflau/jest-fetch-mock/issues/209 + // Tell TypeScript that file is a blob. + const file = result.file as Blob; + await expect(file.text()).resolves.toEqual("test contents"); + }); + }); }); diff --git a/yarn.lock b/yarn.lock index 506384c3840..2a6c27ea9ca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7038,6 +7038,14 @@ matrix-widget-api@^1.8.2: "@types/events" "^3.0.0" events "^3.2.0" +matrix-widget-api@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-1.9.0.tgz#884136b405bd3c56e4ea285095c9e01ec52b6b1f" + integrity sha512-au8mqralNDqrEvaVAkU37bXOb8I9SCe+ACdPk11QWw58FKstVq31q2wRz+qWA6J+42KJ6s1DggWbG/S3fEs3jw== + dependencies: + "@types/events" "^3.0.0" + events "^3.2.0" + md5@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f" From 13ec19c22ef8ca74316a4f51b47bd29b2fb6d932 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Fri, 30 Aug 2024 16:57:30 +0200 Subject: [PATCH 033/154] Sort the pinning message list in the same order than the banner (#12937) --- src/components/views/right_panel/PinnedMessagesCard.tsx | 4 ++-- .../views/right_panel/PinnedMessagesCard-test.tsx | 2 ++ .../__snapshots__/PinnedMessagesCard-test.tsx.snap | 8 ++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/components/views/right_panel/PinnedMessagesCard.tsx b/src/components/views/right_panel/PinnedMessagesCard.tsx index 0f1f856786e..92edaca90f9 100644 --- a/src/components/views/right_panel/PinnedMessagesCard.tsx +++ b/src/components/views/right_panel/PinnedMessagesCard.tsx @@ -34,7 +34,7 @@ import { filterBoolean } from "../../../utils/arrays"; import Modal from "../../../Modal"; import { UnpinAllDialog } from "../dialogs/UnpinAllDialog"; import EmptyState from "./EmptyState"; -import { useFetchedPinnedEvents, usePinnedEvents, useReadPinnedEvents } from "../../../hooks/usePinnedEvents"; +import { usePinnedEvents, useReadPinnedEvents, useSortedFetchedPinnedEvents } from "../../../hooks/usePinnedEvents"; /** * List the pinned messages in a room inside a Card. @@ -59,7 +59,7 @@ export function PinnedMessagesCard({ room, onClose, permalinkCreator }: PinnedMe const roomContext = useRoomContext(); const pinnedEventIds = usePinnedEvents(room); const readPinnedEvents = useReadPinnedEvents(room); - const pinnedEvents = useFetchedPinnedEvents(room, pinnedEventIds); + const pinnedEvents = useSortedFetchedPinnedEvents(room, pinnedEventIds); useEffect(() => { if (!cli || cli.isGuest()) return; // nothing to do diff --git a/test/components/views/right_panel/PinnedMessagesCard-test.tsx b/test/components/views/right_panel/PinnedMessagesCard-test.tsx index cfa32fa4900..bd84070a28c 100644 --- a/test/components/views/right_panel/PinnedMessagesCard-test.tsx +++ b/test/components/views/right_panel/PinnedMessagesCard-test.tsx @@ -165,12 +165,14 @@ describe("", () => { room: "!room:example.org", user: "@alice:example.org", msg: "First pinned message", + ts: 2, }); const pin2 = mkMessage({ event: true, room: "!room:example.org", user: "@alice:example.org", msg: "The second one", + ts: 1, }); it("should show spinner whilst loading", async () => { diff --git a/test/components/views/right_panel/__snapshots__/PinnedMessagesCard-test.tsx.snap b/test/components/views/right_panel/__snapshots__/PinnedMessagesCard-test.tsx.snap index f47de92ffc9..394ebada2f1 100644 --- a/test/components/views/right_panel/__snapshots__/PinnedMessagesCard-test.tsx.snap +++ b/test/components/views/right_panel/__snapshots__/PinnedMessagesCard-test.tsx.snap @@ -184,7 +184,7 @@ exports[` should show two pinned messages 1`] = ` class="mx_EventTile_body translate" dir="auto" > - The second one + First pinned message
@@ -250,7 +250,7 @@ exports[` should show two pinned messages 1`] = ` class="mx_EventTile_body translate" dir="auto" > - First pinned message + The second one
@@ -379,7 +379,7 @@ exports[` unpin all should not allow to unpinall 1`] = ` class="mx_EventTile_body translate" dir="auto" > - The second one + First pinned message @@ -445,7 +445,7 @@ exports[` unpin all should not allow to unpinall 1`] = ` class="mx_EventTile_body translate" dir="auto" > - First pinned message + The second one From 3d41f5be032b6750597d38ce57ea75379b688eb1 Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Sat, 31 Aug 2024 01:20:43 -0500 Subject: [PATCH 034/154] [create-pull-request] automated change (#12938) Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com> --- playwright/plugins/homeserver/synapse/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright/plugins/homeserver/synapse/index.ts b/playwright/plugins/homeserver/synapse/index.ts index ea66f97b5df..f37e23f2406 100644 --- a/playwright/plugins/homeserver/synapse/index.ts +++ b/playwright/plugins/homeserver/synapse/index.ts @@ -28,7 +28,7 @@ import { randB64Bytes } from "../../utils/rand"; // Docker tag to use for synapse docker image. // We target a specific digest as every now and then a Synapse update will break our CI. // This digest is updated by the playwright-image-updates.yaml workflow periodically. -const DOCKER_TAG = "develop@sha256:74cf97b6d17909ac9dca1b728aa834a7a5fd3642695a96d63bda72098e338246"; +const DOCKER_TAG = "develop@sha256:0d1063041b52c2c3759ffabacbe593de439eb79712fd4f0a484a763af1ff7066"; async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise> { const templateDir = path.join(__dirname, "templates", opts.template); From acc7342758452535ef2bb00d4d61367a31711c52 Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Mon, 2 Sep 2024 01:22:27 -0500 Subject: [PATCH 035/154] [create-pull-request] automated change (#12942) Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com> --- playwright/plugins/homeserver/synapse/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright/plugins/homeserver/synapse/index.ts b/playwright/plugins/homeserver/synapse/index.ts index f37e23f2406..985ea662cbe 100644 --- a/playwright/plugins/homeserver/synapse/index.ts +++ b/playwright/plugins/homeserver/synapse/index.ts @@ -28,7 +28,7 @@ import { randB64Bytes } from "../../utils/rand"; // Docker tag to use for synapse docker image. // We target a specific digest as every now and then a Synapse update will break our CI. // This digest is updated by the playwright-image-updates.yaml workflow periodically. -const DOCKER_TAG = "develop@sha256:0d1063041b52c2c3759ffabacbe593de439eb79712fd4f0a484a763af1ff7066"; +const DOCKER_TAG = "develop@sha256:9ed33f589137f34ab6523f983bf21fd94e6cbcf68ece9cc806d0721641f76626"; async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise> { const templateDir = path.join(__dirname, "templates", opts.template); From ae15bbe6e0c4b225054d787fa35faab8b830f7c3 Mon Sep 17 00:00:00 2001 From: Timshel Date: Mon, 2 Sep 2024 11:07:07 +0200 Subject: [PATCH 036/154] Allow user to set timezone (#12775) * Allow user to set timezone * Update test snapshots --------- Co-authored-by: Florian Duros --- .../preferences-user-settings-tab.spec.ts | 24 ++++++- ...ab-should-be-rendered-properly-1-linux.png | Bin 57703 -> 200042 bytes .../settings/shared/_SettingsSubsection.pcss | 4 ++ src/DateUtils.ts | 17 ++++- src/TimezoneHandler.ts | 55 ++++++++++++++++ src/components/structures/RoomView.tsx | 6 ++ .../tabs/user/PreferencesUserSettingsTab.tsx | 59 +++++++++++++++++- src/contexts/RoomContext.ts | 1 + src/i18n/strings/en_EN.json | 4 +- src/settings/Settings.tsx | 5 ++ test/TimezoneHandler-test.ts | 31 +++++++++ .../views/rooms/SendMessageComposer-test.tsx | 1 + .../user/PreferencesUserSettingsTab-test.tsx | 26 ++++++++ .../PreferencesUserSettingsTab-test.tsx.snap | 31 ++++++++- test/test-utils/room.ts | 1 + 15 files changed, 256 insertions(+), 9 deletions(-) create mode 100644 src/TimezoneHandler.ts create mode 100644 test/TimezoneHandler-test.ts diff --git a/playwright/e2e/settings/preferences-user-settings-tab.spec.ts b/playwright/e2e/settings/preferences-user-settings-tab.spec.ts index 368935e91c7..ed56ba5e629 100644 --- a/playwright/e2e/settings/preferences-user-settings-tab.spec.ts +++ b/playwright/e2e/settings/preferences-user-settings-tab.spec.ts @@ -17,6 +17,11 @@ limitations under the License. import { test, expect } from "../../element-web-test"; +test.use({ + locale: "en-GB", + timezoneId: "Europe/London", +}); + test.describe("Preferences user settings tab", () => { test.use({ displayName: "Bob", @@ -26,9 +31,9 @@ test.describe("Preferences user settings tab", () => { }, }); - test("should be rendered properly", async ({ app, user }) => { + test("should be rendered properly", async ({ app, page, user }) => { + page.setViewportSize({ width: 1024, height: 3300 }); const tab = await app.settings.openUserSettings("Preferences"); - // Assert that the top heading is rendered await expect(tab.getByRole("heading", { name: "Preferences" })).toBeVisible(); await expect(tab).toMatchScreenshot("Preferences-user-settings-tab-should-be-rendered-properly-1.png"); @@ -53,4 +58,19 @@ test.describe("Preferences user settings tab", () => { // Assert that the default value is rendered again await expect(languageInput.getByText("English")).toBeVisible(); }); + + test("should be able to change the timezone", async ({ uut, user }) => { + // Check language and region setting dropdown + const timezoneInput = uut.locator(".mx_dropdownUserTimezone"); + const timezoneValue = uut.locator("#mx_dropdownUserTimezone_value"); + await timezoneInput.scrollIntoViewIfNeeded(); + // Check the default value + await expect(timezoneValue.getByText("Browser default")).toBeVisible(); + // Click the button to display the dropdown menu + await timezoneInput.getByRole("button", { name: "Set timezone" }).click(); + // Select a different value + timezoneInput.getByRole("option", { name: /Africa\/Abidjan/ }).click(); + // Check the new value + await expect(timezoneValue.getByText("Africa/Abidjan")).toBeVisible(); + }); }); diff --git a/playwright/snapshots/settings/preferences-user-settings-tab.spec.ts/Preferences-user-settings-tab-should-be-rendered-properly-1-linux.png b/playwright/snapshots/settings/preferences-user-settings-tab.spec.ts/Preferences-user-settings-tab-should-be-rendered-properly-1-linux.png index 56745f9a19cb5d7961a3cafe0b76d76c891baaa6..9d47c993a43e39ef33da622d71e7a3b8b1b286f2 100644 GIT binary patch literal 200042 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�o7 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 literal 57703 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 Ck1+TE diff --git a/res/css/components/views/settings/shared/_SettingsSubsection.pcss b/res/css/components/views/settings/shared/_SettingsSubsection.pcss index f0e31285cbc..aae26466ace 100644 --- a/res/css/components/views/settings/shared/_SettingsSubsection.pcss +++ b/res/css/components/views/settings/shared/_SettingsSubsection.pcss @@ -64,4 +64,8 @@ limitations under the License. gap: var(--cpd-space-6x); margin-top: 0; } + + .mx_SettingsSubsection_dropdown { + min-width: 360px; + } } diff --git a/src/DateUtils.ts b/src/DateUtils.ts index f955299682e..ceabdb02369 100644 --- a/src/DateUtils.ts +++ b/src/DateUtils.ts @@ -19,6 +19,7 @@ limitations under the License. import { Optional } from "matrix-events-sdk"; import { _t, getUserLanguage } from "./languageHandler"; +import { getUserTimezone } from "./TimezoneHandler"; export const MINUTE_MS = 60000; export const HOUR_MS = MINUTE_MS * 60; @@ -77,6 +78,7 @@ export function formatDate(date: Date, showTwelveHour = false, locale?: string): weekday: "short", hour: "numeric", minute: "2-digit", + timeZone: getUserTimezone(), }).format(date); } else if (now.getFullYear() === date.getFullYear()) { return new Intl.DateTimeFormat(_locale, { @@ -86,6 +88,7 @@ export function formatDate(date: Date, showTwelveHour = false, locale?: string): day: "numeric", hour: "numeric", minute: "2-digit", + timeZone: getUserTimezone(), }).format(date); } return formatFullDate(date, showTwelveHour, false, _locale); @@ -104,6 +107,7 @@ export function formatFullDateNoTime(date: Date, locale?: string): string { month: "short", day: "numeric", year: "numeric", + timeZone: getUserTimezone(), }).format(date); } @@ -127,6 +131,7 @@ export function formatFullDate(date: Date, showTwelveHour = false, showSeconds = hour: "numeric", minute: "2-digit", second: showSeconds ? "2-digit" : undefined, + timeZone: getUserTimezone(), }).format(date); } @@ -160,6 +165,7 @@ export function formatFullTime(date: Date, showTwelveHour = false, locale?: stri hour: "numeric", minute: "2-digit", second: "2-digit", + timeZone: getUserTimezone(), }).format(date); } @@ -178,6 +184,7 @@ export function formatTime(date: Date, showTwelveHour = false, locale?: string): ...getTwelveHourOptions(showTwelveHour), hour: "numeric", minute: "2-digit", + timeZone: getUserTimezone(), }).format(date); } @@ -285,6 +292,7 @@ export function formatFullDateNoDayNoTime(date: Date, locale?: string): string { year: "numeric", month: "numeric", day: "numeric", + timeZone: getUserTimezone(), }).format(date); } @@ -354,6 +362,9 @@ export function formatPreciseDuration(durationMs: number): string { * @returns {string} formattedDate */ export const formatLocalDateShort = (timestamp: number, locale?: string): string => - new Intl.DateTimeFormat(locale ?? getUserLanguage(), { day: "2-digit", month: "2-digit", year: "2-digit" }).format( - timestamp, - ); + new Intl.DateTimeFormat(locale ?? getUserLanguage(), { + day: "2-digit", + month: "2-digit", + year: "2-digit", + timeZone: getUserTimezone(), + }).format(timestamp); diff --git a/src/TimezoneHandler.ts b/src/TimezoneHandler.ts new file mode 100644 index 00000000000..49689e4c613 --- /dev/null +++ b/src/TimezoneHandler.ts @@ -0,0 +1,55 @@ +/* +Copyright 2024 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 { SettingLevel } from "./settings/SettingLevel"; +import SettingsStore from "./settings/SettingsStore"; + +export const USER_TIMEZONE_KEY = "userTimezone"; + +/** + * Returning `undefined` ensure that if unset the browser default will be used in `DateTimeFormat`. + * @returns The user specified timezone or `undefined` + */ +export function getUserTimezone(): string | undefined { + const tz = SettingsStore.getValueAt(SettingLevel.DEVICE, USER_TIMEZONE_KEY); + return tz || undefined; +} + +/** + * Set in the settings the given timezone + * @timezone + */ +export function setUserTimezone(timezone: string): Promise { + return SettingsStore.setValue(USER_TIMEZONE_KEY, null, SettingLevel.DEVICE, timezone); +} + +/** + * Return all the available timezones + */ +export function getAllTimezones(): string[] { + return Intl.supportedValuesOf("timeZone"); +} + +/** + * Return the current timezone in a short human readable way + */ +export function shortBrowserTimezone(): string { + return ( + new Intl.DateTimeFormat(undefined, { timeZoneName: "short" }) + .formatToParts(new Date()) + .find((x) => x.type === "timeZoneName")?.value ?? "GMT" + ); +} diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 9c7469346dc..51c0db082f5 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -47,6 +47,7 @@ import { ViewRoomOpts } from "@matrix-org/react-sdk-module-api/lib/lifecycles/Ro import shouldHideEvent from "../../shouldHideEvent"; import { _t } from "../../languageHandler"; +import * as TimezoneHandler from "../../TimezoneHandler"; import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks"; import ResizeNotifier from "../../utils/ResizeNotifier"; import ContentMessages from "../../ContentMessages"; @@ -228,6 +229,7 @@ export interface IRoomState { lowBandwidth: boolean; alwaysShowTimestamps: boolean; showTwelveHourTimestamps: boolean; + userTimezone: string | undefined; readMarkerInViewThresholdMs: number; readMarkerOutOfViewThresholdMs: number; showHiddenEvents: boolean; @@ -455,6 +457,7 @@ export class RoomView extends React.Component { lowBandwidth: SettingsStore.getValue("lowBandwidth"), alwaysShowTimestamps: SettingsStore.getValue("alwaysShowTimestamps"), showTwelveHourTimestamps: SettingsStore.getValue("showTwelveHourTimestamps"), + userTimezone: TimezoneHandler.getUserTimezone(), readMarkerInViewThresholdMs: SettingsStore.getValue("readMarkerInViewThresholdMs"), readMarkerOutOfViewThresholdMs: SettingsStore.getValue("readMarkerOutOfViewThresholdMs"), showHiddenEvents: SettingsStore.getValue("showHiddenEventsInTimeline"), @@ -512,6 +515,9 @@ export class RoomView extends React.Component { SettingsStore.watchSetting("showTwelveHourTimestamps", null, (...[, , , value]) => this.setState({ showTwelveHourTimestamps: value as boolean }), ), + SettingsStore.watchSetting(TimezoneHandler.USER_TIMEZONE_KEY, null, (...[, , , value]) => + this.setState({ userTimezone: value as string }), + ), SettingsStore.watchSetting("readMarkerInViewThresholdMs", null, (...[, , , value]) => this.setState({ readMarkerInViewThresholdMs: value as number }), ), diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx index 0e213de9695..d74ea346620 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx @@ -15,12 +15,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useCallback, useEffect, useState } from "react"; +import React, { ReactElement, useCallback, useEffect, useState } from "react"; +import { NonEmptyArray } from "../../../../../@types/common"; import { _t, getCurrentLanguage } from "../../../../../languageHandler"; import { UseCase } from "../../../../../settings/enums/UseCase"; import SettingsStore from "../../../../../settings/SettingsStore"; import Field from "../../../elements/Field"; +import Dropdown from "../../../elements/Dropdown"; import { SettingLevel } from "../../../../../settings/SettingLevel"; import SettingsFlag from "../../../elements/SettingsFlag"; import AccessibleButton from "../../../elements/AccessibleButton"; @@ -38,12 +40,16 @@ import PlatformPeg from "../../../../../PlatformPeg"; import { IS_MAC } from "../../../../../Keyboard"; import SpellCheckSettings from "../../SpellCheckSettings"; import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch"; +import * as TimezoneHandler from "../../../../../TimezoneHandler"; interface IProps { closeSettingsFn(success: boolean): void; } interface IState { + timezone: string | undefined; + timezones: string[]; + timezoneSearch: string | undefined; autocompleteDelay: string; readMarkerInViewThresholdMs: string; readMarkerOutOfViewThresholdMs: string; @@ -68,7 +74,7 @@ const LanguageSection: React.FC = () => { ); return ( -
+
{_t("settings|general|application_language")}
@@ -173,6 +179,9 @@ export default class PreferencesUserSettingsTab extends React.Component { + this.setState({ timezone: tz }); + TimezoneHandler.setUserTimezone(tz); + }; + + /** + * If present filter the time zones matching the search term + */ + private onTimezoneSearchChange = (search: string): void => { + const timezoneSearch = search.toLowerCase(); + const timezones = timezoneSearch + ? TimezoneHandler.getAllTimezones().filter((tz) => { + return tz.toLowerCase().includes(timezoneSearch); + }) + : TimezoneHandler.getAllTimezones(); + + this.setState({ timezones, timezoneSearch }); + }; + private onAutocompleteDelayChange = (e: React.ChangeEvent): void => { this.setState({ autocompleteDelay: e.target.value }); SettingsStore.setValue("autocompleteDelay", null, SettingLevel.DEVICE, e.target.value); @@ -217,6 +245,16 @@ export default class PreferencesUserSettingsTab extends React.Component it !== "FTUE.userOnboardingButton" || showUserOnboardingPage(useCase)); + const browserTimezoneLabel: string = _t("settings|preferences|default_timezone", { + timezone: TimezoneHandler.shortBrowserTimezone(), + }); + + // Always Preprend the default option + const timezones = this.state.timezones.map((tz) => { + return
{tz}
; + }); + timezones.unshift(
{browserTimezoneLabel}
); + return ( @@ -254,6 +292,23 @@ export default class PreferencesUserSettingsTab extends React.Component +
+ {_t("settings|preferences|user_timezone")} + + {timezones as NonEmptyArray} + +
+ {this.renderGroup(PreferencesUserSettingsTab.TIME_SETTINGS)}
diff --git a/src/contexts/RoomContext.ts b/src/contexts/RoomContext.ts index 52a61fe73a3..a53448de9d7 100644 --- a/src/contexts/RoomContext.ts +++ b/src/contexts/RoomContext.ts @@ -55,6 +55,7 @@ const RoomContext = createContext< lowBandwidth: false, alwaysShowTimestamps: false, showTwelveHourTimestamps: false, + userTimezone: undefined, readMarkerInViewThresholdMs: 3000, readMarkerOutOfViewThresholdMs: 30000, showHiddenEvents: false, diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 352ab437629..e0b85cc4e16 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2703,6 +2703,7 @@ "code_blocks_heading": "Code blocks", "compact_modern": "Use a more compact 'Modern' layout", "composer_heading": "Composer", + "default_timezone": "Browser default (%(timezone)s)", "dialog_title": "Settings: Preferences", "enable_hardware_acceleration": "Enable hardware acceleration", "enable_tray_icon": "Show tray icon and minimise window to it on close", @@ -2718,7 +2719,8 @@ "show_checklist_shortcuts": "Show shortcut to welcome checklist above the room list", "show_polls_button": "Show polls button", "surround_text": "Surround selected text when typing special characters", - "time_heading": "Displaying time" + "time_heading": "Displaying time", + "user_timezone": "Set timezone" }, "prompt_invite": "Prompt before sending invites to potentially invalid matrix IDs", "replace_plain_emoji": "Automatically replace plain text Emoji", diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 476818a1384..7cd58c9355d 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -649,6 +649,11 @@ export const SETTINGS: { [setting: string]: ISetting } = { displayName: _td("settings|always_show_message_timestamps"), default: false, }, + "userTimezone": { + supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, + displayName: _td("settings|preferences|user_timezone"), + default: "", + }, "autoplayGifs": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, displayName: _td("settings|autoplay_gifs"), diff --git a/test/TimezoneHandler-test.ts b/test/TimezoneHandler-test.ts new file mode 100644 index 00000000000..2ac2f9e21e8 --- /dev/null +++ b/test/TimezoneHandler-test.ts @@ -0,0 +1,31 @@ +/* +Copyright 2015, 2016 OpenMarket Ltd +Copyright 2017 Vector Creations Ltd +Copyright 2022 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 * as tzh from "../src/TimezoneHandler"; + +describe("TimezoneHandler", () => { + it("should support setting a user timezone", async () => { + const tz = "Europe/Paris"; + await tzh.setUserTimezone(tz); + expect(tzh.getUserTimezone()).toEqual(tz); + }); + it("Return undefined with an empty TZ", async () => { + await tzh.setUserTimezone(""); + expect(tzh.getUserTimezone()).toEqual(undefined); + }); +}); diff --git a/test/components/views/rooms/SendMessageComposer-test.tsx b/test/components/views/rooms/SendMessageComposer-test.tsx index cc6eb9ec872..a7806507256 100644 --- a/test/components/views/rooms/SendMessageComposer-test.tsx +++ b/test/components/views/rooms/SendMessageComposer-test.tsx @@ -66,6 +66,7 @@ describe("", () => { lowBandwidth: false, alwaysShowTimestamps: false, showTwelveHourTimestamps: false, + userTimezone: undefined, readMarkerInViewThresholdMs: 3000, readMarkerOutOfViewThresholdMs: 30000, showHiddenEvents: false, diff --git a/test/components/views/settings/tabs/user/PreferencesUserSettingsTab-test.tsx b/test/components/views/settings/tabs/user/PreferencesUserSettingsTab-test.tsx index 45417201596..94272c93eb6 100644 --- a/test/components/views/settings/tabs/user/PreferencesUserSettingsTab-test.tsx +++ b/test/components/views/settings/tabs/user/PreferencesUserSettingsTab-test.tsx @@ -55,6 +55,32 @@ describe("PreferencesUserSettingsTab", () => { expect(reloadStub).toHaveBeenCalled(); }); + it("should search and select a user timezone", async () => { + renderTab(); + + expect(await screen.findByText(/Browser default/)).toBeInTheDocument(); + const timezoneDropdown = await screen.findByRole("button", { name: "Set timezone" }); + await userEvent.click(timezoneDropdown); + + // Without filtering `expect(screen.queryByRole("option" ...` take over 1s. + await fireEvent.change(screen.getByRole("combobox", { name: "Set timezone" }), { + target: { value: "Africa/Abidjan" }, + }); + + expect(screen.queryByRole("option", { name: "Africa/Abidjan" })).toBeInTheDocument(); + expect(screen.queryByRole("option", { name: "Europe/Paris" })).not.toBeInTheDocument(); + + await fireEvent.change(screen.getByRole("combobox", { name: "Set timezone" }), { + target: { value: "Europe/Paris" }, + }); + + expect(screen.queryByRole("option", { name: "Africa/Abidjan" })).not.toBeInTheDocument(); + const option = await screen.getByRole("option", { name: "Europe/Paris" }); + await userEvent.click(option); + + expect(await screen.findByText("Europe/Paris")).toBeInTheDocument(); + }); + it("should not show spell check setting if unsupported", async () => { PlatformPeg.get()!.supportsSpellCheckSettings = jest.fn().mockReturnValue(false); diff --git a/test/components/views/settings/tabs/user/__snapshots__/PreferencesUserSettingsTab-test.tsx.snap b/test/components/views/settings/tabs/user/__snapshots__/PreferencesUserSettingsTab-test.tsx.snap index 18ab66cadfd..74b5375ebc1 100644 --- a/test/components/views/settings/tabs/user/__snapshots__/PreferencesUserSettingsTab-test.tsx.snap +++ b/test/components/views/settings/tabs/user/__snapshots__/PreferencesUserSettingsTab-test.tsx.snap @@ -31,7 +31,7 @@ exports[`PreferencesUserSettingsTab should render 1`] = ` class="mx_SettingsSubsection_content" >
Application language
+
+ Set timezone +
+ +
+
diff --git a/test/test-utils/room.ts b/test/test-utils/room.ts index 0a188c7cd59..1b17566c17a 100644 --- a/test/test-utils/room.ts +++ b/test/test-utils/room.ts @@ -72,6 +72,7 @@ export function getRoomContext(room: Room, override: Partial): IRoom layout: Layout.Group, lowBandwidth: false, alwaysShowTimestamps: false, + userTimezone: undefined, showTwelveHourTimestamps: false, readMarkerInViewThresholdMs: 3000, readMarkerOutOfViewThresholdMs: 30000, From 1ac533e7306a49212cec2d21cb7c82c38e6f202c Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Mon, 2 Sep 2024 17:45:25 +0200 Subject: [PATCH 037/154] Don't emit decrypted event for the banner (#12944) --- src/hooks/usePinnedEvents.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hooks/usePinnedEvents.ts b/src/hooks/usePinnedEvents.ts index eb531511385..bd073362198 100644 --- a/src/hooks/usePinnedEvents.ts +++ b/src/hooks/usePinnedEvents.ts @@ -126,7 +126,7 @@ async function fetchPinnedEvent(room: Room, pinnedEventId: string, cli: MatrixCl // Decrypt the event if it's encrypted // Can happen when the tab is refreshed and the pinned events card is opened directly if (localEvent?.isEncrypted()) { - await cli.decryptEventIfNeeded(localEvent); + await cli.decryptEventIfNeeded(localEvent, { emit: false }); } // If the event is available locally, return it if it's pinnable @@ -150,7 +150,7 @@ async function fetchPinnedEvent(room: Room, pinnedEventId: string, cli: MatrixCl // Decrypt the event if it's encrypted if (event.isEncrypted()) { - await cli.decryptEventIfNeeded(event); + await cli.decryptEventIfNeeded(event, { emit: false }); } // Handle poll events From 41686bba584b32b4d57866505fb39e5ff345b52c Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Mon, 2 Sep 2024 17:50:39 +0200 Subject: [PATCH 038/154] Always display last pinned message on the banner (#12945) * Fix when an event is pinned and the banner displays the correct event. Fix when an event is pinned and the banner displays the good event. * Update e2e tests --- .../pinned-messages/pinned-messages.spec.ts | 10 +-- .../views/rooms/PinnedMessageBanner.tsx | 8 +-- .../views/rooms/PinnedMessageBanner-test.tsx | 19 +++++ .../PinnedMessageBanner-test.tsx.snap | 69 +++++++++++++++++++ 4 files changed, 95 insertions(+), 11 deletions(-) diff --git a/playwright/e2e/pinned-messages/pinned-messages.spec.ts b/playwright/e2e/pinned-messages/pinned-messages.spec.ts index 339c3b1f0ec..2a855fcb857 100644 --- a/playwright/e2e/pinned-messages/pinned-messages.spec.ts +++ b/playwright/e2e/pinned-messages/pinned-messages.spec.ts @@ -103,16 +103,16 @@ test.describe("Pinned messages", () => { await util.receiveMessages(room1, ["Msg1", "Msg2"]); await util.pinMessages(["Msg1", "Msg2"]); - await util.assertMessageInBanner("Msg1"); - await expect(util.getBanner()).toMatchScreenshot("pinned-message-banner-2-Msg1.png"); - - await util.getBanner().click(); await util.assertMessageInBanner("Msg2"); await expect(util.getBanner()).toMatchScreenshot("pinned-message-banner-2-Msg2.png"); await util.getBanner().click(); await util.assertMessageInBanner("Msg1"); await expect(util.getBanner()).toMatchScreenshot("pinned-message-banner-2-Msg1.png"); + + await util.getBanner().click(); + await util.assertMessageInBanner("Msg2"); + await expect(util.getBanner()).toMatchScreenshot("pinned-message-banner-2-Msg2.png"); }); test("should display 4 messages in the banner", async ({ page, app, room1, util }) => { @@ -120,7 +120,7 @@ test.describe("Pinned messages", () => { await util.receiveMessages(room1, ["Msg1", "Msg2", "Msg3", "Msg4"]); await util.pinMessages(["Msg1", "Msg2", "Msg3", "Msg4"]); - for (const msg of ["Msg1", "Msg4", "Msg3", "Msg2"]) { + for (const msg of ["Msg4", "Msg3", "Msg2", "Msg1"]) { await util.assertMessageInBanner(msg); await expect(util.getBanner()).toMatchScreenshot(`pinned-message-banner-4-${msg}.png`); await util.getBanner().click(); diff --git a/src/components/views/rooms/PinnedMessageBanner.tsx b/src/components/views/rooms/PinnedMessageBanner.tsx index f7010b88380..1a82a232de1 100644 --- a/src/components/views/rooms/PinnedMessageBanner.tsx +++ b/src/components/views/rooms/PinnedMessageBanner.tsx @@ -57,13 +57,9 @@ export function PinnedMessageBanner({ room, permalinkCreator }: PinnedMessageBan const isSinglePinnedEvent = eventCount === 1; const [currentEventIndex, setCurrentEventIndex] = useState(eventCount - 1); - // If the list of pinned events changes, we need to make sure the current index isn't out of bound + // When the number of pinned messages changes, we want to display the last message useEffect(() => { - setCurrentEventIndex((currentEventIndex) => { - // If the current index is out of bound, we set it to the last index - if (currentEventIndex < 0 || currentEventIndex >= eventCount) return eventCount - 1; - return currentEventIndex; - }); + setCurrentEventIndex((currentEventIndex) => eventCount - 1); }, [eventCount]); const pinnedEvent = pinnedEvents[currentEventIndex]; diff --git a/test/components/views/rooms/PinnedMessageBanner-test.tsx b/test/components/views/rooms/PinnedMessageBanner-test.tsx index 4df0127d824..0febf21a91d 100644 --- a/test/components/views/rooms/PinnedMessageBanner-test.tsx +++ b/test/components/views/rooms/PinnedMessageBanner-test.tsx @@ -136,6 +136,25 @@ describe("", () => { expect(asFragment()).toMatchSnapshot(); }); + it("should display the last message when the pinned event array changed", async () => { + jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([event1.getId()!, event2.getId()!]); + jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event1, event2]); + + const { asFragment, rerender } = renderBanner(); + await userEvent.click(screen.getByRole("button", { name: "View the pinned message in the timeline." })); + expect(screen.getByText("First pinned message")).toBeVisible(); + + jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([ + event1.getId()!, + event2.getId()!, + event3.getId()!, + ]); + jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event1, event2, event3]); + rerender(); + expect(screen.getByText("Third pinned message")).toBeVisible(); + expect(asFragment()).toMatchSnapshot(); + }); + it("should rotate the pinned events when the banner is clicked", async () => { jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([event1.getId()!, event2.getId()!]); jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event1, event2]); diff --git a/test/components/views/rooms/__snapshots__/PinnedMessageBanner-test.tsx.snap b/test/components/views/rooms/__snapshots__/PinnedMessageBanner-test.tsx.snap index fa4c793d909..8fd241fd2f6 100644 --- a/test/components/views/rooms/__snapshots__/PinnedMessageBanner-test.tsx.snap +++ b/test/components/views/rooms/__snapshots__/PinnedMessageBanner-test.tsx.snap @@ -1,5 +1,74 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[` should display the last message when the pinned event array changed 1`] = ` + +
+ + +
+
+`; + exports[` should render 2 pinned event 1`] = `
Date: Mon, 2 Sep 2024 17:51:04 +0200 Subject: [PATCH 039/154] Display the indicator even with one message in pinned message banner (#12946) * Display the indicator even with one message * Update e2e tests --- .../pinned-message-banner-1-Msg1-linux.png | Bin 1325 -> 1372 bytes .../pinned-message-banner-2-Msg1-linux.png | Bin 4174 -> 4170 bytes .../pinned-message-banner-2-Msg2-linux.png | Bin 4290 -> 4279 bytes .../pinned-message-banner-4-Msg1-linux.png | Bin 4142 -> 4142 bytes .../pinned-message-banner-4-Msg2-linux.png | Bin 4410 -> 4400 bytes .../pinned-message-banner-4-Msg3-linux.png | Bin 4449 -> 4454 bytes .../pinned-message-banner-4-Msg4-linux.png | Bin 4142 -> 4141 bytes res/css/views/rooms/_PinnedMessageBanner.pcss | 10 +++++++--- .../views/rooms/PinnedMessageBanner.tsx | 2 +- .../PinnedMessageBanner-test.tsx.snap | 8 ++++++++ 10 files changed, 16 insertions(+), 4 deletions(-) diff --git a/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-1-Msg1-linux.png b/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-1-Msg1-linux.png index d6892c5bbff22d3ae7c10bd62f3de57ebbc44189..c3c4d57acf40e7b2c29b31738a7191f3c029ab21 100644 GIT binary patch literal 1372 zcma)6Sx}Q_6#c@|qKpb6BozZVQklUFK@@`o2}>*ul%Nz%1SBHEq7Yex0Fwfi7Eqye zC}M^Y5>O<91wNV6(|u$lDdrH^f2?!D*X+&TBm+;i&;(P!7r z{W}2wy9nRo0|C+bZwdQiRAdV3gZuN7y12Ib4u3Dt%09i_z3UK$yM65C9c%I0pcOHWP5;rh@^n*+%%}g8=-zjUE`ofYCNGy=VkH${}>Kz!RQ3 zbDyi=MKmA)*c;IaF+JdVz+~kA>?y2qiC>jXG%B_9O>?`!I3|I&KM^11m{k&F z-<+|z`u5(ON~N0P=3!l3-=gz!bI;d@WvG`%Dk>^M9T-F6fn^SdbGac+DR|+>a6q~w zl)HttC13IP=RFpF1G?5Mq&oE0l(5sCuAO37eR+ek#-)Fj*jc|2jdHHdxJJA#*wiR7 z&dz1-8Ew_o)z2v8bLWbQY~^(8u~fx0pU?lS`n2$dO)RtDx3lDj!$tkcDJj$sPs33H z3($o(5oU%9F>s1$P9#NBhSTWy)iE(K7e)G$ zJR%fQGC^WmTAFCEWbwK^4gix_IOHc^TDr;#so!|eVJ%r&T9QawY4YHTGvvl7UY~b9 z9IcQ_EfEMZCvIZ0IrbNu$MyB-VqblI{Y5i{AfwT4KMWk^thxJW)kSwKbXU9k`YFG& zjJOH!H%1TTHFvJ9ODWg+r6+kjUTtk{2q&((uC8v-ILFI@frSoLOWt!GQN_NC{=JI> zjMS7AJ6QdWj~9Jf>q-ow^7I(>6vMLIP( z`Q7f2;NTO~$)<$49(DkW)6{eTm)YTlqscySs8dzMvS1RaC3=WCz(C&-jR9arXkwhG z@k@^1on5Y8WC_)9-P$549HEtHCNBO;_GSUYLoIFz`Cxwq2FV%DP8nn}tFdWlo^YRT7H!c3=TG^reZ3HUP7c z|CR_#UI6HA=YuWeAE6!wZhV}8NdFxr{}3zxTJ5^@E*1crbQ~~tw%a$`6I}yI0ff^; Ke4W>&?7snQEriDa literal 1325 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 delta 3961 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 M@0uHw!JPj7A6G*34FCWD diff --git a/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-2-Msg2-linux.png b/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-2-Msg2-linux.png index 98396be1f829c1fbc0cca1ee16595c6979919759..29fc76f02ae73e70b6eff053fa051452a1dc624d 100644 GIT binary patch literal 4279 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 B2LS*8 literal 4290 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 diff --git a/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-4-Msg1-linux.png b/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-4-Msg1-linux.png index 8169ca9cfd14b9ae5ab628bc8fd8c2c281e3086e..8b4225ef38223ed50c1c3cc10a62c8b313ba6692 100644 GIT binary patch literal 4142 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 literal 4142 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 diff --git a/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-4-Msg2-linux.png b/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-4-Msg2-linux.png index 7b6c9948212df8b8cdcd71027b06ae0b4debdcd4..6d617ecb0a86067f074a2d5cdd0a3c5852bd716b 100644 GIT binary patch literal 4400 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 diff --git a/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-4-Msg3-linux.png b/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-4-Msg3-linux.png index dfec23cb59097a8272ed2f0b0bb50bada67f921c..5be7305d01551d553b3af2468a9731b3d40026c2 100644 GIT binary patch literal 4454 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 GFaHl58)gy! literal 4449 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 diff --git a/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-4-Msg4-linux.png b/playwright/snapshots/pinned-messages/pinned-messages.spec.ts/pinned-message-banner-4-Msg4-linux.png index 72abbbb2606cd0b0b5670ae7e146ff8e7d436bd9..ac88cee9bfdbf99df26015372457c0fed0f1c1b9 100644 GIT binary patch literal 4141 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 diff --git a/res/css/views/rooms/_PinnedMessageBanner.pcss b/res/css/views/rooms/_PinnedMessageBanner.pcss index c6889aba757..31cf3ece8a0 100644 --- a/res/css/views/rooms/_PinnedMessageBanner.pcss +++ b/res/css/views/rooms/_PinnedMessageBanner.pcss @@ -80,7 +80,7 @@ grid-area: title; font: var(--cpd-font-body-sm-regular); color: var(--cpd-color-text-action-accent); - height: 20px; + line-height: 20px; .mx_PinnedMessageBanner_title_counter { font: var(--cpd-font-body-sm-semibold); @@ -90,7 +90,7 @@ .mx_PinnedMessageBanner_message { grid-area: message; font: var(--cpd-font-body-sm-regular); - height: 20px; + line-height: 20px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @@ -114,6 +114,10 @@ height: 63px; .mx_PinnedMessageBanner_content { - grid-template: "pinIcon message" auto; + grid-template: "indicators pinIcon message" auto; + + .mx_PinnedMessageBanner_message { + line-height: 40px; + } } } diff --git a/src/components/views/rooms/PinnedMessageBanner.tsx b/src/components/views/rooms/PinnedMessageBanner.tsx index 1a82a232de1..0479e30ce9e 100644 --- a/src/components/views/rooms/PinnedMessageBanner.tsx +++ b/src/components/views/rooms/PinnedMessageBanner.tsx @@ -102,7 +102,7 @@ export function PinnedMessageBanner({ room, permalinkCreator }: PinnedMessageBan onClick={onBannerClick} >
- {!isSinglePinnedEvent && } + {!isSinglePinnedEvent && (
diff --git a/test/components/views/rooms/__snapshots__/PinnedMessageBanner-test.tsx.snap b/test/components/views/rooms/__snapshots__/PinnedMessageBanner-test.tsx.snap index 8fd241fd2f6..90b1efb6351 100644 --- a/test/components/views/rooms/__snapshots__/PinnedMessageBanner-test.tsx.snap +++ b/test/components/views/rooms/__snapshots__/PinnedMessageBanner-test.tsx.snap @@ -219,6 +219,14 @@ exports[` should render a single pinned event 1`] = `
+
+
+
Date: Mon, 2 Sep 2024 17:35:36 +0100 Subject: [PATCH 040/154] Update to 2.37.9 (#12943) --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 02beac725cc..40294aed5f6 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "@babel/runtime": "^7.12.5", "@matrix-org/analytics-events": "^0.24.0", "@matrix-org/emojibase-bindings": "^1.1.2", - "@matrix-org/matrix-wysiwyg": "2.37.8", + "@matrix-org/matrix-wysiwyg": "2.37.9", "@matrix-org/react-sdk-module-api": "^2.4.0", "@matrix-org/spec": "^1.7.0", "@sentry/browser": "^8.0.0", diff --git a/yarn.lock b/yarn.lock index 2a6c27ea9ca..deabeebb37f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1887,10 +1887,10 @@ resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-7.0.0.tgz#8d6abdb9ded8656cc9e2a7909913a34bf3fc9b3a" integrity sha512-MOencXiW/gI5MuTtCNsuojjwT5DXCrjMqv9xOslJC9h2tPdLFFFMGr58dY5Lis4DRd9MRWcgrGowUIHOqieWTA== -"@matrix-org/matrix-wysiwyg@2.37.8": - version "2.37.8" - resolved "https://registry.yarnpkg.com/@matrix-org/matrix-wysiwyg/-/matrix-wysiwyg-2.37.8.tgz#0b61e9023e3c73ca8789e97c80d7282e8c823c6b" - integrity sha512-fx8KGpztmJvwiY1OvE9A7r08kNcUZQIZXPbWcXNtQ61GwV5VyKvMxCmxfRlZ6Ac8oagVxRPu4WySCRX44Y9Ylw== +"@matrix-org/matrix-wysiwyg@2.37.9": + version "2.37.9" + resolved "https://registry.yarnpkg.com/@matrix-org/matrix-wysiwyg/-/matrix-wysiwyg-2.37.9.tgz#4d5667df3c74e11fd01c4b5be920caff72bf2f48" + integrity sha512-Jn2ug6ySX5es5/SV5BVgSBhVPS7GXggwZ/GGWmnJvhnp/IArit4kgUAsNRhQeKebuVvNphwQykCGwyBOn2PLBA== dependencies: eslint-plugin-unicorn "^54.0.0" From 5ff3fd67c445baeac4cafa3f8ec75ccc49ba1f27 Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Tue, 3 Sep 2024 01:22:31 -0500 Subject: [PATCH 041/154] [create-pull-request] automated change (#12949) Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com> --- playwright/plugins/homeserver/synapse/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright/plugins/homeserver/synapse/index.ts b/playwright/plugins/homeserver/synapse/index.ts index 985ea662cbe..b4e42c0357e 100644 --- a/playwright/plugins/homeserver/synapse/index.ts +++ b/playwright/plugins/homeserver/synapse/index.ts @@ -28,7 +28,7 @@ import { randB64Bytes } from "../../utils/rand"; // Docker tag to use for synapse docker image. // We target a specific digest as every now and then a Synapse update will break our CI. // This digest is updated by the playwright-image-updates.yaml workflow periodically. -const DOCKER_TAG = "develop@sha256:9ed33f589137f34ab6523f983bf21fd94e6cbcf68ece9cc806d0721641f76626"; +const DOCKER_TAG = "develop@sha256:84e3d3a5ecbb618d29878e99fa58b6b926d6feb08ea536063cabed41be0a057c"; async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise> { const templateDir = path.join(__dirname, "templates", opts.template); From 6bfdb3e16b738a0353da8f344a3a1716ab730f97 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 3 Sep 2024 12:59:30 +0100 Subject: [PATCH 042/154] Fix read receipt animation (#12923) * Fix read receipt animation The way it was done involved remembering dom nodes and then getting their position later when animating the receipt to its next position, but I'm not sure how this worked since the DOM node may not neccessarily be in the DOM anymore. Instead, just remember the bounding box coordinates. At worst it might go weird if the window is resized but seems fine in practice. Also, keeping references to dom nodes feels like a fast road to memory leaks. Fixes https://github.com/element-hq/element-web/issues/27916 * Attempt to write a test for read receipts and fix naming * Another test also change a condition to make it testable --- src/components/structures/MessagePanel.tsx | 4 +- src/components/views/rooms/EventTile.tsx | 4 +- .../views/rooms/ReadReceiptGroup.tsx | 16 ++-- .../views/rooms/ReadReceiptMarker.tsx | 58 ++++---------- .../views/rooms/ReadReceiptMarker-test.tsx | 79 +++++++++++++++++++ 5 files changed, 108 insertions(+), 53 deletions(-) create mode 100644 test/components/views/rooms/ReadReceiptMarker-test.tsx diff --git a/src/components/structures/MessagePanel.tsx b/src/components/structures/MessagePanel.tsx index 07b600484ac..c28992f33e0 100644 --- a/src/components/structures/MessagePanel.tsx +++ b/src/components/structures/MessagePanel.tsx @@ -47,7 +47,7 @@ import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks"; import EditorStateTransfer from "../../utils/EditorStateTransfer"; import { Action } from "../../dispatcher/actions"; import { getEventDisplayInfo } from "../../utils/EventRenderingUtils"; -import { IReadReceiptInfo } from "../views/rooms/ReadReceiptMarker"; +import { IReadReceiptPosition } from "../views/rooms/ReadReceiptMarker"; import { haveRendererForEvent } from "../../events/EventTileFactory"; import { editorRoomKey } from "../../Editing"; import { hasThreadSummary } from "../../utils/EventUtils"; @@ -213,7 +213,7 @@ export default class MessagePanel extends React.Component { // opaque readreceipt info for each userId; used by ReadReceiptMarker // to manage its animations - private readReceiptMap: { [userId: string]: IReadReceiptInfo } = {}; + private readReceiptMap: { [userId: string]: IReadReceiptPosition } = {}; // Track read receipts by event ID. For each _shown_ event ID, we store // the list of read receipts to display: diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index e0f8ab1161c..616ed1442cf 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -60,7 +60,7 @@ import PlatformPeg from "../../../PlatformPeg"; import MemberAvatar from "../avatars/MemberAvatar"; import SenderProfile from "../messages/SenderProfile"; import MessageTimestamp from "../messages/MessageTimestamp"; -import { IReadReceiptInfo } from "./ReadReceiptMarker"; +import { IReadReceiptPosition } from "./ReadReceiptMarker"; import MessageActionBar from "../messages/MessageActionBar"; import ReactionsRow from "../messages/ReactionsRow"; import { getEventDisplayInfo } from "../../../utils/EventRenderingUtils"; @@ -167,7 +167,7 @@ export interface EventTileProps { // opaque readreceipt info for each userId; used by ReadReceiptMarker // to manage its animations. Should be an empty object when the room // first loads - readReceiptMap?: { [userId: string]: IReadReceiptInfo }; + readReceiptMap?: { [userId: string]: IReadReceiptPosition }; // A function which is used to check if the parent panel is being // unmounted, to avoid unnecessary work. Should return true if we diff --git a/src/components/views/rooms/ReadReceiptGroup.tsx b/src/components/views/rooms/ReadReceiptGroup.tsx index c9d00a4e699..551658a54b8 100644 --- a/src/components/views/rooms/ReadReceiptGroup.tsx +++ b/src/components/views/rooms/ReadReceiptGroup.tsx @@ -18,7 +18,7 @@ import React, { PropsWithChildren } from "react"; import { User } from "matrix-js-sdk/src/matrix"; import { Tooltip } from "@vector-im/compound-web"; -import ReadReceiptMarker, { IReadReceiptInfo } from "./ReadReceiptMarker"; +import ReadReceiptMarker, { IReadReceiptPosition } from "./ReadReceiptMarker"; import { IReadReceiptProps } from "./EventTile"; import AccessibleButton from "../elements/AccessibleButton"; import MemberAvatar from "../avatars/MemberAvatar"; @@ -41,7 +41,7 @@ export const READ_AVATAR_SIZE = 16; interface Props { readReceipts: IReadReceiptProps[]; - readReceiptMap: { [userId: string]: IReadReceiptInfo }; + readReceiptMap: { [userId: string]: IReadReceiptPosition }; checkUnmounting?: () => boolean; suppressAnimation: boolean; isTwelveHour?: boolean; @@ -111,13 +111,13 @@ export function ReadReceiptGroup({ const { hidden, position } = determineAvatarPosition(index, maxAvatars); const userId = receipt.userId; - let readReceiptInfo: IReadReceiptInfo | undefined; + let readReceiptPosition: IReadReceiptPosition | undefined; if (readReceiptMap) { - readReceiptInfo = readReceiptMap[userId]; - if (!readReceiptInfo) { - readReceiptInfo = {}; - readReceiptMap[userId] = readReceiptInfo; + readReceiptPosition = readReceiptMap[userId]; + if (!readReceiptPosition) { + readReceiptPosition = {}; + readReceiptMap[userId] = readReceiptPosition; } } @@ -128,7 +128,7 @@ export function ReadReceiptGroup({ fallbackUserId={userId} offset={position * READ_AVATAR_OFFSET} hidden={hidden} - readReceiptInfo={readReceiptInfo} + readReceiptPosition={readReceiptPosition} checkUnmounting={checkUnmounting} suppressAnimation={suppressAnimation} timestamp={receipt.ts} diff --git a/src/components/views/rooms/ReadReceiptMarker.tsx b/src/components/views/rooms/ReadReceiptMarker.tsx index 7b907e6f23e..c1f2e3ccaaa 100644 --- a/src/components/views/rooms/ReadReceiptMarker.tsx +++ b/src/components/views/rooms/ReadReceiptMarker.tsx @@ -24,10 +24,10 @@ import { toPx } from "../../../utils/units"; import MemberAvatar from "../avatars/MemberAvatar"; import { READ_AVATAR_SIZE } from "./ReadReceiptGroup"; -export interface IReadReceiptInfo { +// The top & right from the bounding client rect of each read receipt +export interface IReadReceiptPosition { top?: number; right?: number; - parent?: Element; } interface IProps { @@ -48,7 +48,7 @@ interface IProps { suppressAnimation?: boolean; // an opaque object for storing information about this user's RR in this room - readReceiptInfo?: IReadReceiptInfo; + readReceiptPosition?: IReadReceiptPosition; // A function which is used to check if the parent panel is being // unmounted, to avoid unnecessary work. Should return true if we @@ -90,7 +90,7 @@ export default class ReadReceiptMarker extends React.PureComponent { + afterEach(() => { + jest.restoreAllMocks(); + jest.useRealTimers(); + }); + + it("should position at -16px if given no previous position", () => { + render(); + + expect(screen.getByTestId("avatar-img").style.top).toBe("-16px"); + }); + + it("should position at previous top if given", () => { + render(); + + expect(screen.getByTestId("avatar-img").style.top).toBe("100px"); + }); + + it("should apply new styles after mounted to animate", () => { + jest.useFakeTimers(); + + render(); + expect(screen.getByTestId("avatar-img").style.top).toBe("100px"); + + jest.runAllTimers(); + + expect(screen.getByTestId("avatar-img").style.top).toBe("0px"); + }); + + it("should update readReceiptPosition when unmounted", () => { + const pos: IReadReceiptPosition = {}; + const { unmount } = render(); + + expect(pos.top).toBeUndefined(); + + unmount(); + + expect(pos.top).toBe(0); + }); + + it("should update readReceiptPosition to current position", () => { + const pos: IReadReceiptPosition = {}; + jest.spyOn(HTMLElement.prototype, "offsetParent", "get").mockImplementation(function (): Element | null { + return { + getBoundingClientRect: jest.fn().mockReturnValue({ top: 0, right: 0 } as DOMRect), + } as unknown as Element; + }); + jest.spyOn(HTMLElement.prototype, "getBoundingClientRect").mockReturnValue({ top: 100, right: 0 } as DOMRect); + + const { unmount } = render(); + + expect(pos.top).toBeUndefined(); + + unmount(); + + expect(pos.top).toBe(100); + }); +}); From 9c233ef5db3c037136f07571c1504af4b2bdcba1 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 3 Sep 2024 12:32:49 +0000 Subject: [PATCH 043/154] Upgrade dependency to matrix-js-sdk@34.5.0-rc.0 --- package.json | 2 +- yarn.lock | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 40294aed5f6..b02651adf23 100644 --- a/package.json +++ b/package.json @@ -114,7 +114,7 @@ "maplibre-gl": "^2.0.0", "matrix-encrypt-attachment": "^1.0.3", "matrix-events-sdk": "0.0.1", - "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", + "matrix-js-sdk": "34.5.0-rc.0", "matrix-widget-api": "^1.9.0", "memoize-one": "^6.0.0", "minimist": "^1.2.5", diff --git a/yarn.lock b/yarn.lock index deabeebb37f..474be972354 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6999,9 +6999,10 @@ matrix-events-sdk@0.0.1: resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd" integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA== -"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": - version "34.4.0" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/2a6612c73aa7ae8d7f10f426239b2850c86a1ea5" +matrix-js-sdk@34.5.0-rc.0: + version "34.5.0-rc.0" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-34.5.0-rc.0.tgz#198f72383483661cc693c1b256f4bc30e238eaa9" + integrity sha512-6yrxDR3uMaLNp42nVLMQLbFx0qJn35I/Xp3BXH3nRUc5OWSklVvCAJb8p7TFh6089gMR46BjaoVftR2mpqRh1A== dependencies: "@babel/runtime" "^7.12.5" "@matrix-org/matrix-sdk-crypto-wasm" "^7.0.0" From 8f22eb6443214080a61e7282944ac8c93ffa51ec Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 3 Sep 2024 12:40:24 +0000 Subject: [PATCH 044/154] v3.109.0-rc.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b02651adf23..f8738622e68 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.108.0", + "version": "3.109.0-rc.0", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From 476998531575febe3a1ec74e4907a94c96b53544 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2024 22:03:53 +0100 Subject: [PATCH 045/154] Update dependency @types/node to v18.19.47 (#12954) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index deabeebb37f..439d92227ff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2761,9 +2761,9 @@ undici-types "~5.26.4" "@types/node@18": - version "18.19.45" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.45.tgz#a9ebfe4c316a356be7ca11f753ecb2feda6d6bdf" - integrity sha512-VZxPKNNhjKmaC1SUYowuXSRSMGyQGmQjvvA1xE4QZ0xce2kLtEhPDS+kqpCPBZYgqblCLQ2DAjSzmgCM5auvhA== + version "18.19.48" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.48.tgz#3a1696f4a7298d8831ed9ce47db62bf4c62c8880" + integrity sha512-7WevbG4ekUcRQSZzOwxWgi5dZmTak7FaxXDoW7xVxPBmKx1rTzfmRLkeCgJzcbBnOV2dkhAPc8cCeT6agocpjg== dependencies: undici-types "~5.26.4" From 892b29796877996480687fb72ee3bc1d3aa83734 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2024 22:04:12 +0100 Subject: [PATCH 046/154] Update babel monorepo (#12955) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 109 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 65 insertions(+), 44 deletions(-) diff --git a/yarn.lock b/yarn.lock index 439d92227ff..abca74e6b06 100644 --- a/yarn.lock +++ b/yarn.lock @@ -40,9 +40,9 @@ axe-core "~4.10.0" "@babel/cli@^7.12.10": - version "7.24.8" - resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.24.8.tgz#79eaa55a69c77cafbea3e87537fd1df5a5a2edf8" - integrity sha512-isdp+G6DpRyKc+3Gqxy2rjzgF7Zj9K0mzLNnxz+E/fgeag8qT3vVulX4gY9dGO1q0y+0lUv6V3a+uhUzMzrwXg== + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.25.6.tgz#bc35561adc78ade43ac9c09a690768493ab9ed95" + integrity sha512-Z+Doemr4VtvSD2SNHTrkiFZ1LX+JI6tyRXAAOb4N9khIuPyoEPmTPJarPm8ljJV1D6bnMQjyHMWTT9NeKbQuXA== dependencies: "@jridgewell/trace-mapping" "^0.3.25" commander "^6.2.0" @@ -53,7 +53,7 @@ slash "^2.0.0" optionalDependencies: "@nicolo-ribaudo/chokidar-2" "2.1.8-no-fsevents.3" - chokidar "^3.4.0" + chokidar "^3.6.0" "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.24.7": version "7.24.7" @@ -177,7 +177,7 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" -"@babel/generator@^7.25.0", "@babel/generator@^7.25.4": +"@babel/generator@^7.25.0": version "7.25.5" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.5.tgz#b31cf05b3fe8c32d206b6dad03bb0aacbde73450" integrity sha512-abd43wyLfbWoxC6ahM8xTkqLpGB2iWBVyuKC9/srhFunCd1SDNrV1s72bBpK4hLj8KLzHBBcOblvLQZBNw9r3w== @@ -187,6 +187,16 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" +"@babel/generator@^7.25.4", "@babel/generator@^7.25.6": + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.6.tgz#0df1ad8cb32fe4d2b01d8bf437f153d19342a87c" + integrity sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw== + dependencies: + "@babel/types" "^7.25.6" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^2.5.1" + "@babel/helper-annotate-as-pure@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz#5373c7bc8366b12a033b4be1ac13a206c6656aab" @@ -246,7 +256,7 @@ regexpu-core "^5.3.1" semver "^6.3.1" -"@babel/helper-define-polyfill-provider@^0.6.1", "@babel/helper-define-polyfill-provider@^0.6.2": +"@babel/helper-define-polyfill-provider@^0.6.2": version "0.6.2" resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz#18594f789c3594acb24cfdb4a7f7b7d2e8bd912d" integrity sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ== @@ -453,12 +463,12 @@ js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.12.11", "@babel/parser@^7.14.7", "@babel/parser@^7.18.5", "@babel/parser@^7.20.7", "@babel/parser@^7.22.15", "@babel/parser@^7.22.16", "@babel/parser@^7.23.3", "@babel/parser@^7.24.7", "@babel/parser@^7.25.0", "@babel/parser@^7.25.4": - version "7.25.4" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.4.tgz#af4f2df7d02440286b7de57b1c21acfb2a6f257a" - integrity sha512-nq+eWrOgdtu3jG5Os4TQP3x3cLA8hR8TvJNjD8vnPa20WGycimcparWnLK4jJhElTK6SDyuJo1weMKO/5LpmLA== +"@babel/parser@^7.1.0", "@babel/parser@^7.12.11", "@babel/parser@^7.14.7", "@babel/parser@^7.18.5", "@babel/parser@^7.20.7", "@babel/parser@^7.22.15", "@babel/parser@^7.22.16", "@babel/parser@^7.23.3", "@babel/parser@^7.24.7", "@babel/parser@^7.25.0", "@babel/parser@^7.25.4", "@babel/parser@^7.25.6": + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.6.tgz#85660c5ef388cbbf6e3d2a694ee97a38f18afe2f" + integrity sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q== dependencies: - "@babel/types" "^7.25.4" + "@babel/types" "^7.25.6" "@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.3": version "7.25.3" @@ -721,15 +731,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.8" -"@babel/plugin-transform-class-properties@^7.12.1": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.7.tgz#256879467b57b0b68c7ddfc5b76584f398cd6834" - integrity sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" - -"@babel/plugin-transform-class-properties@^7.25.4": +"@babel/plugin-transform-class-properties@^7.12.1", "@babel/plugin-transform-class-properties@^7.25.4": version "7.25.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.4.tgz#bae7dbfcdcc2e8667355cd1fb5eda298f05189fd" integrity sha512-nZeZHyCWPfjkdU5pA/uHiTaDAFUEqkpzf1YoQT2NeSynCGYq9rxfyI3XpQbfx/a0hSnFH6TGlEXvae5Vi7GD8g== @@ -1058,14 +1060,14 @@ "@babel/helper-plugin-utils" "^7.24.7" "@babel/plugin-transform-runtime@^7.12.10": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.7.tgz#00a5bfaf8c43cf5c8703a8a6e82b59d9c58f38ca" - integrity sha512-YqXjrk4C+a1kZjewqt+Mmu2UuV1s07y8kqcUf4qYLnoqemhR4gRQikhdAhSVJioMjVTu6Mo6pAbaypEA3jY6fw== + version "7.25.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.25.4.tgz#96e4ad7bfbbe0b4a7b7e6f2a533ca326cf204963" + integrity sha512-8hsyG+KUYGY0coX6KUCDancA0Vw225KJ2HJO0yCNr1vq5r+lJTleDaJf0K7iOhjw4SWhu03TMBzYTJ9krmzULQ== dependencies: "@babel/helper-module-imports" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.8" babel-plugin-polyfill-corejs2 "^0.4.10" - babel-plugin-polyfill-corejs3 "^0.10.1" + babel-plugin-polyfill-corejs3 "^0.10.6" babel-plugin-polyfill-regenerator "^0.6.1" semver "^6.3.1" @@ -1284,9 +1286,9 @@ integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== "@babel/runtime@^7.0.0", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.9", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": - version "7.25.4" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.4.tgz#6ef37d678428306e7d75f054d5b1bdb8cf8aa8ee" - integrity sha512-DSgLeL/FNcpXuzav5wfYvHCGvynXkJbn3Zvc3823AEe9nPwW9IK4UoCSS5yGymmQzN0pCPvivtgS6/8U2kkm1w== + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.6.tgz#9afc3289f7184d8d7f98b099884c26317b9264d2" + integrity sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ== dependencies: regenerator-runtime "^0.14.0" @@ -1349,7 +1351,20 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/traverse@^7.24.7", "@babel/traverse@^7.24.8", "@babel/traverse@^7.25.0", "@babel/traverse@^7.25.1", "@babel/traverse@^7.25.2", "@babel/traverse@^7.25.3", "@babel/traverse@^7.25.4": +"@babel/traverse@^7.24.7", "@babel/traverse@^7.24.8", "@babel/traverse@^7.25.0", "@babel/traverse@^7.25.4": + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.6.tgz#04fad980e444f182ecf1520504941940a90fea41" + integrity sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ== + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/generator" "^7.25.6" + "@babel/parser" "^7.25.6" + "@babel/template" "^7.25.0" + "@babel/types" "^7.25.6" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/traverse@^7.25.1", "@babel/traverse@^7.25.2", "@babel/traverse@^7.25.3": version "7.25.4" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.4.tgz#648678046990f2957407e3086e97044f13c3e18e" integrity sha512-VJ4XsrD+nOvlXyLzmLzUs/0qjFS4sK30te5yEFlvbbUNEgKaVb2BHZUpAL+ttLPQAHNrsI3zZisbfha5Cvr8vg== @@ -1389,7 +1404,16 @@ "@babel/helper-validator-identifier" "^7.24.7" to-fast-properties "^2.0.0" -"@babel/types@^7.24.7", "@babel/types@^7.24.8", "@babel/types@^7.24.9", "@babel/types@^7.25.0", "@babel/types@^7.25.2", "@babel/types@^7.25.4", "@babel/types@^7.4.4": +"@babel/types@^7.24.7", "@babel/types@^7.24.8", "@babel/types@^7.25.0", "@babel/types@^7.25.4", "@babel/types@^7.25.6": + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.6.tgz#893942ddb858f32ae7a004ec9d3a76b3463ef8e6" + integrity sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw== + dependencies: + "@babel/helper-string-parser" "^7.24.8" + "@babel/helper-validator-identifier" "^7.24.7" + to-fast-properties "^2.0.0" + +"@babel/types@^7.24.9", "@babel/types@^7.25.2", "@babel/types@^7.4.4": version "7.25.4" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.4.tgz#6bcb46c72fdf1012a209d016c07f769e10adcb5f" integrity sha512-zQ1ijeeCXVEh+aNL0RlmkPkG8HUiDcU2pzQQFjtbntgAczRASFzj4H+6+bV+dy1ntKR14I/DypeuRG1uma98iQ== @@ -3482,14 +3506,6 @@ babel-plugin-polyfill-corejs2@^0.4.10: "@babel/helper-define-polyfill-provider" "^0.6.2" semver "^6.3.1" -babel-plugin-polyfill-corejs3@^0.10.1: - version "0.10.4" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz#789ac82405ad664c20476d0233b485281deb9c77" - integrity sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg== - dependencies: - "@babel/helper-define-polyfill-provider" "^0.6.1" - core-js-compat "^3.36.1" - babel-plugin-polyfill-corejs3@^0.10.6: version "0.10.6" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz#2deda57caef50f59c525aeb4964d3b2f867710c7" @@ -3751,7 +3767,7 @@ charenc@0.0.2: resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA== -chokidar@^3.4.0: +chokidar@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== @@ -3952,7 +3968,7 @@ cookie@0.6.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== -core-js-compat@^3.36.1, core-js-compat@^3.37.0: +core-js-compat@^3.37.0: version "3.37.1" resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.37.1.tgz#c844310c7852f4bdf49b8d339730b97e17ff09ee" integrity sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg== @@ -4606,11 +4622,16 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -escalade@^3.1.1, escalade@^3.1.2: +escalade@^3.1.1: version "3.1.2" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== +escalade@^3.1.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + escape-html@^1.0.3, escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -7597,9 +7618,9 @@ pbf@^3.2.1: resolve-protobuf-schema "^2.1.0" picocolors@^1.0.0, picocolors@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" - integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== + version "1.1.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.0.tgz#5358b76a78cde483ba5cef6a9dc9671440b27d59" + integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw== picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" From df82c8a84fa1f80073221ba416bf1f81c0102c94 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2024 22:21:20 +0100 Subject: [PATCH 047/154] Update dependency @sentry/browser to v8.27.0 (#12956) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 136 +++++++++++++++++++++++++++--------------------------- 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/yarn.lock b/yarn.lock index abca74e6b06..dcd0d23f934 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2347,76 +2347,76 @@ resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.1.0.tgz#f817d1d3265ac5415dadc67edab30ae196696438" integrity sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg== -"@sentry-internal/browser-utils@8.26.0": - version "8.26.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.26.0.tgz#3c76015e1bddde6775e6a7e115fbb494f247fed1" - integrity sha512-O2Tj+WK33/ZVp5STnz6ZL0OO+/Idk2KqsH0ITQkQmyZ2z0kdzWOeqK7s7q3/My6rB1GfPcyqPcBBv4dVv92FYQ== - dependencies: - "@sentry/core" "8.26.0" - "@sentry/types" "8.26.0" - "@sentry/utils" "8.26.0" - -"@sentry-internal/feedback@8.26.0": - version "8.26.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.26.0.tgz#c29a2a4d97d9a9b56344521f3dbb16e2c40d799e" - integrity sha512-hQtw1gg8n6ERK1UH47F7ZI1zOsbhu0J2VX+TrnkpaQR2FgxDW1oe9Ja6oCV4CQKuR4w+1ZI/Kj4imSt0K33kEw== - dependencies: - "@sentry/core" "8.26.0" - "@sentry/types" "8.26.0" - "@sentry/utils" "8.26.0" - -"@sentry-internal/replay-canvas@8.26.0": - version "8.26.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.26.0.tgz#005e4ebed631d0e505e117d42ae8bc64748628d1" - integrity sha512-2CFQW6f9aJHIo/DqmqYa9PaYoLn1o36ywc0h8oyGrD4oPCbrnE5F++PmTdc71GBODu41HBn/yoCTLmxOD+UjpA== - dependencies: - "@sentry-internal/replay" "8.26.0" - "@sentry/core" "8.26.0" - "@sentry/types" "8.26.0" - "@sentry/utils" "8.26.0" - -"@sentry-internal/replay@8.26.0": - version "8.26.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.26.0.tgz#7d01b1915343bf8ca3d9ef7500994d4a45f3785e" - integrity sha512-JDY7W2bswlp5c3483lKP4kcb75fHNwGNfwD8x8FsY9xMjv7nxeXjLpR5cCEk1XqPq2+n6w4j7mJOXhEXGiUIKg== - dependencies: - "@sentry-internal/browser-utils" "8.26.0" - "@sentry/core" "8.26.0" - "@sentry/types" "8.26.0" - "@sentry/utils" "8.26.0" +"@sentry-internal/browser-utils@8.28.0": + version "8.28.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.28.0.tgz#bddc58c154e898195d45e971e058e237085bbcc2" + integrity sha512-tE9++KEy8SlqibTmYymuxFVAnutsXBqrwQ936WJbjaMfkqXiro7C1El0ybkprskd0rKS7kln20Q6nQlNlMEoTA== + dependencies: + "@sentry/core" "8.28.0" + "@sentry/types" "8.28.0" + "@sentry/utils" "8.28.0" + +"@sentry-internal/feedback@8.28.0": + version "8.28.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.28.0.tgz#f278548ead037ad38e54d24b1afcdc1f711c5715" + integrity sha512-5vYunPCDBLCJ8QNnhepacdYheiN+UtYxpGAIaC/zjBC1nDuBgWs+TfKPo1UlO/1sesfgs9ibpxtShOweucL61g== + dependencies: + "@sentry/core" "8.28.0" + "@sentry/types" "8.28.0" + "@sentry/utils" "8.28.0" + +"@sentry-internal/replay-canvas@8.28.0": + version "8.28.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.28.0.tgz#6a08541f9fecd912b7334c693a403469c9e34a89" + integrity sha512-RfpYHDHMUKGeEdx41QtHITjEn6P3tGaDPHvatqdrD3yv4j+wbJ6laX1PrIxCpGFUtjdzkqi/KUcvUd2kzbH/FA== + dependencies: + "@sentry-internal/replay" "8.28.0" + "@sentry/core" "8.28.0" + "@sentry/types" "8.28.0" + "@sentry/utils" "8.28.0" + +"@sentry-internal/replay@8.28.0": + version "8.28.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.28.0.tgz#a84523066ab363239ef6b4180726908cab510e5f" + integrity sha512-70jvzzOL5O74gahgXKyRkZgiYN93yly5gq+bbj4/6NRQ+EtPd285+ccy0laExdfyK0ugvvwD4v+1MQit52OAsg== + dependencies: + "@sentry-internal/browser-utils" "8.28.0" + "@sentry/core" "8.28.0" + "@sentry/types" "8.28.0" + "@sentry/utils" "8.28.0" "@sentry/browser@^8.0.0": - version "8.26.0" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.26.0.tgz#749508ca8d1da857930f41430eb3a77102712f46" - integrity sha512-e5s6eKlwLZWzTwQcBwqyAGZMMuQROW9Z677VzwkSyREWAIkKjfH2VBxHATnNGc0IVkNHjD7iH3ixo3C0rLKM3w== - dependencies: - "@sentry-internal/browser-utils" "8.26.0" - "@sentry-internal/feedback" "8.26.0" - "@sentry-internal/replay" "8.26.0" - "@sentry-internal/replay-canvas" "8.26.0" - "@sentry/core" "8.26.0" - "@sentry/types" "8.26.0" - "@sentry/utils" "8.26.0" - -"@sentry/core@8.26.0": - version "8.26.0" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.26.0.tgz#0673a9e2c5b699cf1bde1ed073a345cc393577da" - integrity sha512-g/tVmTZD4GNbLFf++hKJfBpcCAtduFEMLnbfa9iT/QEZjlmP+EzY+GsH9bafM5VsNe8DiOUp+kJKWtShzlVdBA== - dependencies: - "@sentry/types" "8.26.0" - "@sentry/utils" "8.26.0" - -"@sentry/types@8.26.0": - version "8.26.0" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.26.0.tgz#c999fdd9e52587570f723d2370244bad8f79b571" - integrity sha512-zKmh6SWsJh630rpt7a9vP4Cm4m1C2gDTUqUiH565CajCL/4cePpNWYrNwalSqsOSL7B9OrczA1+n6a6XvND+ng== - -"@sentry/utils@8.26.0": - version "8.26.0" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-8.26.0.tgz#c6629f0f2bc8cbc4fddd124770e9063b4e2d1c65" - integrity sha512-xvlPU9Hd2BlyT+FhWHGNwnxWqdVRk2AHnDtVcW4Ma0Ri5EwS+uy4Jeik5UkSv8C5RVb9VlxFmS8LN3I1MPJsLw== - dependencies: - "@sentry/types" "8.26.0" + version "8.28.0" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.28.0.tgz#e3d28e7a917c212418c887b4fabacc6ed88baea8" + integrity sha512-i/gjMYzIGQiPFH1pCbdnTwH9xs9mTAqzN+goP3GWX5a58frc7h8vxyA/5z0yMd0aCW6U8mVxnoAT72vGbKbx0g== + dependencies: + "@sentry-internal/browser-utils" "8.28.0" + "@sentry-internal/feedback" "8.28.0" + "@sentry-internal/replay" "8.28.0" + "@sentry-internal/replay-canvas" "8.28.0" + "@sentry/core" "8.28.0" + "@sentry/types" "8.28.0" + "@sentry/utils" "8.28.0" + +"@sentry/core@8.28.0": + version "8.28.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.28.0.tgz#dd28fa913c296b443d4070f147c63e81edf429c8" + integrity sha512-+If9uubvpZpvaQQw4HLiKPhrSS9/KcoA/AcdQkNm+5CVwAoOmDPtyYfkPBgfo2hLZnZQqR1bwkz/PrNoOm+gqA== + dependencies: + "@sentry/types" "8.28.0" + "@sentry/utils" "8.28.0" + +"@sentry/types@8.28.0": + version "8.28.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.28.0.tgz#a1cfc004d5714679cb3fed06c27298b0275d13b5" + integrity sha512-hOfqfd92/AzBrEdMgmmV1VfOXJbIfleFTnerRl0mg/+CcNgP/6+Fdonp354TD56ouWNF2WkOM6sEKSXMWp6SEQ== + +"@sentry/utils@8.28.0": + version "8.28.0" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-8.28.0.tgz#0feb46015033879b2a3cee4c0661386610025f47" + integrity sha512-smhk7PJpvDMQ2DB5p2qn9UeoUHdU41IgjMmS2xklZpa8tjzBTxDeWpGvrX2fuH67D9bAJuLC/XyZjJCHLoEW5g== + dependencies: + "@sentry/types" "8.28.0" "@sinclair/typebox@^0.27.8": version "0.27.8" From 33404e42fd0efda6b2eda191e61c7656e328798e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 4 Sep 2024 00:35:18 +0100 Subject: [PATCH 048/154] Update stylelint (#12958) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 52 ++++++++++++++++++++++------------------------------ 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/yarn.lock b/yarn.lock index dcd0d23f934..0303b060723 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1443,17 +1443,17 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@csstools/css-parser-algorithms@^3.0.0": +"@csstools/css-parser-algorithms@^3.0.1": version "3.0.1" resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.1.tgz#f14ade63bae5f6025ac85c7d03fe47a7ca0e58af" integrity sha512-lSquqZCHxDfuTg/Sk2hiS0mcSFCEBuj49JfzPHJogDBT0mGCyY5A1AQzBWngitrp7i1/HAZpIgzF/VjhOEIJIg== -"@csstools/css-tokenizer@^3.0.0": +"@csstools/css-tokenizer@^3.0.1": version "3.0.1" resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-3.0.1.tgz#9dd9b10084f3011290f96789598091e5bcb3c29a" integrity sha512-UBqaiu7kU0lfvaP982/o3khfXccVlHPWp0/vwwiIgDF0GmqqqxoiXC/6FCjlS9u92f7CoEz6nXKQnrn1kIAkOw== -"@csstools/media-query-list-parser@^3.0.0": +"@csstools/media-query-list-parser@^3.0.1": version "3.0.1" resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-3.0.1.tgz#9474e08e6d7767cf68c56bf1581b59d203360cb0" integrity sha512-HNo8gGD02kHmcbX6PvCoUuOQvn4szyB9ca63vZHKX5A81QytgDG4oxG4IaEfHTlEZSZ6MjPEMWIVU+zF2PZcgw== @@ -5157,9 +5157,9 @@ file-entry-cache@^6.0.1: flat-cache "^3.0.4" file-entry-cache@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-9.0.0.tgz#4478e7ceaa5191fa9676a2daa7030211c31b1e7e" - integrity sha512-6MgEugi8p2tiUhqO7GnPsmbCCzj0YRCwwaTbpGRyKZesjRSzkqkAE9fPp7V2yMs5hwfgbQLgdvSSkGNg1s5Uvw== + version "9.1.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-9.1.0.tgz#2e66ad98ce93f49aed1b178c57b0b5741591e075" + integrity sha512-/pqPFG+FdxWQj+/WSuzXSDaNzxgTLr/OrR1QuqfEZzDakpdYE70PwUxL7BPUa8hpjbvY1+qvCl8k+8Tq34xJgg== dependencies: flat-cache "^5.0.0" @@ -7126,7 +7126,7 @@ methods@~1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== -micromatch@^4.0.4, micromatch@~4.0.7: +micromatch@^4.0.4, micromatch@^4.0.8, micromatch@~4.0.7: version "4.0.8" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== @@ -7134,14 +7134,6 @@ micromatch@^4.0.4, micromatch@~4.0.7: braces "^3.0.3" picomatch "^2.3.1" -micromatch@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.7.tgz#33e8190d9fe474a9895525f5618eee136d46c2e5" - integrity sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q== - dependencies: - braces "^3.0.3" - picomatch "^2.3.1" - mime-db@1.52.0: version "1.52.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" @@ -7740,9 +7732,9 @@ postcss@^8.3.11: source-map-js "^1.0.2" postcss@^8.4.41: - version "8.4.41" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.41.tgz#d6104d3ba272d882fe18fc07d15dc2da62fa2681" - integrity sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ== + version "8.4.44" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.44.tgz#d56834ef6508610ba224bb22b2457b2169ed0480" + integrity sha512-Aweb9unOEpQ3ezu4Q00DPvvM2ZTUitJdNKeP/+uQgr1IBIqu574IaZoURId7BKtWMREwzKa9OgzPzezWGPWFQw== dependencies: nanoid "^3.3.7" picocolors "^1.0.1" @@ -8865,9 +8857,9 @@ stylelint-config-standard@^36.0.0: stylelint-config-recommended "^14.0.1" stylelint-scss@^6.0.0: - version "6.5.0" - resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-6.5.0.tgz#c5495f254195c41b97f9bc995e4d3725b375447a" - integrity sha512-yOnYlr71wrTPT3rYyUurgTj6Rw7JUtzsZQsiPEjvs+k/yqoYHdweqpw6XN/ARpxjAuvJpddoMUvV8aAIpvUwTg== + version "6.5.1" + resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-6.5.1.tgz#bcb6a4ada71a0adbf181e155548e5f25ee4aeece" + integrity sha512-ZLqdqihm6uDYkrsOeD6YWb+stZI8Wn92kUNDhE4M+g9g1aCnRv0JlOrttFiAJJwaNzpdQgX3YJb5vDQXVuO9Ww== dependencies: css-tree "2.3.1" is-plain-object "5.0.0" @@ -8878,13 +8870,13 @@ stylelint-scss@^6.0.0: postcss-value-parser "^4.2.0" stylelint@^16.1.0: - version "16.8.2" - resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-16.8.2.tgz#7fda18b919a36e206e897417d4720baceb3af122" - integrity sha512-fInKATippQhcSm7AB+T32GpI+626yohrg33GkFT/5jzliUw5qhlwZq2UQQwgl3HsHrf09oeARi0ZwgY/UWEv9A== + version "16.9.0" + resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-16.9.0.tgz#81615c0608b9dc645486e08e35c6c9206e1ba132" + integrity sha512-31Nm3WjxGOBGpQqF43o3wO9L5AC36TPIe6030Lnm13H3vDMTcS21DrLh69bMX+DBilKqMMVLian4iG6ybBoNRQ== dependencies: - "@csstools/css-parser-algorithms" "^3.0.0" - "@csstools/css-tokenizer" "^3.0.0" - "@csstools/media-query-list-parser" "^3.0.0" + "@csstools/css-parser-algorithms" "^3.0.1" + "@csstools/css-tokenizer" "^3.0.1" + "@csstools/media-query-list-parser" "^3.0.1" "@csstools/selector-specificity" "^4.0.0" "@dual-bundle/import-meta-resolve" "^4.1.0" balanced-match "^2.0.0" @@ -8906,7 +8898,7 @@ stylelint@^16.1.0: known-css-properties "^0.34.0" mathml-tag-names "^2.1.3" meow "^13.2.0" - micromatch "^4.0.7" + micromatch "^4.0.8" normalize-path "^3.0.0" picocolors "^1.0.1" postcss "^8.4.41" @@ -8917,7 +8909,7 @@ stylelint@^16.1.0: resolve-from "^5.0.0" string-width "^4.2.3" strip-ansi "^7.1.0" - supports-hyperlinks "^3.0.0" + supports-hyperlinks "^3.1.0" svg-tags "^1.0.0" table "^6.8.2" write-file-atomic "^5.0.1" @@ -8950,7 +8942,7 @@ supports-color@^8.0.0: dependencies: has-flag "^4.0.0" -supports-hyperlinks@^3.0.0: +supports-hyperlinks@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-3.1.0.tgz#b56150ff0173baacc15f21956450b61f2b18d3ac" integrity sha512-2rn0BZ+/f7puLOHZm1HOJfwBggfaHXUpPUSSG/SWM4TWp5KCfmNYwnC3hruy2rZlMnmWZ+QAGpZfchu3f3695A== From dbc8c9f5b1a8c1281cf154acb8d323abdfe86d38 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 4 Sep 2024 00:35:44 +0100 Subject: [PATCH 049/154] Update peter-evans/create-pull-request action to v7 (#12960) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/playwright-image-updates.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/playwright-image-updates.yaml b/.github/workflows/playwright-image-updates.yaml index 761d8a8b6c1..99f74653961 100644 --- a/.github/workflows/playwright-image-updates.yaml +++ b/.github/workflows/playwright-image-updates.yaml @@ -20,7 +20,7 @@ jobs: - name: Create Pull Request id: cpr - uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6 + uses: peter-evans/create-pull-request@4320041ed380b20e97d388d56a7fb4f9b8c20e79 # v7 with: token: ${{ secrets.ELEMENT_BOT_TOKEN }} branch: actions/playwright-image-updates From 9d8c5b6a1cc815e379248ed8ee9fe8363f2b93e3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2024 23:36:25 +0000 Subject: [PATCH 050/154] Update dependency @testing-library/jest-dom to v6.5.0 (#12957) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index 0303b060723..1140d3a0641 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2466,12 +2466,11 @@ pretty-format "^27.0.2" "@testing-library/jest-dom@^6.0.0": - version "6.4.8" - resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.4.8.tgz#9c435742b20c6183d4e7034f2b329d562c079daa" - integrity sha512-JD0G+Zc38f5MBHA4NgxQMR5XtO5Jx9g86jqturNTt2WUfRmLDIY7iKkWHDCCTiDuFMre6nxAD5wHw9W5kI4rGw== + version "6.5.0" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.5.0.tgz#50484da3f80fb222a853479f618a9ce5c47bfe54" + integrity sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA== dependencies: "@adobe/css-tools" "^4.4.0" - "@babel/runtime" "^7.9.2" aria-query "^5.0.0" chalk "^3.0.0" css.escape "^1.5.1" From 60fe70b3cc3a9c6d36c659223db53a3d8aca5448 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Wed, 4 Sep 2024 11:07:19 +0200 Subject: [PATCH 051/154] Add a prefix to file, poll, image, video and audio in the pinned message banner (#12950) * Move event preview to its own component * Remove unused parameter * Add prefix to file, audio, video and image in the pinned message banner * Add prefix to poll in the pinned message banner * Add tests --- res/css/views/rooms/_PinnedMessageBanner.pcss | 4 + .../views/rooms/PinnedMessageBanner.tsx | 90 ++++++- src/i18n/strings/en_EN.json | 8 + .../views/rooms/PinnedMessageBanner-test.tsx | 28 +- .../PinnedMessageBanner-test.tsx.snap | 239 ++++++++++++++++++ 5 files changed, 359 insertions(+), 10 deletions(-) diff --git a/res/css/views/rooms/_PinnedMessageBanner.pcss b/res/css/views/rooms/_PinnedMessageBanner.pcss index 31cf3ece8a0..838b356ac4d 100644 --- a/res/css/views/rooms/_PinnedMessageBanner.pcss +++ b/res/css/views/rooms/_PinnedMessageBanner.pcss @@ -94,6 +94,10 @@ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + + .mx_PinnedMessageBanner_prefix { + font: var(--cpd-font-body-sm-semibold); + } } .mx_PinnedMessageBanner_redactedMessage { diff --git a/src/components/views/rooms/PinnedMessageBanner.tsx b/src/components/views/rooms/PinnedMessageBanner.tsx index 0479e30ce9e..3f6a1b14a61 100644 --- a/src/components/views/rooms/PinnedMessageBanner.tsx +++ b/src/components/views/rooms/PinnedMessageBanner.tsx @@ -17,7 +17,7 @@ import React, { JSX, useEffect, useMemo, useState } from "react"; import { Icon as PinIcon } from "@vector-im/compound-design-tokens/icons/pin-solid.svg"; import { Button } from "@vector-im/compound-web"; -import { Room } from "matrix-js-sdk/src/matrix"; +import { M_POLL_START, MatrixEvent, MsgType, Room } from "matrix-js-sdk/src/matrix"; import classNames from "classnames"; import { usePinnedEvents, useSortedFetchedPinnedEvents } from "../../../hooks/usePinnedEvents"; @@ -59,16 +59,10 @@ export function PinnedMessageBanner({ room, permalinkCreator }: PinnedMessageBan const [currentEventIndex, setCurrentEventIndex] = useState(eventCount - 1); // When the number of pinned messages changes, we want to display the last message useEffect(() => { - setCurrentEventIndex((currentEventIndex) => eventCount - 1); + setCurrentEventIndex(() => eventCount - 1); }, [eventCount]); const pinnedEvent = pinnedEvents[currentEventIndex]; - // Generate a preview for the pinned event - const eventPreview = useMemo(() => { - if (!pinnedEvent || pinnedEvent.isRedacted() || pinnedEvent.isDecryptionFailure()) return null; - return MessagePreviewStore.instance.generatePreviewForEvent(pinnedEvent); - }, [pinnedEvent]); - if (!pinnedEvent) return null; const shouldUseMessageEvent = pinnedEvent.isRedacted() || pinnedEvent.isDecryptionFailure(); @@ -116,7 +110,7 @@ export function PinnedMessageBanner({ room, permalinkCreator }: PinnedMessageBan )}
)} - {eventPreview && {eventPreview}} + {/* In case of redacted event, we want to display the nice sentence of the message event like in the timeline or in the pinned message list */} {shouldUseMessageEvent && (
@@ -135,6 +129,84 @@ export function PinnedMessageBanner({ room, permalinkCreator }: PinnedMessageBan ); } +/** + * The props for the {@link EventPreview} component. + */ +interface EventPreviewProps { + /** + * The pinned event to display the preview for + */ + pinnedEvent: MatrixEvent; +} + +/** + * A component that displays a preview for the pinned event. + */ +function EventPreview({ pinnedEvent }: EventPreviewProps): JSX.Element | null { + const preview = useEventPreview(pinnedEvent); + if (!preview) return null; + + const prefix = getPreviewPrefix(pinnedEvent.getType(), pinnedEvent.getContent().msgtype as MsgType); + if (!prefix) + return ( + + {preview} + + ); + + return ( + + {_t( + "room|pinned_message_banner|preview", + { + prefix, + preview, + }, + { + bold: (sub) => {sub}, + }, + )} + + ); +} + +/** + * Hooks to generate a preview for the pinned event. + * @param pinnedEvent + */ +function useEventPreview(pinnedEvent: MatrixEvent | null): string | null { + return useMemo(() => { + if (!pinnedEvent || pinnedEvent.isRedacted() || pinnedEvent.isDecryptionFailure()) return null; + return MessagePreviewStore.instance.generatePreviewForEvent(pinnedEvent); + }, [pinnedEvent]); +} + +/** + * Get the prefix for the preview based on the type and the message type. + * @param type + * @param msgType + */ +function getPreviewPrefix(type: string, msgType: MsgType): string | null { + switch (type) { + case M_POLL_START.name: + return _t("room|pinned_message_banner|prefix|poll"); + default: + } + + switch (msgType) { + case MsgType.Audio: + return _t("room|pinned_message_banner|prefix|audio"); + case MsgType.Image: + return _t("room|pinned_message_banner|prefix|image"); + case MsgType.Video: + return _t("room|pinned_message_banner|prefix|video"); + case MsgType.File: + return _t("room|pinned_message_banner|prefix|file"); + default: + return null; + } +} + const MAX_INDICATORS = 3; /** diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index e0b85cc4e16..b1f4fb46070 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2053,6 +2053,14 @@ "button_view_all": "View all", "description": "This room has pinned messages. Click to view them.", "go_to_message": "View the pinned message in the timeline.", + "prefix": { + "audio": "Audio", + "file": "File", + "image": "Image", + "poll": "Poll", + "video": "Video" + }, + "preview": "%(prefix)s: %(preview)s", "title": "%(index)s of %(length)s Pinned messages" }, "read_topic": "Click to read topic", diff --git a/test/components/views/rooms/PinnedMessageBanner-test.tsx b/test/components/views/rooms/PinnedMessageBanner-test.tsx index 0febf21a91d..0231d18ffcb 100644 --- a/test/components/views/rooms/PinnedMessageBanner-test.tsx +++ b/test/components/views/rooms/PinnedMessageBanner-test.tsx @@ -22,7 +22,7 @@ import userEvent from "@testing-library/user-event"; import * as pinnedEventHooks from "../../../../src/hooks/usePinnedEvents"; import { PinnedMessageBanner } from "../../../../src/components/views/rooms/PinnedMessageBanner"; import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks"; -import { stubClient } from "../../../test-utils"; +import { makePollStartEvent, stubClient } from "../../../test-utils"; import dis from "../../../../src/dispatcher/dispatcher"; import RightPanelStore from "../../../../src/stores/right-panel/RightPanelStore"; import { RightPanelPhases } from "../../../../src/stores/right-panel/RightPanelStorePhases"; @@ -185,6 +185,32 @@ describe("", () => { }); }); + it.each([ + ["m.file", "File"], + ["m.audio", "Audio"], + ["m.video", "Video"], + ["m.image", "Image"], + ])("should display the %s event type", (msgType, label) => { + const body = `Message with ${msgType} type`; + const event = makePinEvent({ content: { body, msgtype: msgType } }); + jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([event.getId()!]); + jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event]); + + const { asFragment } = renderBanner(); + expect(screen.getByTestId("banner-message")).toHaveTextContent(`${label}: ${body}`); + expect(asFragment()).toMatchSnapshot(); + }); + + it("should display display a poll event", async () => { + const event = makePollStartEvent("Alice?", userId); + jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([event.getId()!]); + jest.spyOn(pinnedEventHooks, "useSortedFetchedPinnedEvents").mockReturnValue([event]); + + const { asFragment } = renderBanner(); + expect(screen.getByTestId("banner-message")).toHaveTextContent("Poll: Alice?"); + expect(asFragment()).toMatchSnapshot(); + }); + describe("Right button", () => { beforeEach(() => { jest.spyOn(pinnedEventHooks, "usePinnedEvents").mockReturnValue([event1.getId()!, event2.getId()!]); diff --git a/test/components/views/rooms/__snapshots__/PinnedMessageBanner-test.tsx.snap b/test/components/views/rooms/__snapshots__/PinnedMessageBanner-test.tsx.snap index 90b1efb6351..bc733146ba8 100644 --- a/test/components/views/rooms/__snapshots__/PinnedMessageBanner-test.tsx.snap +++ b/test/components/views/rooms/__snapshots__/PinnedMessageBanner-test.tsx.snap @@ -1,5 +1,52 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[` should display display a poll event 1`] = ` + +
+ +
+
+`; + exports[` should display the last message when the pinned event array changed 1`] = `
should display the last message when the pinned
Third pinned message @@ -69,6 +117,194 @@ exports[` should display the last message when the pinned
`; +exports[` should display the m.audio event type 1`] = ` + +
+ +
+
+`; + +exports[` should display the m.file event type 1`] = ` + +
+ +
+
+`; + +exports[` should display the m.image event type 1`] = ` + +
+ +
+
+`; + +exports[` should display the m.video event type 1`] = ` + +
+ +
+
+`; + exports[` should render 2 pinned event 1`] = `
should render 2 pinned event 1`] = `
Second pinned message @@ -185,6 +422,7 @@ exports[` should render 4 pinned event 1`] = `
Fourth pinned message @@ -233,6 +471,7 @@ exports[` should render a single pinned event 1`] = ` /> First pinned message From ab1e28bfeaf2ce2879f67fead92c6d954733e890 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Wed, 4 Sep 2024 11:07:53 +0200 Subject: [PATCH 052/154] Compute with of content of pinned event tile (#12951) --- res/css/views/rooms/_PinnedEventTile.pcss | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/res/css/views/rooms/_PinnedEventTile.pcss b/res/css/views/rooms/_PinnedEventTile.pcss index b37e3724fcd..b727a5ff5a1 100644 --- a/res/css/views/rooms/_PinnedEventTile.pcss +++ b/res/css/views/rooms/_PinnedEventTile.pcss @@ -23,7 +23,9 @@ limitations under the License. display: flex; flex-direction: column; gap: var(--cpd-space-1x); - width: 100%; + /* Remove avatar width and space between the avatar and the wrapper */ + /* We need it to make the location fit */ + width: calc(100% - var(--cpd-space-4x) - 32px); .mx_PinnedEventTile_top { display: flex; From 1e3320da1ee63308dd10f0c67ce3fea30746ffbb Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Wed, 4 Sep 2024 11:45:18 +0200 Subject: [PATCH 053/154] Pinned message list: prevent sender name to overflow pinned event tile (#12947) * Prevent sender name to overflow pinned event tile * Add tooltip to display the sender name --- res/css/views/rooms/_PinnedEventTile.pcss | 3 +++ src/components/views/rooms/PinnedEventTile.tsx | 14 ++++++-------- .../__snapshots__/PinnedMessagesCard-test.tsx.snap | 8 ++++---- .../__snapshots__/PinnedEventTile-test.tsx.snap | 4 ++-- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/res/css/views/rooms/_PinnedEventTile.pcss b/res/css/views/rooms/_PinnedEventTile.pcss index b727a5ff5a1..c415a4da41e 100644 --- a/res/css/views/rooms/_PinnedEventTile.pcss +++ b/res/css/views/rooms/_PinnedEventTile.pcss @@ -26,6 +26,8 @@ limitations under the License. /* Remove avatar width and space between the avatar and the wrapper */ /* We need it to make the location fit */ width: calc(100% - var(--cpd-space-4x) - 32px); + /* Prevent a long sender name to overflow the tile */ + overflow: hidden; .mx_PinnedEventTile_top { display: flex; @@ -37,6 +39,7 @@ limitations under the License. text-overflow: ellipsis; overflow: hidden; white-space: nowrap; + font: var(--cpd-font-body-md-semibold); } } diff --git a/src/components/views/rooms/PinnedEventTile.tsx b/src/components/views/rooms/PinnedEventTile.tsx index 5fb9c07f452..c77cffe7768 100644 --- a/src/components/views/rooms/PinnedEventTile.tsx +++ b/src/components/views/rooms/PinnedEventTile.tsx @@ -17,7 +17,7 @@ limitations under the License. import React, { JSX, useCallback, useState } from "react"; import { EventTimeline, EventType, MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; -import { IconButton, Menu, MenuItem, Separator, Text } from "@vector-im/compound-web"; +import { IconButton, Menu, MenuItem, Separator, Tooltip } from "@vector-im/compound-web"; import { Icon as ViewIcon } from "@vector-im/compound-design-tokens/icons/visibility-on.svg"; import { Icon as UnpinIcon } from "@vector-im/compound-design-tokens/icons/unpin.svg"; import { Icon as ForwardIcon } from "@vector-im/compound-design-tokens/icons/forward.svg"; @@ -86,13 +86,11 @@ export function PinnedEventTile({ event, room, permalinkCreator }: PinnedEventTi
- - {event.sender?.name || sender} - + + + {event.sender?.name || sender} + +
should show two pinned messages 1`] = ` class="mx_PinnedEventTile_top" > @alice:example.org @@ -218,7 +218,7 @@ exports[` should show two pinned messages 1`] = ` class="mx_PinnedEventTile_top" > @alice:example.org @@ -347,7 +347,7 @@ exports[` unpin all should not allow to unpinall 1`] = ` class="mx_PinnedEventTile_top" > @alice:example.org @@ -413,7 +413,7 @@ exports[` unpin all should not allow to unpinall 1`] = ` class="mx_PinnedEventTile_top" > @alice:example.org diff --git a/test/components/views/rooms/__snapshots__/PinnedEventTile-test.tsx.snap b/test/components/views/rooms/__snapshots__/PinnedEventTile-test.tsx.snap index bf8cafe2dc6..5c44de4c4d8 100644 --- a/test/components/views/rooms/__snapshots__/PinnedEventTile-test.tsx.snap +++ b/test/components/views/rooms/__snapshots__/PinnedEventTile-test.tsx.snap @@ -25,7 +25,7 @@ exports[` should render pinned event 1`] = ` class="mx_PinnedEventTile_top" > @alice:server.org @@ -90,7 +90,7 @@ exports[` should render pinned event with thread info 1`] = ` class="mx_PinnedEventTile_top" > @alice:server.org From cdffbdb81ad5d44c4879820ce90b589d087fe996 Mon Sep 17 00:00:00 2001 From: Sahil Silare <32628578+sahil9001@users.noreply.github.com> Date: Thu, 5 Sep 2024 14:31:10 +0530 Subject: [PATCH 054/154] Add error handling for room publish toggle (#12941) * feat: added error handling for room publish toggle * chore: fixed the messages and removed `console.log` * chore: added minor comment that was removed * fix: addressed review comments * fix: fixed linting --- .../views/room_settings/RoomPublishSetting.tsx | 10 ++++++++++ src/i18n/strings/en_EN.json | 2 ++ 2 files changed, 12 insertions(+) diff --git a/src/components/views/room_settings/RoomPublishSetting.tsx b/src/components/views/room_settings/RoomPublishSetting.tsx index 5fddb66a154..f97776bf94b 100644 --- a/src/components/views/room_settings/RoomPublishSetting.tsx +++ b/src/components/views/room_settings/RoomPublishSetting.tsx @@ -21,6 +21,8 @@ import LabelledToggleSwitch from "../elements/LabelledToggleSwitch"; import { _t } from "../../../languageHandler"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import DirectoryCustomisations from "../../../customisations/Directory"; +import Modal from "../../../Modal"; +import ErrorDialog from "../dialogs/ErrorDialog"; interface IProps { roomId: string; @@ -41,6 +43,13 @@ export default class RoomPublishSetting extends React.PureComponent { const valueBefore = this.state.isRoomPublished; const newValue = !valueBefore; @@ -50,6 +59,7 @@ export default class RoomPublishSetting extends React.PureComponent { + this.showError(); // Roll back the local echo on the change this.setState({ isRoomPublished: valueBefore }); }); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b1f4fb46070..e1e44efc61d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2214,6 +2214,8 @@ "error_deleting_alias_description": "There was an error removing that address. It may no longer exist or a temporary error occurred.", "error_deleting_alias_description_forbidden": "You don't have permission to delete the address.", "error_deleting_alias_title": "Error removing address", + "error_publishing": "Unable to publish room", + "error_publishing_detail": "There was an error publishing this room", "error_save_space_settings": "Failed to save space settings.", "error_updating_alias_description": "There was an error updating the room's alternative addresses. It may not be allowed by the server or a temporary failure occurred.", "error_updating_canonical_alias_description": "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.", From 649074273e8178f775449daca4e32a371982586f Mon Sep 17 00:00:00 2001 From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Date: Thu, 5 Sep 2024 10:34:39 +0100 Subject: [PATCH 055/154] Log phases in the verification process (#12963) When the EncryptionPanel (which does the work of the verification flow) changes "phase", log it. I hope this will be helpful when diagnosing stuck verifications. --- src/components/views/right_panel/EncryptionPanel.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/views/right_panel/EncryptionPanel.tsx b/src/components/views/right_panel/EncryptionPanel.tsx index 26ba841f715..85303405000 100644 --- a/src/components/views/right_panel/EncryptionPanel.tsx +++ b/src/components/views/right_panel/EncryptionPanel.tsx @@ -17,6 +17,7 @@ limitations under the License. import React, { useCallback, useEffect, useRef, useState } from "react"; import { VerificationPhase, VerificationRequest, VerificationRequestEvent } from "matrix-js-sdk/src/crypto-api"; import { RoomMember, User } from "matrix-js-sdk/src/matrix"; +import { logger } from "matrix-js-sdk/src/logger"; import EncryptionInfo from "./EncryptionInfo"; import VerificationPanel from "./VerificationPanel"; @@ -48,7 +49,12 @@ const EncryptionPanel: React.FC = (props: IProps) => { // state to show a spinner immediately after clicking "start verification", // before we have a request const [isRequesting, setRequesting] = useState(false); - const [phase, setPhase] = useState(request?.phase); + const [phase, doSetPhase] = useState(request?.phase); + const setPhase = (phase: VerificationPhase | undefined): void => { + logger.debug(`EncryptionPanel: phase now ${phase === undefined ? phase : VerificationPhase[phase]}`); + doSetPhase(phase); + }; + useEffect(() => { setRequest(verificationRequest); if (verificationRequest) { From 26399237f6e6f11acb7b617f8fec548b9f12bf2d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 5 Sep 2024 15:32:02 +0100 Subject: [PATCH 056/154] Update browserslist (#12953) * Update browserslist * Increment version in user-agent string --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: R Midhun Suresh --- package.json | 2 +- test/SupportedBrowser-test.ts | 4 ++-- yarn.lock | 27 ++++++--------------------- 3 files changed, 9 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index 40294aed5f6..a1cbcce0e8b 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "jwt-decode": "4.0.0", "@floating-ui/react": "0.26.11", "@radix-ui/react-id": "1.1.0", - "caniuse-lite": "1.0.30001643" + "caniuse-lite": "1.0.30001655" }, "dependencies": { "@babel/runtime": "^7.12.5", diff --git a/test/SupportedBrowser-test.ts b/test/SupportedBrowser-test.ts index f5cccf221bf..ec73edb1ace 100644 --- a/test/SupportedBrowser-test.ts +++ b/test/SupportedBrowser-test.ts @@ -72,8 +72,8 @@ describe("SupportedBrowser", () => { it.each([ // Safari 17.5 on macOS Sonoma "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15", - // Firefox 127 on macOS Sonoma - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:127.0) Gecko/20100101 Firefox/127.0", + // Firefox 128 on macOS Sonoma + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:128.0) Gecko/20100101 Firefox/128.0", // Edge 126 on Windows "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36 Edg/126.0.2592.113", // Edge 126 on macOS diff --git a/yarn.lock b/yarn.lock index 1140d3a0641..f0f8a5c495e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3641,17 +3641,7 @@ braces@^3.0.3, braces@~3.0.2: dependencies: fill-range "^7.1.1" -browserslist@^4.22.2, browserslist@^4.23.2: - version "4.23.2" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.2.tgz#244fe803641f1c19c28c48c4b6ec9736eb3d32ed" - integrity sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA== - dependencies: - caniuse-lite "^1.0.30001640" - electron-to-chromium "^1.4.820" - node-releases "^2.0.14" - update-browserslist-db "^1.1.0" - -browserslist@^4.23.0, browserslist@^4.23.1, browserslist@^4.23.3: +browserslist@^4.22.2, browserslist@^4.23.0, browserslist@^4.23.1, browserslist@^4.23.2, browserslist@^4.23.3: version "4.23.3" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.3.tgz#debb029d3c93ebc97ffbc8d9cbb03403e227c800" integrity sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA== @@ -3716,10 +3706,10 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@1.0.30001643, caniuse-lite@^1.0.30001640, caniuse-lite@^1.0.30001646: - version "1.0.30001643" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz#9c004caef315de9452ab970c3da71085f8241dbd" - integrity sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg== +caniuse-lite@1.0.30001655, caniuse-lite@^1.0.30001646: + version "1.0.30001655" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001655.tgz#0ce881f5a19a2dcfda2ecd927df4d5c1684b982f" + integrity sha512-jRGVy3iSGO5Uutn2owlb5gR6qsGngTw9ZTb4ali9f3glshcNmJ2noam4Mo9zia5P9Dk3jNNydy7vQjuE5dQmfg== chalk@5.2.0: version "5.2.0" @@ -4403,11 +4393,6 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -electron-to-chromium@^1.4.820: - version "1.5.2" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.2.tgz#6126ad229ce45e781ec54ca40db0504787f23d19" - integrity sha512-kc4r3U3V3WLaaZqThjYz/Y6z8tJe+7K0bbjUVo3i+LWIypVdMx5nXCkwRe6SWbY6ILqLdc1rKcKmr3HoH7wjSQ== - electron-to-chromium@^1.5.4: version "1.5.13" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz#1abf0410c5344b2b829b7247e031f02810d442e6" @@ -7281,7 +7266,7 @@ node-int64@^0.4.0: resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== -node-releases@^2.0.14, node-releases@^2.0.18: +node-releases@^2.0.18: version "2.0.18" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== From 5bfbca9eb075c76ef559cabf37310b75bbcf8799 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Thu, 5 Sep 2024 16:37:24 +0200 Subject: [PATCH 057/154] Migrate all pinning checks and actions into `PinningUtils` (#12964) --- .../context_menus/MessageContextMenu.tsx | 2 +- .../views/dialogs/UnpinAllDialog.tsx | 5 +- .../views/messages/MessageActionBar.tsx | 5 +- .../views/right_panel/PinnedMessagesCard.tsx | 8 +- .../views/rooms/PinnedEventTile.tsx | 22 +--- src/utils/PinningUtils.ts | 49 +++++++- .../views/rooms/PinnedEventTile-test.tsx | 4 + test/utils/PinningUtils-test.ts | 106 +++++++++++++----- 8 files changed, 140 insertions(+), 61 deletions(-) diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 2d5a81a89b6..9da62c7d232 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -177,7 +177,7 @@ export default class MessageContextMenu extends React.Component this.props.mxEvent.getType() !== EventType.RoomServerAcl && this.props.mxEvent.getType() !== EventType.RoomEncryption; - const canPin = PinningUtils.canPinOrUnpin(cli, this.props.mxEvent); + const canPin = PinningUtils.canPin(cli, this.props.mxEvent) || PinningUtils.canUnpin(cli, this.props.mxEvent); this.setState({ canRedact, canPin }); }; diff --git a/src/components/views/dialogs/UnpinAllDialog.tsx b/src/components/views/dialogs/UnpinAllDialog.tsx index ef7439d858e..4b752e820f6 100644 --- a/src/components/views/dialogs/UnpinAllDialog.tsx +++ b/src/components/views/dialogs/UnpinAllDialog.tsx @@ -16,11 +16,12 @@ import React, { JSX } from "react"; import { Button, Text } from "@vector-im/compound-web"; -import { EventType, MatrixClient } from "matrix-js-sdk/src/matrix"; +import { MatrixClient } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; import BaseDialog from "../dialogs/BaseDialog"; import { _t } from "../../../languageHandler"; +import PinningUtils from "../../../utils/PinningUtils.ts"; /** * Properties for {@link UnpinAllDialog}. @@ -59,7 +60,7 @@ export function UnpinAllDialog({ matrixClient, roomId, onFinished }: UnpinAllDia destructive={true} onClick={async () => { try { - await matrixClient.sendStateEvent(roomId, EventType.RoomPinnedEvents, { pinned: [] }, ""); + await PinningUtils.unpinAllEvents(matrixClient, roomId); } catch (e) { logger.error("Failed to unpin all events:", e); } diff --git a/src/components/views/messages/MessageActionBar.tsx b/src/components/views/messages/MessageActionBar.tsx index 9130183e84e..bb870e3dbe4 100644 --- a/src/components/views/messages/MessageActionBar.tsx +++ b/src/components/views/messages/MessageActionBar.tsx @@ -432,7 +432,10 @@ export default class MessageActionBar extends React.PureComponent - state.mayClientSendStateEvent(EventType.RoomPinnedEvents, matrixClient), - ); + const canUnpin = useRoomState(room, () => PinningUtils.userHasPinOrUnpinPermission(matrixClient, room)); /** * Opens the unpin all dialog. diff --git a/src/components/views/rooms/PinnedEventTile.tsx b/src/components/views/rooms/PinnedEventTile.tsx index c77cffe7768..af4ff71916f 100644 --- a/src/components/views/rooms/PinnedEventTile.tsx +++ b/src/components/views/rooms/PinnedEventTile.tsx @@ -41,6 +41,7 @@ import { getForwardableEvent } from "../../../events"; import { OpenForwardDialogPayload } from "../../../dispatcher/payloads/OpenForwardDialogPayload"; import { createRedactEventDialog } from "../dialogs/ConfirmRedactDialog"; import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload"; +import PinningUtils from "../../../utils/PinningUtils.ts"; const AVATAR_SIZE = "32px"; @@ -162,30 +163,17 @@ function PinMenu({ event, room, permalinkCreator }: PinMenuProps): JSX.Element { /** * Whether the client can unpin the event. - * Pin and unpin are using the same permission. + * If the room state change, we want to check again the permission */ - const canUnpin = useRoomState(room, (state) => - state.mayClientSendStateEvent(EventType.RoomPinnedEvents, matrixClient), - ); + const canUnpin = useRoomState(room, () => PinningUtils.canUnpin(matrixClient, event)); /** * Unpin the event. * @param event */ const onUnpin = useCallback(async (): Promise => { - const pinnedEvents = room - .getLiveTimeline() - .getState(EventTimeline.FORWARDS) - ?.getStateEvents(EventType.RoomPinnedEvents, ""); - if (pinnedEvents?.getContent()?.pinned) { - const pinned = pinnedEvents.getContent().pinned; - const index = pinned.indexOf(event.getId()); - if (index !== -1) { - pinned.splice(index, 1); - await matrixClient.sendStateEvent(room.roomId, EventType.RoomPinnedEvents, { pinned }, ""); - } - } - }, [event, room, matrixClient]); + await PinningUtils.pinOrUnpinEvent(matrixClient, event); + }, [event, matrixClient]); const contentActionable = isContentActionable(event); // Get the forwardable event for the given event diff --git a/src/utils/PinningUtils.ts b/src/utils/PinningUtils.ts index 9a20a721b96..66fee10efb7 100644 --- a/src/utils/PinningUtils.ts +++ b/src/utils/PinningUtils.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { MatrixEvent, EventType, M_POLL_START, MatrixClient, EventTimeline } from "matrix-js-sdk/src/matrix"; +import { MatrixEvent, EventType, M_POLL_START, MatrixClient, EventTimeline, Room } from "matrix-js-sdk/src/matrix"; import { isContentActionable } from "./EventUtils"; import SettingsStore from "../settings/SettingsStore"; @@ -71,23 +71,53 @@ export default class PinningUtils { } /** - * Determines if the given event may be pinned or unpinned by the current user. - * This checks if the user has the necessary permissions to pin or unpin the event, and if the event is pinnable. + * Determines if the given event may be pinned or unpinned by the current user + * It doesn't check if the event is pinnable or unpinnable. * @param matrixClient * @param mxEvent + * @private */ - public static canPinOrUnpin(matrixClient: MatrixClient, mxEvent: MatrixEvent): boolean { + private static canPinOrUnpin(matrixClient: MatrixClient, mxEvent: MatrixEvent): boolean { if (!SettingsStore.getValue("feature_pinning")) return false; if (!isContentActionable(mxEvent)) return false; const room = matrixClient.getRoom(mxEvent.getRoomId()); if (!room) return false; + return PinningUtils.userHasPinOrUnpinPermission(matrixClient, room); + } + + /** + * Determines if the given event may be pinned by the current user. + * This checks if the user has the necessary permissions to pin or unpin the event, and if the event is pinnable. + * @param matrixClient + * @param mxEvent + */ + public static canPin(matrixClient: MatrixClient, mxEvent: MatrixEvent): boolean { + return PinningUtils.canPinOrUnpin(matrixClient, mxEvent) && PinningUtils.isPinnable(mxEvent); + } + + /** + * Determines if the given event may be unpinned by the current user. + * This checks if the user has the necessary permissions to pin or unpin the event, and if the event is unpinnable. + * @param matrixClient + * @param mxEvent + */ + public static canUnpin(matrixClient: MatrixClient, mxEvent: MatrixEvent): boolean { + return PinningUtils.canPinOrUnpin(matrixClient, mxEvent) && PinningUtils.isUnpinnable(mxEvent); + } + + /** + * Determines if the current user has permission to pin or unpin events in the given room. + * @param matrixClient + * @param room + */ + public static userHasPinOrUnpinPermission(matrixClient: MatrixClient, room: Room): boolean { return Boolean( room .getLiveTimeline() .getState(EventTimeline.FORWARDS) - ?.mayClientSendStateEvent(EventType.RoomPinnedEvents, matrixClient) && PinningUtils.isPinnable(mxEvent), + ?.mayClientSendStateEvent(EventType.RoomPinnedEvents, matrixClient), ); } @@ -128,4 +158,13 @@ export default class PinningUtils { roomAccountDataPromise, ]); } + + /** + * Unpin all events in the given room. + * @param matrixClient + * @param roomId + */ + public static async unpinAllEvents(matrixClient: MatrixClient, roomId: string): Promise { + await matrixClient.sendStateEvent(roomId, EventType.RoomPinnedEvents, { pinned: [] }, ""); + } } diff --git a/test/components/views/rooms/PinnedEventTile-test.tsx b/test/components/views/rooms/PinnedEventTile-test.tsx index 124138d834a..6d3723019d4 100644 --- a/test/components/views/rooms/PinnedEventTile-test.tsx +++ b/test/components/views/rooms/PinnedEventTile-test.tsx @@ -27,6 +27,7 @@ import dis from "../../../../src/dispatcher/dispatcher"; import { Action } from "../../../../src/dispatcher/actions"; import { getForwardableEvent } from "../../../../src/events"; import { createRedactEventDialog } from "../../../../src/components/views/dialogs/ConfirmRedactDialog"; +import SettingsStore from "../../../../src/settings/SettingsStore.ts"; jest.mock("../../../../src/components/views/dialogs/ConfirmRedactDialog", () => ({ createRedactEventDialog: jest.fn(), @@ -43,7 +44,10 @@ describe("", () => { mockClient = stubClient(); room = new Room(roomId, mockClient, userId); permalinkCreator = new RoomPermalinkCreator(room); + mockClient.getRoom = jest.fn().mockReturnValue(room); jest.spyOn(dis, "dispatch").mockReturnValue(undefined); + // Enable feature_pinning + jest.spyOn(SettingsStore, "getValue").mockReturnValue(true); }); /** diff --git a/test/utils/PinningUtils-test.ts b/test/utils/PinningUtils-test.ts index adfd268bf1b..4f374435824 100644 --- a/test/utils/PinningUtils-test.ts +++ b/test/utils/PinningUtils-test.ts @@ -147,49 +147,65 @@ describe("PinningUtils", () => { }); }); - describe("canPinOrUnpin", () => { - test("should return false if pinning is disabled", () => { - // Disable feature pinning - jest.spyOn(SettingsStore, "getValue").mockReturnValue(false); - const event = makePinEvent(); + describe("canPin & canUnpin", () => { + describe("canPin", () => { + test("should return false if pinning is disabled", () => { + // Disable feature pinning + jest.spyOn(SettingsStore, "getValue").mockReturnValue(false); + const event = makePinEvent(); + + expect(PinningUtils.canPin(matrixClient, event)).toBe(false); + }); - expect(PinningUtils.canPinOrUnpin(matrixClient, event)).toBe(false); - }); + test("should return false if event is not actionable", () => { + mockedIsContentActionable.mockImplementation(() => false); + const event = makePinEvent(); - test("should return false if event is not actionable", () => { - mockedIsContentActionable.mockImplementation(() => false); - const event = makePinEvent(); + expect(PinningUtils.canPin(matrixClient, event)).toBe(false); + }); - expect(PinningUtils.canPinOrUnpin(matrixClient, event)).toBe(false); - }); + test("should return false if no room", () => { + matrixClient.getRoom = jest.fn().mockReturnValue(undefined); + const event = makePinEvent(); - test("should return false if no room", () => { - matrixClient.getRoom = jest.fn().mockReturnValue(undefined); - const event = makePinEvent(); + expect(PinningUtils.canPin(matrixClient, event)).toBe(false); + }); - expect(PinningUtils.canPinOrUnpin(matrixClient, event)).toBe(false); - }); + test("should return false if client cannot send state event", () => { + jest.spyOn( + matrixClient.getRoom(roomId)!.getLiveTimeline().getState(EventTimeline.FORWARDS)!, + "mayClientSendStateEvent", + ).mockReturnValue(false); + const event = makePinEvent(); - test("should return false if client cannot send state event", () => { - jest.spyOn( - matrixClient.getRoom(roomId)!.getLiveTimeline().getState(EventTimeline.FORWARDS)!, - "mayClientSendStateEvent", - ).mockReturnValue(false); - const event = makePinEvent(); + expect(PinningUtils.canPin(matrixClient, event)).toBe(false); + }); - expect(PinningUtils.canPinOrUnpin(matrixClient, event)).toBe(false); - }); + test("should return false if event is not pinnable", () => { + const event = makePinEvent({ type: EventType.RoomCreate }); - test("should return false if event is not pinnable", () => { - const event = makePinEvent({ type: EventType.RoomCreate }); + expect(PinningUtils.canPin(matrixClient, event)).toBe(false); + }); - expect(PinningUtils.canPinOrUnpin(matrixClient, event)).toBe(false); + test("should return true if all conditions are met", () => { + const event = makePinEvent(); + + expect(PinningUtils.canPin(matrixClient, event)).toBe(true); + }); }); - test("should return true if all conditions are met", () => { - const event = makePinEvent(); + describe("canUnpin", () => { + test("should return false if event is not unpinnable", () => { + const event = makePinEvent({ type: EventType.RoomCreate }); - expect(PinningUtils.canPinOrUnpin(matrixClient, event)).toBe(true); + expect(PinningUtils.canUnpin(matrixClient, event)).toBe(false); + }); + + test("should return true if all conditions are met", () => { + const event = makePinEvent(); + + expect(PinningUtils.canUnpin(matrixClient, event)).toBe(true); + }); }); }); @@ -258,4 +274,32 @@ describe("PinningUtils", () => { ); }); }); + + describe("userHasPinOrUnpinPermission", () => { + test("should return true if user can pin or unpin", () => { + expect(PinningUtils.userHasPinOrUnpinPermission(matrixClient, room)).toBe(true); + }); + + test("should return false if client cannot send state event", () => { + jest.spyOn( + matrixClient.getRoom(roomId)!.getLiveTimeline().getState(EventTimeline.FORWARDS)!, + "mayClientSendStateEvent", + ).mockReturnValue(false); + + expect(PinningUtils.userHasPinOrUnpinPermission(matrixClient, room)).toBe(false); + }); + }); + + describe("unpinAllEvents", () => { + it("should unpin all events in the given room", async () => { + await PinningUtils.unpinAllEvents(matrixClient, roomId); + + expect(matrixClient.sendStateEvent).toHaveBeenCalledWith( + roomId, + EventType.RoomPinnedEvents, + { pinned: [] }, + "", + ); + }); + }); }); From bce710e34e25edcf470a57283160be85f69ec218 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Thu, 5 Sep 2024 14:27:40 +0200 Subject: [PATCH 058/154] Upgrade `@matrix-org/analytics-events` to `0.25.0` --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index a1cbcce0e8b..edfe13584f9 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ }, "dependencies": { "@babel/runtime": "^7.12.5", - "@matrix-org/analytics-events": "^0.24.0", + "@matrix-org/analytics-events": "^0.25.0", "@matrix-org/emojibase-bindings": "^1.1.2", "@matrix-org/matrix-wysiwyg": "2.37.9", "@matrix-org/react-sdk-module-api": "^2.4.0", diff --git a/yarn.lock b/yarn.lock index f0f8a5c495e..f872acbe5fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1893,10 +1893,10 @@ resolved "https://registry.yarnpkg.com/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz#497c67a1cef50d1a2459ba60f315e448d2ad87fe" integrity sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q== -"@matrix-org/analytics-events@^0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@matrix-org/analytics-events/-/analytics-events-0.24.0.tgz#21a64537ac975b18e1eb13d9fd0bdc7d448a6039" - integrity sha512-3FDdtqZ+5cMqVffWjFNOIQ7RDFN6XS11kqdtN2ps8uvq5ce8gT0yXQvK37WeKWKZZ5QAKeoMzGhud+lsVcb1xg== +"@matrix-org/analytics-events@^0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@matrix-org/analytics-events/-/analytics-events-0.25.0.tgz#b0b85297dc05a67feaf89cc5d70b80283c988141" + integrity sha512-UCTuMjlJGArMqG9qXGfeNz/XtZDFldwuO+dkqP6Wo1nVdWasoWAOlcimDWQ2JnNFCg+UDZU+HLBdS5juTd6xTg== "@matrix-org/emojibase-bindings@^1.1.2": version "1.1.3" From 08d1b6ceae6b1704f08fa29c7d04d69f9d47996e Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Thu, 5 Sep 2024 16:11:55 +0200 Subject: [PATCH 059/154] Add analytics event for pinned messages --- src/PosthogTrackers.ts | 14 ++++++++++++++ src/TextForEvent.tsx | 14 +++++++++++--- .../views/context_menus/MessageContextMenu.tsx | 7 +++++-- src/components/views/dialogs/UnpinAllDialog.tsx | 2 ++ src/components/views/messages/MessageActionBar.tsx | 8 +++++--- .../views/right_panel/RoomSummaryCard.tsx | 1 + src/components/views/rooms/PinnedEventTile.tsx | 4 ++++ src/components/views/rooms/PinnedMessageBanner.tsx | 6 ++++++ 8 files changed, 48 insertions(+), 8 deletions(-) diff --git a/src/PosthogTrackers.ts b/src/PosthogTrackers.ts index 18b6edc064a..48b5d59300c 100644 --- a/src/PosthogTrackers.ts +++ b/src/PosthogTrackers.ts @@ -17,6 +17,7 @@ limitations under the License. import { PureComponent, SyntheticEvent } from "react"; import { WebScreen as ScreenEvent } from "@matrix-org/analytics-events/types/typescript/WebScreen"; import { Interaction as InteractionEvent } from "@matrix-org/analytics-events/types/typescript/Interaction"; +import { PinUnpinAction } from "@matrix-org/analytics-events/types/typescript/PinUnpinAction"; import PageType from "./PageTypes"; import Views from "./Views"; @@ -106,6 +107,19 @@ export default class PosthogTrackers { name, }); } + + /** + * Track a pin or unpin action on a message. + * @param kind - Is pin or unpin. + * @param from - From where the action is triggered. + */ + public static trackPinUnpinMessage(kind: PinUnpinAction["kind"], from: PinUnpinAction["from"]): void { + PosthogAnalytics.instance.trackEvent({ + eventName: "PinUnpinAction", + kind, + from, + }); + } } export class PosthogScreenTracker extends PureComponent<{ screenName: ScreenName }> { diff --git a/src/TextForEvent.tsx b/src/TextForEvent.tsx index 96eb38f01b1..d85b7519ae6 100644 --- a/src/TextForEvent.tsx +++ b/src/TextForEvent.tsx @@ -40,12 +40,13 @@ import { WIDGET_LAYOUT_EVENT_TYPE } from "./stores/widgets/WidgetLayoutStore"; import { RightPanelPhases } from "./stores/right-panel/RightPanelStorePhases"; import defaultDispatcher from "./dispatcher/dispatcher"; import { RoomSettingsTab } from "./components/views/dialogs/RoomSettingsDialog"; -import AccessibleButton, { ButtonEvent } from "./components/views/elements/AccessibleButton"; +import AccessibleButton from "./components/views/elements/AccessibleButton"; import RightPanelStore from "./stores/right-panel/RightPanelStore"; import { highlightEvent, isLocationEvent } from "./utils/EventUtils"; import { ElementCall } from "./models/Call"; import { textForVoiceBroadcastStoppedEvent, VoiceBroadcastInfoEventType } from "./voice-broadcast"; import { getSenderName } from "./utils/event/getSenderName"; +import PosthogTrackers from "./PosthogTrackers.ts"; function getRoomMemberDisplayname(client: MatrixClient, event: MatrixEvent, userId = event.getSender()): string { const roomId = event.getRoomId(); @@ -563,6 +564,7 @@ function textForPowerEvent(event: MatrixEvent, client: MatrixClient): (() => str } const onPinnedMessagesClick = (): void => { + PosthogTrackers.trackInteraction("PinnedMessageStateEventClick"); RightPanelStore.instance.setCard({ phase: RightPanelPhases.PinnedMessages }, false); }; @@ -590,7 +592,10 @@ function textForPinnedEvent(event: MatrixEvent, client: MatrixClient, allowJSX: a: (sub) => ( highlightEvent(roomId, messageId)} + onClick={() => { + PosthogTrackers.trackInteraction("PinnedMessageStateEventClick"); + highlightEvent(roomId, messageId); + }} > {sub} @@ -623,7 +628,10 @@ function textForPinnedEvent(event: MatrixEvent, client: MatrixClient, allowJSX: a: (sub) => ( highlightEvent(roomId, messageId)} + onClick={() => { + PosthogTrackers.trackInteraction("PinnedMessageStateEventClick"); + highlightEvent(roomId, messageId); + }} > {sub} diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 9da62c7d232..31f6a06cc17 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -60,6 +60,7 @@ import { getShareableLocationEvent } from "../../../events/location/getShareable import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload"; import { CardContext } from "../right_panel/context"; import PinningUtils from "../../../utils/PinningUtils"; +import PosthogTrackers from "../../../PosthogTrackers.ts"; interface IReplyInThreadButton { mxEvent: MatrixEvent; @@ -243,9 +244,11 @@ export default class MessageContextMenu extends React.Component this.closeMenu(); }; - private onPinClick = (): void => { + private onPinClick = (isPinned: boolean): void => { // Pin or unpin in background PinningUtils.pinOrUnpinEvent(MatrixClientPeg.safeGet(), this.props.mxEvent); + PosthogTrackers.trackPinUnpinMessage(isPinned ? "Pin" : "Unpin", "Timeline"); + this.closeMenu(); }; @@ -618,7 +621,7 @@ export default class MessageContextMenu extends React.Component this.onPinClick(isPinned)} /> ); } diff --git a/src/components/views/dialogs/UnpinAllDialog.tsx b/src/components/views/dialogs/UnpinAllDialog.tsx index 4b752e820f6..e6ce5f556b4 100644 --- a/src/components/views/dialogs/UnpinAllDialog.tsx +++ b/src/components/views/dialogs/UnpinAllDialog.tsx @@ -22,6 +22,7 @@ import { logger } from "matrix-js-sdk/src/logger"; import BaseDialog from "../dialogs/BaseDialog"; import { _t } from "../../../languageHandler"; import PinningUtils from "../../../utils/PinningUtils.ts"; +import PosthogTrackers from "../../../PosthogTrackers.ts"; /** * Properties for {@link UnpinAllDialog}. @@ -61,6 +62,7 @@ export function UnpinAllDialog({ matrixClient, roomId, onFinished }: UnpinAllDia onClick={async () => { try { await PinningUtils.unpinAllEvents(matrixClient, roomId); + PosthogTrackers.trackPinUnpinMessage("Unpin", "UnpinAll"); } catch (e) { logger.error("Failed to unpin all events:", e); } diff --git a/src/components/views/messages/MessageActionBar.tsx b/src/components/views/messages/MessageActionBar.tsx index bb870e3dbe4..3c9da8b2940 100644 --- a/src/components/views/messages/MessageActionBar.tsx +++ b/src/components/views/messages/MessageActionBar.tsx @@ -67,6 +67,7 @@ import { GetRelationsForEvent, IEventTileType } from "../rooms/EventTile"; import { VoiceBroadcastInfoEventType } from "../../../voice-broadcast/types"; import { ButtonEvent } from "../elements/AccessibleButton"; import PinningUtils from "../../../utils/PinningUtils"; +import PosthogTrackers from "../../../PosthogTrackers.ts"; interface IOptionsButtonProps { mxEvent: MatrixEvent; @@ -407,12 +408,13 @@ export default class MessageActionBar extends React.PureComponent => { + private onPinClick = async (event: ButtonEvent, isPinned: boolean): Promise => { // Don't open the regular browser or our context menu on right-click event.preventDefault(); event.stopPropagation(); await PinningUtils.pinOrUnpinEvent(MatrixClientPeg.safeGet(), this.props.mxEvent); + PosthogTrackers.trackPinUnpinMessage(isPinned ? "Pin" : "Unpin", "Timeline"); }; public render(): React.ReactNode { @@ -441,8 +443,8 @@ export default class MessageActionBar extends React.PureComponent this.onPinClick(e, isPinned)} + onContextMenu={(e: ButtonEvent) => this.onPinClick(e, isPinned)} key="pin" placement="left" > diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index ece150495e4..f4dcccdae99 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -95,6 +95,7 @@ const onRoomFilesClick = (): void => { }; const onRoomPinsClick = (): void => { + PosthogTrackers.trackInteraction("PinnedMessageRoomInfoButton"); RightPanelStore.instance.pushCard({ phase: RightPanelPhases.PinnedMessages }, true); }; diff --git a/src/components/views/rooms/PinnedEventTile.tsx b/src/components/views/rooms/PinnedEventTile.tsx index af4ff71916f..ed3fa105ec8 100644 --- a/src/components/views/rooms/PinnedEventTile.tsx +++ b/src/components/views/rooms/PinnedEventTile.tsx @@ -42,6 +42,7 @@ import { OpenForwardDialogPayload } from "../../../dispatcher/payloads/OpenForwa import { createRedactEventDialog } from "../dialogs/ConfirmRedactDialog"; import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload"; import PinningUtils from "../../../utils/PinningUtils.ts"; +import PosthogTrackers from "../../../PosthogTrackers.ts"; const AVATAR_SIZE = "32px"; @@ -152,6 +153,8 @@ function PinMenu({ event, room, permalinkCreator }: PinMenuProps): JSX.Element { * View the event in the timeline. */ const onViewInTimeline = useCallback(() => { + PosthogTrackers.trackInteraction("PinnedMessageListViewTimeline"); + dis.dispatch({ action: Action.ViewRoom, event_id: event.getId(), @@ -173,6 +176,7 @@ function PinMenu({ event, room, permalinkCreator }: PinMenuProps): JSX.Element { */ const onUnpin = useCallback(async (): Promise => { await PinningUtils.pinOrUnpinEvent(matrixClient, event); + PosthogTrackers.trackPinUnpinMessage("Unpin", "MessagePinningList"); }, [event, matrixClient]); const contentActionable = isContentActionable(event); diff --git a/src/components/views/rooms/PinnedMessageBanner.tsx b/src/components/views/rooms/PinnedMessageBanner.tsx index 3f6a1b14a61..8c9d2cf252d 100644 --- a/src/components/views/rooms/PinnedMessageBanner.tsx +++ b/src/components/views/rooms/PinnedMessageBanner.tsx @@ -32,6 +32,7 @@ import dis from "../../../dispatcher/dispatcher"; import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; import { Action } from "../../../dispatcher/actions"; import MessageEvent from "../messages/MessageEvent"; +import PosthogTrackers from "../../../PosthogTrackers.ts"; /** * The props for the {@link PinnedMessageBanner} component. @@ -68,6 +69,8 @@ export function PinnedMessageBanner({ room, permalinkCreator }: PinnedMessageBan const shouldUseMessageEvent = pinnedEvent.isRedacted() || pinnedEvent.isDecryptionFailure(); const onBannerClick = (): void => { + PosthogTrackers.trackInteraction("PinnedMessageBannerClick"); + // Scroll to the pinned message dis.dispatch({ action: Action.ViewRoom, @@ -309,6 +312,9 @@ function BannerButton({ room }: BannerButtonProps): JSX.Element { className="mx_PinnedMessageBanner_actions" kind="tertiary" onClick={() => { + if (isPinnedMessagesPhase) PosthogTrackers.trackInteraction("PinnedMessageBannerCloseListButton"); + else PosthogTrackers.trackInteraction("PinnedMessageBannerViewAllButton"); + RightPanelStore.instance.showOrHidePhase(RightPanelPhases.PinnedMessages); }} > From eb14223e2179595678f9cbb7784c452add61891f Mon Sep 17 00:00:00 2001 From: dbkr <986903+dbkr@users.noreply.github.com> Date: Fri, 6 Sep 2024 09:38:36 +0000 Subject: [PATCH 060/154] [create-pull-request] automated change --- playwright/plugins/homeserver/synapse/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright/plugins/homeserver/synapse/index.ts b/playwright/plugins/homeserver/synapse/index.ts index b4e42c0357e..6ab7abc18ac 100644 --- a/playwright/plugins/homeserver/synapse/index.ts +++ b/playwright/plugins/homeserver/synapse/index.ts @@ -28,7 +28,7 @@ import { randB64Bytes } from "../../utils/rand"; // Docker tag to use for synapse docker image. // We target a specific digest as every now and then a Synapse update will break our CI. // This digest is updated by the playwright-image-updates.yaml workflow periodically. -const DOCKER_TAG = "develop@sha256:84e3d3a5ecbb618d29878e99fa58b6b926d6feb08ea536063cabed41be0a057c"; +const DOCKER_TAG = "develop@sha256:c826fb82717a26e6dbb42b566c864064e2b67d28e1dae092908ce01ab84233c5"; async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise> { const templateDir = path.join(__dirname, "templates", opts.template); From 774222f74b830786618398a9b8624f558aea30b4 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 6 Sep 2024 11:22:50 +0100 Subject: [PATCH 061/154] Update codeowners to element-hq teams --- .github/CODEOWNERS | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e7963c26735..2b299a74e21 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,15 +1,15 @@ -* @matrix-org/element-web-reviewers -/.github/workflows/** @matrix-org/element-web-team -/package.json @matrix-org/element-web-team -/yarn.lock @matrix-org/element-web-team +* @element-hq/element-web-reviewers +/.github/workflows/** @element-hq/element-web-team +/package.json @element-hq/element-web-team +/yarn.lock @element-hq/element-web-team -/src/SecurityManager.ts @matrix-org/element-crypto-web-reviewers -/test/SecurityManager-test.ts @matrix-org/element-crypto-web-reviewers -/src/async-components/views/dialogs/security/ @matrix-org/element-crypto-web-reviewers -/src/components/views/dialogs/security/ @matrix-org/element-crypto-web-reviewers -/test/components/views/dialogs/security/ @matrix-org/element-crypto-web-reviewers -/src/stores/SetupEncryptionStore.ts @matrix-org/element-crypto-web-reviewers -/test/stores/SetupEncryptionStore-test.ts @matrix-org/element-crypto-web-reviewers +/src/SecurityManager.ts @element-hq/element-crypto-web-reviewers +/test/SecurityManager-test.ts @element-hq/element-crypto-web-reviewers +/src/async-components/views/dialogs/security/ @element-hq/element-crypto-web-reviewers +/src/components/views/dialogs/security/ @element-hq/element-crypto-web-reviewers +/test/components/views/dialogs/security/ @element-hq/element-crypto-web-reviewers +/src/stores/SetupEncryptionStore.ts @element-hq/element-crypto-web-reviewers +/test/stores/SetupEncryptionStore-test.ts @element-hq/element-crypto-web-reviewers # Ignore translations as those will be updated by GHA for Localazy download /src/i18n/strings From f8da25776915332c0daf59b98d332a7de4c89afe Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 6 Sep 2024 14:08:23 +0100 Subject: [PATCH 062/154] Update repos for changelog fetching --- src/components/views/dialogs/ChangelogDialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/dialogs/ChangelogDialog.tsx b/src/components/views/dialogs/ChangelogDialog.tsx index ca110aa16fb..382f9a858ec 100644 --- a/src/components/views/dialogs/ChangelogDialog.tsx +++ b/src/components/views/dialogs/ChangelogDialog.tsx @@ -38,7 +38,7 @@ interface Commit { }; } -const REPOS = ["vector-im/element-web", "matrix-org/matrix-react-sdk", "matrix-org/matrix-js-sdk"] as const; +const REPOS = ["element-hq/element-web", "element-hq/matrix-react-sdk", "matrix-org/matrix-js-sdk"] as const; export default class ChangelogDialog extends React.Component { public constructor(props: IProps) { From 9601be583bde4740d956b3030493d871607ff0a7 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 6 Sep 2024 14:16:25 +0100 Subject: [PATCH 063/154] Update tests --- .../views/dialogs/ChangelogDialog-test.tsx | 28 +++++++++---------- .../ChangelogDialog-test.tsx.snap | 8 +++--- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/test/components/views/dialogs/ChangelogDialog-test.tsx b/test/components/views/dialogs/ChangelogDialog-test.tsx index a8f394f64d7..948d6e20106 100644 --- a/test/components/views/dialogs/ChangelogDialog-test.tsx +++ b/test/components/views/dialogs/ChangelogDialog-test.tsx @@ -22,13 +22,13 @@ import ChangelogDialog from "../../../../src/components/views/dialogs/ChangelogD describe("", () => { it("should fetch github proxy url for each repo with old and new version strings", async () => { - const webUrl = "https://riot.im/github/repos/vector-im/element-web/compare/oldsha1...newsha1"; + const webUrl = "https://riot.im/github/repos/element-hq/element-web/compare/oldsha1...newsha1"; fetchMock.get(webUrl, { - url: "https://api.github.com/repos/vector-im/element-web/compare/master...develop", - html_url: "https://github.com/vector-im/element-web/compare/master...develop", - permalink_url: "https://github.com/vector-im/element-web/compare/vector-im:72ca95e...vector-im:8891698", - diff_url: "https://github.com/vector-im/element-web/compare/master...develop.diff", - patch_url: "https://github.com/vector-im/element-web/compare/master...develop.patch", + url: "https://api.github.com/repos/element-hq/element-web/compare/master...develop", + html_url: "https://github.com/element-hq/element-web/compare/master...develop", + permalink_url: "https://github.com/element-hq/element-web/compare/vector-im:72ca95e...vector-im:8891698", + diff_url: "https://github.com/element-hq/element-web/compare/master...develop.diff", + patch_url: "https://github.com/element-hq/element-web/compare/master...develop.patch", base_commit: {}, merge_base_commit: {}, status: "ahead", @@ -38,19 +38,19 @@ describe("", () => { commits: [ { sha: "commit-sha", - html_url: "https://api.github.com/repos/vector-im/element-web/commit/commit-sha", + html_url: "https://api.github.com/repos/element-hq/element-web/commit/commit-sha", commit: { message: "This is the first commit message" }, }, ], files: [], }); - const reactUrl = "https://riot.im/github/repos/matrix-org/matrix-react-sdk/compare/oldsha2...newsha2"; + const reactUrl = "https://riot.im/github/repos/element-hq/matrix-react-sdk/compare/oldsha2...newsha2"; fetchMock.get(reactUrl, { - url: "https://api.github.com/repos/matrix-org/matrix-react-sdk/compare/master...develop", - html_url: "https://github.com/matrix-org/matrix-react-sdk/compare/master...develop", - permalink_url: "https://github.com/matrix-org/matrix-react-sdk/compare/matrix-org:cdb00...matrix-org:4a926", - diff_url: "https://github.com/matrix-org/matrix-react-sdk/compare/master...develop.diff", - patch_url: "https://github.com/matrix-org/matrix-react-sdk/compare/master...develop.patch", + url: "https://api.github.com/repos/element-hq/matrix-react-sdk/compare/master...develop", + html_url: "https://github.com/element-hq/matrix-react-sdk/compare/master...develop", + permalink_url: "https://github.com/element-hq/matrix-react-sdk/compare/matrix-org:cdb00...matrix-org:4a926", + diff_url: "https://github.com/element-hq/matrix-react-sdk/compare/master...develop.diff", + patch_url: "https://github.com/element-hq/matrix-react-sdk/compare/master...develop.patch", base_commit: {}, merge_base_commit: {}, status: "ahead", @@ -60,7 +60,7 @@ describe("", () => { commits: [ { sha: "commit-sha0", - html_url: "https://api.github.com/repos/matrix-org/matrix-react-sdk/commit/commit-sha", + html_url: "https://api.github.com/repos/element-hq/matrix-react-sdk/commit/commit-sha", commit: { message: "This is a commit message" }, }, ], diff --git a/test/components/views/dialogs/__snapshots__/ChangelogDialog-test.tsx.snap b/test/components/views/dialogs/__snapshots__/ChangelogDialog-test.tsx.snap index 11e0c2135c8..d4609d61684 100644 --- a/test/components/views/dialogs/__snapshots__/ChangelogDialog-test.tsx.snap +++ b/test/components/views/dialogs/__snapshots__/ChangelogDialog-test.tsx.snap @@ -35,14 +35,14 @@ exports[` should fetch github proxy url for each repo with ol

- vector-im/element-web + element-hq/element-web

`; + +exports[`DecryptionFailureBody should handle messages from users who change identities after verification 1`] = ` +
+
+ +
+ Verified identity has changed + +
+
+`; From 4f391645e734f67e4e68b632a6b71d401ae22eb2 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 30 Sep 2024 14:50:17 +0100 Subject: [PATCH 139/154] Ensure timeline search results are visible even in video rooms (#96) Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/RoomView.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 87e8c3c307f..37e45d6b8d4 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -2521,9 +2521,15 @@ export class RoomView extends React.Component { mx_RoomView_timeline_rr_enabled: this.state.showReadReceipts, }); + let { mainSplitContentType } = this.state; + if (this.state.search) { + // When in the middle of a search force the main split content type to timeline + mainSplitContentType = MainSplitContentType.Timeline; + } + const mainClasses = classNames("mx_RoomView", { mx_RoomView_inCall: Boolean(activeCall), - mx_RoomView_immersive: this.state.mainSplitContentType !== MainSplitContentType.Timeline, + mx_RoomView_immersive: mainSplitContentType !== MainSplitContentType.Timeline, }); const showChatEffects = SettingsStore.getValue("showChatEffects"); @@ -2531,7 +2537,7 @@ export class RoomView extends React.Component { let mainSplitBody: JSX.Element | undefined; let mainSplitContentClassName: string | undefined; // Decide what to show in the main split - switch (this.state.mainSplitContentType) { + switch (mainSplitContentType) { case MainSplitContentType.Timeline: mainSplitContentClassName = "mx_MainSplit_timeline"; mainSplitBody = ( @@ -2595,7 +2601,7 @@ export class RoomView extends React.Component { let viewingCall = false; // Simplify the header for other main split types - switch (this.state.mainSplitContentType) { + switch (mainSplitContentType) { case MainSplitContentType.MaximisedWidget: excludedRightPanelPhaseButtons = []; onAppsClick = null; From bd793a0970744a637f733fd46455aa187879f416 Mon Sep 17 00:00:00 2001 From: Robin Date: Mon, 30 Sep 2024 10:07:53 -0400 Subject: [PATCH 140/154] Allow joining calls and video rooms without enabling the labs flags (#95) Since Element Call has now reached production on Element X, Element Web needs to be able to at least participate in group calls. Starting a group call or creating a video room will still require the labs flags, for now. Note that Jitsi-based video rooms are also affected by this change. This is not because we intend to delabs them (rather, we intend to get rid of them in favor of Element Call video rooms), but because it's easiest to handle both video room variants consistently. --- src/Notifier.ts | 7 +- src/components/structures/RoomView.tsx | 10 +-- .../views/context_menus/RoomContextMenu.tsx | 4 +- .../views/right_panel/RightPanelTabs.tsx | 4 +- .../views/right_panel/RoomSummaryCard.tsx | 4 +- .../views/rooms/LegacyRoomHeader.tsx | 76 ++++++------------- src/components/views/rooms/RoomHeader.tsx | 4 +- src/components/views/rooms/RoomInfoLine.tsx | 4 +- .../views/rooms/RoomPreviewCard.tsx | 30 ++------ src/hooks/room/useRoomCall.ts | 8 +- src/i18n/strings/en_EN.json | 3 - src/models/Call.ts | 47 +++++------- src/utils/video-rooms.ts | 23 +----- test/Notifier-test.ts | 21 +---- .../components/structures/MatrixChat-test.tsx | 7 +- .../views/rooms/RoomPreviewCard-test.tsx | 17 ----- 16 files changed, 75 insertions(+), 194 deletions(-) diff --git a/src/Notifier.ts b/src/Notifier.ts index 6323343871c..23d239de0aa 100644 --- a/src/Notifier.ts +++ b/src/Notifier.ts @@ -513,12 +513,7 @@ class NotifierClass extends TypedEventEmitter m.sender === cli.getUserId()); - if ( - EventType.CallNotify === ev.getType() && - SettingsStore.getValue("feature_group_calls") && - (ev.getAge() ?? 0) < 10000 && - !thisUserHasConnectedDevice - ) { + if (EventType.CallNotify === ev.getType() && (ev.getAge() ?? 0) < 10000 && !thisUserHasConnectedDevice) { const content = ev.getContent(); const roomId = ev.getRoomId(); if (typeof content.call_id !== "string") { diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 37e45d6b8d4..b56172be425 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -614,10 +614,7 @@ export class RoomView extends React.Component { }; private getMainSplitContentType = (room: Room): MainSplitContentType => { - if ( - (SettingsStore.getValue("feature_group_calls") && this.context.roomViewStore.isViewingCall()) || - isVideoRoom(room) - ) { + if (this.context.roomViewStore.isViewingCall() || isVideoRoom(room)) { return MainSplitContentType.Call; } if (this.context.widgetLayoutStore.hasMaximisedWidget(room)) { @@ -2183,10 +2180,7 @@ export class RoomView extends React.Component { } const myMembership = this.state.room.getMyMembership(); - if ( - isVideoRoom(this.state.room) && - !(SettingsStore.getValue("feature_video_rooms") && myMembership === KnownMembership.Join) - ) { + if (isVideoRoom(this.state.room) && myMembership !== KnownMembership.Join) { return (
diff --git a/src/components/views/context_menus/RoomContextMenu.tsx b/src/components/views/context_menus/RoomContextMenu.tsx index 694aba0ef51..f431218b06f 100644 --- a/src/components/views/context_menus/RoomContextMenu.tsx +++ b/src/components/views/context_menus/RoomContextMenu.tsx @@ -42,7 +42,7 @@ import { shouldShowComponent } from "../../../customisations/helpers/UIComponent import { UIComponent } from "../../../settings/UIFeature"; import { DeveloperToolsOption } from "./DeveloperToolsOption"; import { tagRoom } from "../../../utils/room/tagRoom"; -import { useIsVideoRoom } from "../../../utils/video-rooms"; +import { isVideoRoom as calcIsVideoRoom } from "../../../utils/video-rooms"; import { usePinnedEvents } from "../../../hooks/usePinnedEvents"; interface IProps extends IContextMenuProps { @@ -105,7 +105,7 @@ const RoomContextMenu: React.FC = ({ room, onFinished, ...props }) => { } const isDm = DMRoomMap.shared().getUserIdForRoomId(room.roomId); - const isVideoRoom = useIsVideoRoom(room); + const isVideoRoom = calcIsVideoRoom(room); const canInvite = useEventEmitterState(cli, RoomMemberEvent.PowerLevel, () => room.canInvite(cli.getUserId()!)); let inviteOption: JSX.Element | undefined; if (canInvite && !isDm && shouldShowComponent(UIComponent.InviteUsers)) { diff --git a/src/components/views/right_panel/RightPanelTabs.tsx b/src/components/views/right_panel/RightPanelTabs.tsx index 07a423dd054..4873f707b2b 100644 --- a/src/components/views/right_panel/RightPanelTabs.tsx +++ b/src/components/views/right_panel/RightPanelTabs.tsx @@ -20,7 +20,7 @@ import { Action } from "../../../dispatcher/actions"; import SettingsStore from "../../../settings/SettingsStore"; import { UIComponent, UIFeature } from "../../../settings/UIFeature"; import { shouldShowComponent } from "../../../customisations/helpers/UIComponents"; -import { useIsVideoRoom } from "../../../utils/video-rooms"; +import { isVideoRoom as calcIsVideoRoom } from "../../../utils/video-rooms"; function shouldShowTabsForPhase(phase?: RightPanelPhases): boolean { const tabs = [ @@ -48,7 +48,7 @@ export const RightPanelTabs: React.FC = ({ phase, room }): JSX.Element | } }); - const isVideoRoom = useIsVideoRoom(room); + const isVideoRoom = room !== undefined && calcIsVideoRoom(room); if (!shouldShowTabsForPhase(phase)) return null; diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index 39575d94f52..e67d38a2ede 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -70,7 +70,7 @@ import { useDispatcher } from "../../../hooks/useDispatcher"; import { Action } from "../../../dispatcher/actions"; import { Key } from "../../../Keyboard"; import { useTransition } from "../../../hooks/useTransition"; -import { useIsVideoRoom } from "../../../utils/video-rooms"; +import { isVideoRoom as calcIsVideoRoom } from "../../../utils/video-rooms"; import { usePinnedEvents } from "../../../hooks/usePinnedEvents"; import { ReleaseAnnouncement } from "../../structures/ReleaseAnnouncement.tsx"; @@ -219,7 +219,7 @@ const RoomSummaryCard: React.FC = ({ const isRoomEncrypted = useIsEncrypted(cli, room); const roomContext = useContext(RoomContext); const e2eStatus = roomContext.e2eStatus; - const isVideoRoom = useIsVideoRoom(room); + const isVideoRoom = calcIsVideoRoom(room); const roomState = useRoomState(room); const directRoomsList = useAccountData>(room.client, EventType.Direct); diff --git a/src/components/views/rooms/LegacyRoomHeader.tsx b/src/components/views/rooms/LegacyRoomHeader.tsx index 3464aef30de..0e4cd28aa82 100644 --- a/src/components/views/rooms/LegacyRoomHeader.tsx +++ b/src/components/views/rooms/LegacyRoomHeader.tsx @@ -251,8 +251,7 @@ const CallButtons: FC = ({ room }) => { const [busy, setBusy] = useState(false); const showButtons = useSettingValue("showCallButtonsInComposer"); const groupCallsEnabled = useFeatureEnabled("feature_group_calls"); - const videoRoomsEnabled = useFeatureEnabled("feature_video_rooms"); - const isVideoRoom = useMemo(() => videoRoomsEnabled && calcIsVideoRoom(room), [videoRoomsEnabled, room]); + const isVideoRoom = useMemo(() => calcIsVideoRoom(room), [room]); const useElementCallExclusively = useMemo(() => { return SdkConfig.get("element_call").use_exclusively; }, []); @@ -290,53 +289,13 @@ const CallButtons: FC = ({ room }) => { if (isVideoRoom || !showButtons) { return null; - } else if (groupCallsEnabled) { - if (useElementCallExclusively) { - if (hasGroupCall) { - return makeVideoCallButton(new DisabledWithReason(_t("voip|disabled_ongoing_call"))); - } else if (mayCreateElementCalls) { - return makeVideoCallButton("element"); - } else { - return makeVideoCallButton(new DisabledWithReason(_t("voip|disabled_no_perms_start_video_call"))); - } - } else if (hasLegacyCall || hasJitsiWidget) { - return ( - <> - {makeVoiceCallButton(new DisabledWithReason(_t("voip|disabled_ongoing_call")))} - {makeVideoCallButton(new DisabledWithReason(_t("voip|disabled_ongoing_call")))} - - ); - } else if (functionalMembers.length <= 1) { - return ( - <> - {makeVoiceCallButton(new DisabledWithReason(_t("voip|disabled_no_one_here")))} - {makeVideoCallButton(new DisabledWithReason(_t("voip|disabled_no_one_here")))} - - ); - } else if (functionalMembers.length === 2) { - return ( - <> - {makeVoiceCallButton("legacy_or_jitsi")} - {makeVideoCallButton("legacy_or_element")} - - ); - } else if (mayEditWidgets) { - return ( - <> - {makeVoiceCallButton("legacy_or_jitsi")} - {makeVideoCallButton(mayCreateElementCalls ? "jitsi_or_element" : "legacy_or_jitsi")} - - ); + } else if (groupCallsEnabled && useElementCallExclusively) { + if (hasGroupCall) { + return makeVideoCallButton(new DisabledWithReason(_t("voip|disabled_ongoing_call"))); + } else if (mayCreateElementCalls) { + return makeVideoCallButton("element"); } else { - const videoCallBehavior = mayCreateElementCalls - ? "element" - : new DisabledWithReason(_t("voip|disabled_no_perms_start_video_call")); - return ( - <> - {makeVoiceCallButton(new DisabledWithReason(_t("voip|disabled_no_perms_start_voice_call")))} - {makeVideoCallButton(videoCallBehavior)} - - ); + return makeVideoCallButton(new DisabledWithReason(_t("voip|disabled_no_perms_start_video_call"))); } } else if (hasLegacyCall || hasJitsiWidget) { return ( @@ -352,18 +311,31 @@ const CallButtons: FC = ({ room }) => { {makeVideoCallButton(new DisabledWithReason(_t("voip|disabled_no_one_here")))} ); - } else if (functionalMembers.length === 2 || mayEditWidgets) { + } else if (functionalMembers.length === 2) { + return ( + <> + {makeVoiceCallButton("legacy_or_jitsi")} + {makeVideoCallButton(groupCallsEnabled ? "legacy_or_element" : "legacy_or_jitsi")} + + ); + } else if (mayEditWidgets) { return ( <> {makeVoiceCallButton("legacy_or_jitsi")} - {makeVideoCallButton("legacy_or_jitsi")} + {makeVideoCallButton( + groupCallsEnabled && mayCreateElementCalls ? "jitsi_or_element" : "legacy_or_jitsi", + )} ); } else { + const videoCallBehavior = + groupCallsEnabled && mayCreateElementCalls + ? "element" + : new DisabledWithReason(_t("voip|disabled_no_perms_start_video_call")); return ( <> {makeVoiceCallButton(new DisabledWithReason(_t("voip|disabled_no_perms_start_voice_call")))} - {makeVideoCallButton(new DisabledWithReason(_t("voip|disabled_no_perms_start_video_call")))} + {makeVideoCallButton(videoCallBehavior)} ); } @@ -745,7 +717,7 @@ export default class RoomHeader extends React.Component { } public render(): React.ReactNode { - const isVideoRoom = SettingsStore.getValue("feature_video_rooms") && calcIsVideoRoom(this.props.room); + const isVideoRoom = calcIsVideoRoom(this.props.room); let roomAvatar: JSX.Element | null = null; if (this.props.room) { diff --git a/src/components/views/rooms/RoomHeader.tsx b/src/components/views/rooms/RoomHeader.tsx index fae9e264ae6..6e04a606013 100644 --- a/src/components/views/rooms/RoomHeader.tsx +++ b/src/components/views/rooms/RoomHeader.tsx @@ -42,7 +42,7 @@ import RightPanelStore from "../../../stores/right-panel/RightPanelStore"; import PosthogTrackers from "../../../PosthogTrackers"; import { VideoRoomChatButton } from "./RoomHeader/VideoRoomChatButton"; import { RoomKnocksBar } from "./RoomKnocksBar"; -import { useIsVideoRoom } from "../../../utils/video-rooms"; +import { isVideoRoom as calcIsVideoRoom } from "../../../utils/video-rooms"; import { notificationLevelToIndicator } from "../../../utils/notifications"; import { CallGuestLinkButton } from "./RoomHeader/CallGuestLinkButton"; import { ButtonEvent } from "../elements/AccessibleButton"; @@ -225,7 +225,7 @@ export default function RoomHeader({ } const roomContext = useContext(RoomContext); - const isVideoRoom = useIsVideoRoom(room); + const isVideoRoom = calcIsVideoRoom(room); const showChatButton = isVideoRoom || roomContext.mainSplitContentType === MainSplitContentType.MaximisedWidget || diff --git a/src/components/views/rooms/RoomInfoLine.tsx b/src/components/views/rooms/RoomInfoLine.tsx index c52b17cd91c..5d570abb9aa 100644 --- a/src/components/views/rooms/RoomInfoLine.tsx +++ b/src/components/views/rooms/RoomInfoLine.tsx @@ -17,7 +17,7 @@ import { useAsyncMemo } from "../../../hooks/useAsyncMemo"; import { useRoomState } from "../../../hooks/useRoomState"; import { useRoomMemberCount, useMyRoomMembership } from "../../../hooks/useRoomMembers"; import AccessibleButton from "../elements/AccessibleButton"; -import { useIsVideoRoom } from "../../../utils/video-rooms"; +import { isVideoRoom as calcIsVideoRoom } from "../../../utils/video-rooms"; interface IProps { room: Room; @@ -37,7 +37,7 @@ const RoomInfoLine: FC = ({ room }) => { const membership = useMyRoomMembership(room); const memberCount = useRoomMemberCount(room); - const isVideoRoom = useIsVideoRoom(room, true); + const isVideoRoom = calcIsVideoRoom(room); let iconClass: string; let roomType: string; diff --git a/src/components/views/rooms/RoomPreviewCard.tsx b/src/components/views/rooms/RoomPreviewCard.tsx index 9b926e6518e..2c816e4743e 100644 --- a/src/components/views/rooms/RoomPreviewCard.tsx +++ b/src/components/views/rooms/RoomPreviewCard.tsx @@ -17,7 +17,6 @@ import { UserTab } from "../dialogs/UserTab"; import { EffectiveMembership, getEffectiveMembership } from "../../../utils/membership"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { useDispatcher } from "../../../hooks/useDispatcher"; -import { useFeatureEnabled } from "../../../hooks/useSettings"; import { useRoomState } from "../../../hooks/useRoomState"; import { useMyRoomMembership } from "../../../hooks/useRoomMembers"; import AccessibleButton from "../elements/AccessibleButton"; @@ -29,7 +28,7 @@ import RoomAvatar from "../avatars/RoomAvatar"; import MemberAvatar from "../avatars/MemberAvatar"; import { BetaPill } from "../beta/BetaCard"; import RoomInfoLine from "./RoomInfoLine"; -import { useIsVideoRoom } from "../../../utils/video-rooms"; +import { isVideoRoom as calcIsVideoRoom } from "../../../utils/video-rooms"; interface IProps { room: Room; @@ -43,8 +42,7 @@ interface IProps { // and viewing invite reasons to achieve parity with the default invite screen. const RoomPreviewCard: FC = ({ room, onJoinButtonClicked, onRejectButtonClicked }) => { const cli = useContext(MatrixClientContext); - const videoRoomsEnabled = useFeatureEnabled("feature_video_rooms"); - const isVideoRoom = useIsVideoRoom(room, true); + const isVideoRoom = calcIsVideoRoom(room); const myMembership = useMyRoomMembership(room); useDispatcher(defaultDispatcher, (payload) => { if (payload.action === Action.JoinRoomError && payload.roomId === room.roomId) { @@ -164,24 +162,6 @@ const RoomPreviewCard: FC = ({ room, onJoinButtonClicked, onRejectButton avatarRow = ; } - let notice: string | null = null; - if (cannotJoin) { - notice = _t("room|join_failed_needs_invite", { - roomName: room.name, - }); - } else if (isVideoRoom && !videoRoomsEnabled) { - notice = - myMembership === KnownMembership.Join - ? _t("room|view_failed_enable_video_rooms") - : _t("room|join_failed_enable_video_rooms"); - - joinButtons = ( - - {_t("room|show_labs_settings")} - - ); - } - return (
{inviterSection} @@ -192,7 +172,11 @@ const RoomPreviewCard: FC = ({ room, onJoinButtonClicked, onRejectButton {room.getJoinRule() === "public" && } - {notice ?
{notice}
: null} + {cannotJoin ? ( +
+ {_t("room|join_failed_needs_invite", { roomName: room.name })} +
+ ) : null}
{joinButtons}
); diff --git a/src/hooks/room/useRoomCall.ts b/src/hooks/room/useRoomCall.ts index 537f443750f..adf16cc5cc9 100644 --- a/src/hooks/room/useRoomCall.ts +++ b/src/hooks/room/useRoomCall.ts @@ -133,10 +133,10 @@ export const useRoomCall = ( if (useElementCallExclusively && !hasJitsiWidget) { return [PlatformCallType.ElementCall]; } - if (hasGroupCall && WidgetType.CALL.matches(groupCall.widget.type)) { - // only allow joining the ongoing Element call if there is one. - return [PlatformCallType.ElementCall]; - } + } + if (hasGroupCall && WidgetType.CALL.matches(groupCall.widget.type)) { + // only allow joining the ongoing Element call if there is one. + return [PlatformCallType.ElementCall]; } return options; }, [ diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 5a0b9d7cacd..9b608c9ab31 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2019,7 +2019,6 @@ "inviter_unknown": "Unknown", "invites_you_text": " invites you", "join_button_account": "Sign Up", - "join_failed_enable_video_rooms": "To join, please enable video rooms in Labs first", "join_failed_needs_invite": "To view %(roomName)s, you need an invite", "join_the_discussion": "Join the discussion", "join_title": "Join the room to participate", @@ -2088,7 +2087,6 @@ }, "this_room_button": "Search this room" }, - "show_labs_settings": "Show Labs settings", "status_bar": { "delete_all": "Delete all", "exceeded_resource_limit": "Your message wasn't sent because this homeserver has exceeded a resource limit. Please
contact your service administrator to continue using the service.", @@ -2119,7 +2117,6 @@ }, "uploading_single_file": "Uploading %(filename)s" }, - "view_failed_enable_video_rooms": "To view, please enable video rooms in Labs first", "waiting_for_join_subtitle": "Once invited users have joined %(brand)s, you will be able to chat and the room will be end-to-end encrypted", "waiting_for_join_title": "Waiting for users to join %(brand)s" }, diff --git a/src/models/Call.ts b/src/models/Call.ts index 63859ae6f8f..0238d159145 100644 --- a/src/models/Call.ts +++ b/src/models/Call.ts @@ -338,7 +338,7 @@ export class JitsiCall extends Call { public static get(room: Room): JitsiCall | null { // Only supported in video rooms - if (SettingsStore.getValue("feature_video_rooms") && room.isElementVideoRoom()) { + if (room.isElementVideoRoom()) { const apps = WidgetStore.instance.getApps(room.roomId); // The isVideoChannel field differentiates rich Jitsi calls from bare Jitsi widgets const jitsiWidget = apps.find((app) => WidgetType.JITSI.matches(app.type) && app.data?.isVideoChannel); @@ -805,33 +805,24 @@ export class ElementCall extends Call { } public static get(room: Room): ElementCall | null { - // Only supported in the new group call experience or in video rooms. - - if ( - SettingsStore.getValue("feature_group_calls") || - (SettingsStore.getValue("feature_video_rooms") && - SettingsStore.getValue("feature_element_call_video_rooms") && - room.isCallRoom()) - ) { - const apps = WidgetStore.instance.getApps(room.roomId); - const hasEcWidget = apps.some((app) => WidgetType.CALL.matches(app.type)); - const session = room.client.matrixRTC.getRoomSession(room); - - // A call is present if we - // - have a widget: This means the create function was called. - // - or there is a running session where we have not yet created a widget for. - // - or this is a call room. Then we also always want to show a call. - if (hasEcWidget || session.memberships.length !== 0 || room.isCallRoom()) { - // create a widget for the case we are joining a running call and don't have on yet. - const availableOrCreatedWidget = ElementCall.createOrGetCallWidget( - room.roomId, - room.client, - undefined, - undefined, - isVideoRoom(room), - ); - return new ElementCall(session, availableOrCreatedWidget, room.client); - } + const apps = WidgetStore.instance.getApps(room.roomId); + const hasEcWidget = apps.some((app) => WidgetType.CALL.matches(app.type)); + const session = room.client.matrixRTC.getRoomSession(room); + + // A call is present if we + // - have a widget: This means the create function was called. + // - or there is a running session where we have not yet created a widget for. + // - or this is a call room. Then we also always want to show a call. + if (hasEcWidget || session.memberships.length !== 0 || room.isCallRoom()) { + // create a widget for the case we are joining a running call and don't have on yet. + const availableOrCreatedWidget = ElementCall.createOrGetCallWidget( + room.roomId, + room.client, + undefined, + undefined, + isVideoRoom(room), + ); + return new ElementCall(session, availableOrCreatedWidget, room.client); } return null; diff --git a/src/utils/video-rooms.ts b/src/utils/video-rooms.ts index bb66747e3f0..a0003d59d90 100644 --- a/src/utils/video-rooms.ts +++ b/src/utils/video-rooms.ts @@ -7,27 +7,8 @@ Please see LICENSE files in the repository root for full details. */ import type { Room } from "matrix-js-sdk/src/matrix"; -import SettingsStore from "../settings/SettingsStore"; -import { useFeatureEnabled } from "../hooks/useSettings"; - -function checkIsVideoRoom(room: Room, elementCallVideoRoomsEnabled: boolean): boolean { - return room.isElementVideoRoom() || (elementCallVideoRoomsEnabled && room.isCallRoom()); -} - -export const isVideoRoom = (room: Room): boolean => - checkIsVideoRoom(room, SettingsStore.getValue("feature_element_call_video_rooms")); /** - * Returns whether the given room is a video room based on the current feature flags. - * @param room The room to check. - * @param skipVideoRoomsEnabledCheck If true, the check for the video rooms feature flag is skipped, - * useful for suggesting to the user to enable the labs flag. + * Determines whether the given room is a video room. */ -export const useIsVideoRoom = (room?: Room, skipVideoRoomsEnabledCheck = false): boolean => { - const videoRoomsEnabled = useFeatureEnabled("feature_video_rooms"); - const elementCallVideoRoomsEnabled = useFeatureEnabled("feature_element_call_video_rooms"); // react to updates as isVideoRoom reads the value itself - - if (!room) return false; - if (!videoRoomsEnabled && !skipVideoRoomsEnabledCheck) return false; - return checkIsVideoRoom(room, elementCallVideoRoomsEnabled); -}; +export const isVideoRoom = (room: Room): boolean => room.isElementVideoRoom() || room.isCallRoom(); diff --git a/test/Notifier-test.ts b/test/Notifier-test.ts index e5b1a303e8d..08a070104c1 100644 --- a/test/Notifier-test.ts +++ b/test/Notifier-test.ts @@ -423,15 +423,7 @@ describe("Notifier", () => { return callEvent; }; - const setGroupCallsEnabled = (val: boolean) => { - jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string) => { - if (name === "feature_group_calls") return val; - }); - }; - - it("should show toast when group calls are supported", () => { - setGroupCallsEnabled(true); - + it("shows group call toast", () => { const notifyEvent = emitCallNotifyEvent(); expect(ToastStore.sharedInstance().addOrReplaceToast).toHaveBeenCalledWith( @@ -445,16 +437,7 @@ describe("Notifier", () => { ); }); - it("should not show toast when group calls are not supported", () => { - setGroupCallsEnabled(false); - - emitCallNotifyEvent(); - - expect(ToastStore.sharedInstance().addOrReplaceToast).not.toHaveBeenCalled(); - }); - it("should not show toast when group call is already connected", () => { - setGroupCallsEnabled(true); const spyCallMemberships = jest.spyOn(MatrixRTCSession, "callMembershipsForRoom").mockReturnValue([ new CallMembership( mkEvent({ @@ -483,8 +466,6 @@ describe("Notifier", () => { }); it("should not show toast when calling with non-group call event", () => { - setGroupCallsEnabled(true); - emitCallNotifyEvent("event_type"); expect(ToastStore.sharedInstance().addOrReplaceToast).not.toHaveBeenCalled(); diff --git a/test/components/structures/MatrixChat-test.tsx b/test/components/structures/MatrixChat-test.tsx index 83b4feea3ac..e786370b7c4 100644 --- a/test/components/structures/MatrixChat-test.tsx +++ b/test/components/structures/MatrixChat-test.tsx @@ -1039,10 +1039,13 @@ describe("", () => { }); describe("when encryption is force disabled", () => { - const unencryptedRoom = new Room("!unencrypted:server.org", loginClient, userId); - const encryptedRoom = new Room("!encrypted:server.org", loginClient, userId); + let unencryptedRoom: Room; + let encryptedRoom: Room; beforeEach(() => { + unencryptedRoom = new Room("!unencrypted:server.org", loginClient, userId); + encryptedRoom = new Room("!encrypted:server.org", loginClient, userId); + loginClient.getClientWellKnown.mockReturnValue({ "io.element.e2ee": { force_disable: true, diff --git a/test/components/views/rooms/RoomPreviewCard-test.tsx b/test/components/views/rooms/RoomPreviewCard-test.tsx index 0a4fa3089fa..4d7cf1d51e3 100644 --- a/test/components/views/rooms/RoomPreviewCard-test.tsx +++ b/test/components/views/rooms/RoomPreviewCard-test.tsx @@ -83,21 +83,4 @@ describe("RoomPreviewCard", () => { await renderPreview(); expect(screen.queryByRole("button", { name: /beta/i })).toBeNull(); }); - - it("shows instructions on Jitsi video rooms invites if video rooms are disabled", async () => { - jest.spyOn(room, "getType").mockReturnValue(RoomType.ElementVideo); - jest.spyOn(room, "getMyMembership").mockReturnValue(KnownMembership.Invite); - - await renderPreview(); - screen.getByText(/enable video rooms in labs/i); - }); - - it("shows instructions on Element video rooms invites if video rooms are disabled", async () => { - jest.spyOn(room, "getType").mockReturnValue(RoomType.UnstableCall); - jest.spyOn(room, "getMyMembership").mockReturnValue(KnownMembership.Invite); - enabledFeatures = ["feature_element_call_video_rooms"]; - - await renderPreview(); - screen.getByText(/enable video rooms in labs/i); - }); }); From 0b3b499fc4acc1e0925588f6f4c668e28d57821b Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 30 Sep 2024 16:31:22 +0100 Subject: [PATCH 141/154] Fix label sync (#101) The colours can't have hashes on --- .github/labels.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/labels.yml b/.github/labels.yml index d240f3750b9..7c4b66d7f05 100644 --- a/.github/labels.yml +++ b/.github/labels.yml @@ -1,28 +1,28 @@ - name: "A-Timesheet-1" description: "Log any time spent on this into the A-Timesheet-1 project" - color: "#5319E7" + color: "5319E7" - name: "backport staging" description: "Label to automatically backport PR to staging branch" - color: "#B60205" + color: "B60205" - name: "Dependencies" description: "Pull requests that update a dependency file" - color: "#0366d6" + color: "0366d6" - name: "Sponsored" - color: "#b506d8" + color: "b506d8" - name: "T-Deprecation" description: "A pull request that makes something deprecated" - color: "#98e6ae" + color: "98e6ae" - name: "X-Blocked" description: "The PR cannot move forward in any capacity until an action is made" color: "ff7979" - name: "X-Breaking-Change" - color: "#ff7979" + color: "ff7979" - name: "X-Upcoming-Release-Blocker" description: "This does not affect the current release cycle but will affect the next one" - color: "#e99695" + color: "e99695" - name: "Z-Community-PR" description: "Issue is solved by a community member's PR" - color: "#ededed" + color: "ededed" - name: "Z-Experiment" description: "Experimental PR, primarily up for its Netlify build, high likelihood of never making it beyond here." - color: "#b60205" + color: "b60205" From fe402e28bb9d1643121b96ed85c44444171b2def Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 30 Sep 2024 17:34:39 +0100 Subject: [PATCH 142/154] Fix flaky mobile registration tests (#102) * Fix flaky mobile registration tests Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Discard changes to src/theme.ts * Add comment Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- test/components/structures/MatrixChat-test.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/components/structures/MatrixChat-test.tsx b/test/components/structures/MatrixChat-test.tsx index e786370b7c4..dd4ae5de640 100644 --- a/test/components/structures/MatrixChat-test.tsx +++ b/test/components/structures/MatrixChat-test.tsx @@ -61,6 +61,10 @@ jest.mock("matrix-js-sdk/src/oidc/authorize", () => ({ completeAuthorizationCodeGrant: jest.fn(), })); +// Stub out ThemeWatcher as the necessary bits for themes are done in element-web's index.html and thus are lacking here, +// plus JSDOM's implementation of CSSStyleDeclaration has a bunch of differences to real browsers which cause issues. +jest.mock("../../../src/settings/watchers/ThemeWatcher"); + /** The matrix versions our mock server claims to support */ const SERVER_SUPPORTED_MATRIX_VERSIONS = ["v1.1", "v1.5", "v1.6", "v1.8", "v1.9"]; From 81bb56ae2bc60dde4089499d041c657e91ab290a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 30 Sep 2024 17:34:48 +0100 Subject: [PATCH 143/154] Simplify Jest runs in CI to share failures with merge queue (#103) Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .github/workflows/tests.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 22a02779b3c..3bf5700f03e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -89,13 +89,17 @@ jobs: coverage !coverage/lcov-report - skip_sonar: - name: Skip SonarCloud in merge queue - if: github.event_name == 'merge_group' || inputs.disable_coverage == 'true' - runs-on: ubuntu-latest + complete: + name: jest-tests needs: jest + if: always() + runs-on: ubuntu-latest steps: - - name: Skip SonarCloud + - if: needs.jest.result != 'skipped' && needs.jest.result != 'success' + run: exit 1 + + - name: Skip SonarCloud in merge queue + if: github.event_name == 'merge_group' || inputs.disable_coverage == 'true' uses: Sibz/github-status-action@faaa4d96fecf273bd762985e0e7f9f933c774918 # v1 with: authToken: ${{ secrets.GITHUB_TOKEN }} From 33c900e3070ae57d0c5c255107f030508e82a02f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 30 Sep 2024 17:36:24 +0100 Subject: [PATCH 144/154] Remove right panel toggling behaviour on room header buttons (#100) * Remove right panel toggling behaviour on room header buttons Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Remove stale test Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Fix tests Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- playwright/e2e/read-receipts/index.ts | 3 +++ src/components/views/rooms/RoomHeader.tsx | 10 +++++----- .../rooms/RoomHeader/VideoRoomChatButton.tsx | 2 +- src/stores/right-panel/RightPanelStore.ts | 17 ----------------- .../RoomHeader/VideoRoomChatButton-test.tsx | 4 ++-- .../stores/right-panel/RightPanelStore-test.ts | 18 ------------------ 6 files changed, 11 insertions(+), 43 deletions(-) diff --git a/playwright/e2e/read-receipts/index.ts b/playwright/e2e/read-receipts/index.ts index 47bd5c2d5c9..f097ec839d5 100644 --- a/playwright/e2e/read-receipts/index.ts +++ b/playwright/e2e/read-receipts/index.ts @@ -395,6 +395,9 @@ class Helpers { */ async closeThreadsPanel() { await this.page.locator(".mx_RoomHeader").getByLabel("Threads").click(); + if (await this.page.locator("#thread-panel").isVisible()) { + await this.page.locator(".mx_RoomHeader").getByLabel("Threads").click(); + } await expect(this.page.locator(".mx_RightPanel")).not.toBeVisible(); } diff --git a/src/components/views/rooms/RoomHeader.tsx b/src/components/views/rooms/RoomHeader.tsx index 6e04a606013..b09d4c70e29 100644 --- a/src/components/views/rooms/RoomHeader.tsx +++ b/src/components/views/rooms/RoomHeader.tsx @@ -255,7 +255,7 @@ export default function RoomHeader({ - -======= - ->>>>>>> v3.112.0 {additionalButtons?.map((props) => { const label = props.label(); diff --git a/test/MatrixClientPeg-test.ts b/test/MatrixClientPeg-test.ts index 712011cf20d..9ece98e13c0 100644 --- a/test/MatrixClientPeg-test.ts +++ b/test/MatrixClientPeg-test.ts @@ -8,14 +8,7 @@ Please see LICENSE files in the repository root for full details. import { logger } from "matrix-js-sdk/src/logger"; import fetchMockJest from "fetch-mock-jest"; -<<<<<<< HEAD import EventEmitter from "events"; -import { - ProvideCryptoSetupExtensions, - SecretStorageKeyDescription, -} from "@matrix-org/react-sdk-module-api/lib/lifecycles/CryptoSetupExtensions"; -======= ->>>>>>> v3.112.0 import { advanceDateAndTime, stubClient } from "./test-utils"; import { IMatrixClientPeg, MatrixClientPeg as peg } from "../src/MatrixClientPeg"; @@ -23,11 +16,7 @@ import SettingsStore from "../src/settings/SettingsStore"; import Modal from "../src/Modal"; import PlatformPeg from "../src/PlatformPeg"; import { SettingLevel } from "../src/settings/SettingLevel"; -<<<<<<< HEAD import { Features } from "../src/settings/Settings"; -import { ModuleRunner } from "../src/modules/ModuleRunner"; -======= ->>>>>>> v3.112.0 jest.useFakeTimers();