Merge branch 'develop' of ssh://github.com/element-hq/element-web into t3chguy/esm

This commit is contained in:
Michael Telatynski 2026-02-05 11:03:10 +00:00
commit e34fc3b828
No known key found for this signature in database
GPG Key ID: A2B008A5F49F5D0D
36 changed files with 357 additions and 137 deletions

View File

@ -1,4 +1,4 @@
# syntax=docker.io/docker/dockerfile:1.20-labs@sha256:dbcde2ebc4abc8bb5c3c499b9c9a6876842bf5da243951cd2697f921a7aeb6a9
# syntax=docker.io/docker/dockerfile:1.21-labs@sha256:2e681d22e86e738a057075f930b81b2ab8bc2a34cd16001484a7453cfa7a03fb
# Builder
FROM --platform=$BUILDPLATFORM node:24-bullseye@sha256:8036dbe5b1f465e3acb8b866031cd06e4f84c31b0e83dabbdc59397a40dbe288 AS builder

View File

@ -274,6 +274,7 @@ Inheriting all the rules of TypeScript, the following additionally apply:
20. Do not use `React.Component::forceUpdate`.
21. Prefer to use [compound typography components](https://compound.element.io/?path=/docs/compound-web_typography--docs) instead of raw HTML elements for text. This ensures consistent font usage and letter spacing across the app.
22. If you can't use 21, don't forget to apply the correct CSS classes for font and letter spacing.
23. Prefer to use `Flex` or `Box` components from shared-components for layout instead of raw HTML elements with CSS flexbox styles.
## Stylesheets

View File

@ -581,8 +581,6 @@ Currently, the following UI feature flags are supported:
This should only be used if the room history visibility options are managed by the server.
- `UIFeature.TimelineEnableRelativeDates` - Display relative date separators (eg: 'Today', 'Yesterday') in the
timeline for recent messages. When false day dates will be used.
- `UIFeature.BulkUnverifiedSessionsReminder` - Display popup reminders to verify or remove unverified sessions. Defaults
to true.
- `UIFeature.locationSharing` - Whether or not location sharing menus will be shown.
- `UIFeature.allowCreatingPublicRooms` - Whether or not public rooms can be created.
- `UIFeature.allowCreatingPublicSpaces` - Whether or not public spaces can be created.

View File

@ -75,7 +75,7 @@
"@types/react-dom": "19.2.3",
"oidc-client-ts": "3.4.1",
"jwt-decode": "4.0.0",
"caniuse-lite": "1.0.30001764",
"caniuse-lite": "1.0.30001766",
"testcontainers": "^11.0.0",
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0",
"wrap-ansi": "npm:wrap-ansi@^7.0.0",

View File

@ -18,6 +18,7 @@ export * from "./event-tiles/TextualEventView";
export * from "./message-body/MediaBody";
export * from "./message-body/DecryptionFailureBodyView";
export * from "./message-body/ReactionsRowButtonTooltip";
export * from "./message-body/TimelineSeparator/";
export * from "./pill-input/Pill";
export * from "./pill-input/PillInput";
export * from "./room/RoomStatusBar";

View File

@ -0,0 +1,21 @@
/*
* Copyright 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
.timelineSeparator {
clear: both;
margin: var(--cpd-space-1x) 0;
font: var(--cpd-font-body-md-regular);
letter-spacing: var(--cpd-font-letter-spacing-body-md);
color: var(--cpd-color-text-primary);
}
.timelineSeparator > hr {
flex: 1 1 0;
height: 0;
border: none;
border-bottom: 1px solid var(--cpd-color-gray-400);
}

View File

@ -0,0 +1,54 @@
/*
* Copyright 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import type { Meta, StoryFn } from "@storybook/react-vite";
import TimelineSeparator from "./TimelineSeparator";
import styles from "./TimelineSeparator.module.css";
export default {
title: "MessageBody/TimelineSeparator",
component: TimelineSeparator,
tags: ["autodocs"],
args: {
label: "Label Separator",
children: "Timeline Separator",
},
} as Meta<typeof TimelineSeparator>;
const Template: StoryFn<typeof TimelineSeparator> = (args) => <TimelineSeparator {...args} />;
export const Default = Template.bind({});
export const WithHtmlChild = Template.bind({});
WithHtmlChild.args = {
label: "Custom Label",
children: (
<h2 className={styles.timelineSeparator} aria-hidden="true">
Thursday
</h2>
),
};
export const WithDateEvent = Template.bind({});
WithDateEvent.args = {
label: "Date Event Separator",
children: "Wednesday",
};
export const WithLateEvent = Template.bind({});
WithLateEvent.args = {
label: "Late Event Separator",
children: "Fri, Jan 9, 2026",
};
export const WithoutChildren = Template.bind({});
WithoutChildren.args = {
children: undefined,
label: "Separator without children",
};

View File

@ -0,0 +1,48 @@
/*
* Copyright 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
import { render } from "@test-utils";
import { composeStories } from "@storybook/react-vite";
import React from "react";
import { afterEach, describe, expect, it, vi } from "vitest";
import * as stories from "./TimelineSeparator.stories.tsx";
const { Default, WithHtmlChild, WithoutChildren, WithDateEvent, WithLateEvent } = composeStories(stories);
describe("TimelineSeparator", () => {
afterEach(() => {
vi.clearAllMocks();
});
describe("Snapshot tests", () => {
it("renders the timeline separator in default state", () => {
const { container } = render(<Default />);
expect(container).toMatchSnapshot();
});
it("renders the timeline separator with HTML child", () => {
const { container } = render(<WithHtmlChild />);
expect(container).toMatchSnapshot();
});
it("renders the timeline separator with date event", () => {
const { container } = render(<WithDateEvent />);
expect(container).toMatchSnapshot();
});
it("renders the timeline separator with late event", () => {
const { container } = render(<WithLateEvent />);
expect(container).toMatchSnapshot();
});
it("renders the timeline separator without children", () => {
const { container } = render(<WithoutChildren />);
expect(container).toMatchSnapshot();
});
});
});

View File

@ -0,0 +1,54 @@
/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import React, { type PropsWithChildren } from "react";
import classNames from "classnames";
import styles from "./TimelineSeparator.module.css";
import { Flex } from "../..";
/**
* Timeline separator props
*/
export interface TimelineSeparatorProps {
/**
* Accessible label for the separator (for example: "Today", "Yesterday", or a date).
*/
label: string;
/**
* The CSS class name.
*/
className?: string;
/**
* Optional children to render inside the timeline separator
*/
children?: PropsWithChildren["children"];
}
/**
* Generic timeline separator component to render within a MessagePanel
*
* @param label the accessible label string describing the separator
* @param children the children to draw within the timeline separator
*/
const TimelineSeparator: React.FC<TimelineSeparatorProps> = ({ label, className, children }) => {
// ARIA treats <hr/>s as separators, here we abuse them slightly so manually treat this entire thing as one
return (
<Flex
className={classNames(className, styles.timelineSeparator)}
role="separator"
aria-label={label}
align="center"
>
<hr role="none" />
{children}
<hr role="none" />
</Flex>
);
};
export default TimelineSeparator;

View File

@ -0,0 +1,100 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`TimelineSeparator > Snapshot tests > renders the timeline separator in default state 1`] = `
<div>
<div
aria-label="Label Separator"
class="flex timelineSeparator"
role="separator"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
>
<hr
role="none"
/>
Timeline Separator
<hr
role="none"
/>
</div>
</div>
`;
exports[`TimelineSeparator > Snapshot tests > renders the timeline separator with HTML child 1`] = `
<div>
<div
aria-label="Custom Label"
class="flex timelineSeparator"
role="separator"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
>
<hr
role="none"
/>
<h2
aria-hidden="true"
class="timelineSeparator"
>
Thursday
</h2>
<hr
role="none"
/>
</div>
</div>
`;
exports[`TimelineSeparator > Snapshot tests > renders the timeline separator with date event 1`] = `
<div>
<div
aria-label="Date Event Separator"
class="flex timelineSeparator"
role="separator"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
>
<hr
role="none"
/>
Wednesday
<hr
role="none"
/>
</div>
</div>
`;
exports[`TimelineSeparator > Snapshot tests > renders the timeline separator with late event 1`] = `
<div>
<div
aria-label="Late Event Separator"
class="flex timelineSeparator"
role="separator"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
>
<hr
role="none"
/>
Fri, Jan 9, 2026
<hr
role="none"
/>
</div>
</div>
`;
exports[`TimelineSeparator > Snapshot tests > renders the timeline separator without children 1`] = `
<div>
<div
aria-label="Separator without children"
class="flex timelineSeparator"
role="separator"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
>
<hr
role="none"
/>
<hr
role="none"
/>
</div>
</div>
`;

View File

@ -0,0 +1,8 @@
/*
* Copyright 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
export { default as TimelineSeparator, type TimelineSeparatorProps } from "./TimelineSeparator";

View File

@ -81,7 +81,10 @@ export default defineConfig({
configDir: path.join(dirname, ".storybook"),
storybookScript: "storybook --ci",
}),
storybookVis({}),
storybookVis({
// 3px of difference allowed before marking as failed
failureThreshold: 3,
}),
],
test: {
name: "storybook",

View File

@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
import { SynapseContainer as BaseSynapseContainer } from "@element-hq/element-web-playwright-common/lib/testcontainers/index.js";
const TAG = "develop@sha256:9abab158a1cd7af619d5889fc8c83496e569e647068a8b856f4ad05e8cf342e8";
const TAG = "develop@sha256:4620e446582e79a3942f5438ebf714da18c281143496e53be318334b4697b449";
/**
* SynapseContainer which freezes the docker digest to stabilise tests,

View File

@ -246,7 +246,6 @@
@import "./views/messages/_RedactedBody.pcss";
@import "./views/messages/_RoomAvatarEvent.pcss";
@import "./views/messages/_TextualEvent.pcss";
@import "./views/messages/_TimelineSeparator.pcss";
@import "./views/messages/_UnknownBody.pcss";
@import "./views/messages/_ViewSourceEvent.pcss";
@import "./views/messages/_common_CryptoEvent.pcss";

View File

@ -1,23 +0,0 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2017 Vector Creations Ltd
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
.mx_TimelineSeparator {
clear: both;
margin: 4px 0;
display: flex;
align-items: center;
font: var(--cpd-font-body-md-regular);
color: var(--cpd-color-text-primary);
}
.mx_TimelineSeparator > hr {
flex: 1 1 0;
height: 0;
border: none;
border-bottom: 1px solid var(--cpd-color-gray-400);
}

View File

@ -43,7 +43,6 @@ import SdkConfig from "./SdkConfig";
import PlatformPeg from "./PlatformPeg";
import { recordClientInformation, removeClientInformation } from "./utils/device/clientInformation";
import SettingsStore, { type CallbackFn } from "./settings/SettingsStore";
import { UIFeature } from "./settings/UIFeature";
import { isBulkUnverifiedDeviceReminderSnoozed } from "./utils/device/snoozeBulkUnverifiedDeviceReminder";
import { getUserDeviceIds } from "./utils/crypto/deviceInfo";
import { asyncSomeParallel } from "./utils/arrays.ts";
@ -125,7 +124,6 @@ export default class DeviceListener extends TypedEventEmitter<DeviceListenerEven
// The client with which the instance is running. Only set if `running` is true, otherwise undefined.
private client?: MatrixClient;
private shouldRecordClientInformation = false;
private enableBulkUnverifiedSessionsReminder = true;
private deviceClientInformationSettingWatcherRef: string | undefined;
private deviceState: DeviceState = "ok";
@ -151,7 +149,6 @@ export default class DeviceListener extends TypedEventEmitter<DeviceListenerEven
this.client.on(ClientEvent.ToDeviceEvent, this.onToDeviceEvent);
this.shouldRecordClientInformation = SettingsStore.getValue("deviceClientInformationOptIn");
// only configurable in config, so we don't need to watch the value
this.enableBulkUnverifiedSessionsReminder = SettingsStore.getValue(UIFeature.BulkUnverifiedSessionsReminder);
this.deviceClientInformationSettingWatcherRef = SettingsStore.watchSetting(
"deviceClientInformationOptIn",
null,
@ -587,12 +584,7 @@ export default class DeviceListener extends TypedEventEmitter<DeviceListenerEven
// Display or hide the batch toast for old unverified sessions
// don't show the toast if the current device is unverified
if (
oldUnverifiedDeviceIds.size > 0 &&
isCurrentDeviceTrusted &&
this.enableBulkUnverifiedSessionsReminder &&
!isBulkUnverifiedSessionsReminderSnoozed
) {
if (oldUnverifiedDeviceIds.size > 0 && isCurrentDeviceTrusted && !isBulkUnverifiedSessionsReminderSnoozed) {
showBulkUnverifiedSessionsToast(oldUnverifiedDeviceIds);
} else {
hideBulkUnverifiedSessionsToast();

View File

@ -18,6 +18,7 @@ import {
} from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import { isSupportedReceiptType } from "matrix-js-sdk/src/utils";
import { TimelineSeparator } from "@element-hq/web-shared-components";
import shouldHideEvent from "../../shouldHideEvent";
import { formatDate, wantsDateSeparator } from "../../DateUtils";
@ -37,7 +38,6 @@ import type LegacyCallEventGrouper from "./LegacyCallEventGrouper";
import WhoIsTypingTile from "../views/rooms/WhoIsTypingTile";
import ScrollPanel, { type IScrollState } from "./ScrollPanel";
import DateSeparator from "../views/messages/DateSeparator";
import TimelineSeparator, { SeparatorKind } from "../views/messages/TimelineSeparator";
import ErrorBoundary from "../views/elements/ErrorBoundary";
import Spinner from "../views/elements/Spinner";
import { type RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
@ -57,6 +57,18 @@ import { getLateEventInfo } from "./grouper/LateEventGrouper";
const CONTINUATION_MAX_INTERVAL = 5 * 60 * 1000; // 5 minutes
const continuedTypes = [EventType.Sticker, EventType.RoomMessage];
/**
* Indicates which separator (if any) should be rendered between timeline events.
*/
export const enum SeparatorKind {
/** No separator should be shown between the two events. */
None,
/** Insert a date separator (oriented by event date boundaries). */
Date,
/** Insert a late-event separator when events belong to different late groups. */
LateEvent,
}
// check if there is a previous event and it has the same sender as this event
// and the types are the same/is in continuedTypes and the time between them is <= CONTINUATION_MAX_INTERVAL
export function shouldFormContinuation(
@ -756,7 +768,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
});
ret.push(
<li key={ts1}>
<TimelineSeparator key={ts1} label={text}>
<TimelineSeparator key={ts1} label={text} className="mx_TimelineSeparator">
{text}
</TimelineSeparator>
</li>,

View File

@ -11,14 +11,13 @@ import { EventType, M_BEACON_INFO, type MatrixEvent } from "matrix-js-sdk/src/ma
import { KnownMembership } from "matrix-js-sdk/src/types";
import { BaseGrouper } from "./BaseGrouper";
import { type WrappedEvent } from "../MessagePanel";
import { SeparatorKind, type WrappedEvent } from "../MessagePanel";
import type MessagePanel from "../MessagePanel";
import DMRoomMap from "../../../utils/DMRoomMap";
import { _t } from "../../../languageHandler";
import DateSeparator from "../../views/messages/DateSeparator";
import NewRoomIntro from "../../views/rooms/NewRoomIntro";
import GenericEventListSummary from "../../views/elements/GenericEventListSummary";
import { SeparatorKind } from "../../views/messages/TimelineSeparator";
// Wrap initial room creation events into a GenericEventListSummary
// Grouping only events sent by the same user that sent the `m.room.create` and only until

View File

@ -10,14 +10,13 @@ import React, { type ReactNode } from "react";
import { EventType, type MatrixEvent } from "matrix-js-sdk/src/matrix";
import type MessagePanel from "../MessagePanel";
import type { WrappedEvent } from "../MessagePanel";
import { SeparatorKind, type WrappedEvent } from "../MessagePanel";
import { BaseGrouper } from "./BaseGrouper";
import { hasText } from "../../../TextForEvent";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import DateSeparator from "../../views/messages/DateSeparator";
import HistoryTile from "../../views/rooms/HistoryTile";
import EventListSummary from "../../views/elements/EventListSummary";
import { SeparatorKind } from "../../views/messages/TimelineSeparator";
const groupedStateEvents = [
EventType.RoomMember,

View File

@ -12,6 +12,7 @@ import { Direction, ConnectionError, MatrixError, HTTPError } from "matrix-js-sd
import { logger } from "matrix-js-sdk/src/logger";
import { capitalize } from "lodash";
import { ChevronDownIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { TimelineSeparator } from "@element-hq/web-shared-components";
import { _t, getUserLanguage } from "../../../languageHandler";
import { formatFullDateNoDay, formatFullDateNoTime, getDaysArray } from "../../../DateUtils";
@ -32,7 +33,6 @@ import IconizedContextMenu, {
} from "../context_menus/IconizedContextMenu";
import JumpToDatePicker from "./JumpToDatePicker";
import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import TimelineSeparator from "./TimelineSeparator";
import RoomContext from "../../../contexts/RoomContext";
interface IProps {
@ -335,6 +335,10 @@ export default class DateSeparator extends React.Component<IProps, IState> {
);
}
return <TimelineSeparator label={label}>{dateHeaderContent}</TimelineSeparator>;
return (
<TimelineSeparator label={label} className="mx_TimelineSeparator">
{dateHeaderContent}
</TimelineSeparator>
);
}
}

View File

@ -1,39 +0,0 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import React, { type ReactNode } from "react";
interface Props {
label: string;
children?: ReactNode;
}
export const enum SeparatorKind {
None,
Date,
LateEvent,
}
/**
* Generic timeline separator component to render within a MessagePanel
*
* @param label the accessible label string describing the separator
* @param children the children to draw within the timeline separator
*/
const TimelineSeparator: React.FC<Props> = ({ label, children }) => {
// ARIA treats <hr/>s as separators, here we abuse them slightly so manually treat this entire thing as one
return (
<div className="mx_TimelineSeparator" role="separator" aria-label={label}>
<hr role="none" />
{children}
<hr role="none" />
</div>
);
};
export default TimelineSeparator;

View File

@ -1437,10 +1437,6 @@ export const SETTINGS: Settings = {
supportedLevels: LEVELS_UI_FEATURE,
default: true,
},
[UIFeature.BulkUnverifiedSessionsReminder]: {
supportedLevels: LEVELS_UI_FEATURE,
default: true,
},
[UIFeature.AllowCreatingPublicSpaces]: {
supportedLevels: LEVELS_UI_FEATURE,
default: true,

View File

@ -24,7 +24,6 @@ export const enum UIFeature {
AdvancedSettings = "UIFeature.advancedSettings",
RoomHistorySettings = "UIFeature.roomHistorySettings",
TimelineEnableRelativeDates = "UIFeature.timelineEnableRelativeDates",
BulkUnverifiedSessionsReminder = "UIFeature.BulkUnverifiedSessionsReminder",
AllowCreatingPublicRooms = "UIFeature.allowCreatingPublicRooms",
AllowCreatingPublicSpaces = "UIFeature.allowCreatingPublicSpaces",
}

View File

@ -63,7 +63,7 @@ export default class MatrixToPermalinkConstructor extends PermalinkConstructor {
const entity = parts[0];
if (entity[0] === "@") {
// Probably a user, no further parsing needed.
return PermalinkParts.forUser(entity);
return PermalinkParts.forUser(matches[1]);
} else if (entity[0] === "#" || entity[0] === "!") {
if (parts.length === 1) {
// room without event permalink

View File

@ -35,7 +35,6 @@ import { Action } from "../../src/dispatcher/actions";
import SettingsStore from "../../src/settings/SettingsStore";
import { SettingLevel } from "../../src/settings/SettingLevel";
import { getMockClientWithEventEmitter, mockPlatformPeg } from "../test-utils";
import { UIFeature } from "../../src/settings/UIFeature";
import { isBulkUnverifiedDeviceReminderSnoozed } from "../../src/utils/device/snoozeBulkUnverifiedDeviceReminder";
import { PosthogAnalytics } from "../../src/PosthogAnalytics";
@ -653,10 +652,8 @@ describe("DeviceListener", () => {
// all devices verified by default
mockCrypto!.getDeviceVerificationStatus.mockResolvedValue(deviceTrustVerified);
mockClient!.deviceId = currentDevice.deviceId;
jest.spyOn(SettingsStore, "getValue").mockImplementation(
(settingName) => settingName === UIFeature.BulkUnverifiedSessionsReminder,
);
});
describe("bulk unverified sessions toasts", () => {
it("hides toast when cross signing is not ready", async () => {
mockCrypto!.isCrossSigningReady.mockResolvedValue(false);
@ -671,24 +668,6 @@ describe("DeviceListener", () => {
expect(BulkUnverifiedSessionsToast.showToast).not.toHaveBeenCalled();
});
it("hides toast when feature is disabled", async () => {
// BulkUnverifiedSessionsReminder set to false
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
// currentDevice, device2 are verified, device3 is unverified
// ie if reminder was enabled it should be shown
mockCrypto!.getDeviceVerificationStatus.mockImplementation(async (_userId, deviceId) => {
switch (deviceId) {
case currentDevice.deviceId:
case device2.deviceId:
return deviceTrustVerified;
default:
return deviceTrustUnverified;
}
});
await createAndStart();
expect(BulkUnverifiedSessionsToast.hideToast).toHaveBeenCalled();
});
it("hides toast when current device is unverified", async () => {
// device2 verified, current and device3 unverified
mockCrypto!.getDeviceVerificationStatus.mockImplementation(async (_userId, deviceId) => {

View File

@ -66,17 +66,17 @@ describe("SupportedBrowser", () => {
// Safari 26.0 on macOS
"Mozilla/5.0 (Macintosh; Intel Mac OS X 15_7_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Safari/605.1.15",
// Latest Firefox on macOS Sonoma
"Mozilla/5.0 (Macintosh; Intel Mac OS X 15.7; rv:145.0) Gecko/20100101 Firefox/145.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 15.7; rv:145.0) Gecko/20100101 Firefox/147.0",
// Latest Edge on Windows
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.3595.76",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36 Edg/144.0.3595.76",
// Latest Edge on macOS
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.3595.76",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36 Edg/144.0.3595.76",
// Latest Firefox on Windows
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:145.0) Gecko/20100101 Firefox/145.0",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:147.0) Gecko/20100101 Firefox/147.0",
// Latest Firefox on Linux
"Mozilla/5.0 (X11; Linux i686; rv:145.0) Gecko/20100101 Firefox/145.0",
"Mozilla/5.0 (X11; Linux i686; rv:147.0) Gecko/20100101 Firefox/147.0",
// Latest Chrome on Windows
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36",
])("should not warn for supported browsers", testUserAgentFactory());
it.each([

View File

@ -39,8 +39,9 @@ exports[`MessagePanel should handle lots of membership events quickly 1`] = `
>
<div
aria-label="Thu, Jan 1, 1970"
class="mx_TimelineSeparator"
class="_flex_4dswl_9 mx_TimelineSeparator _timelineSeparator_yq5ye_8"
role="separator"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
>
<hr
role="none"

View File

@ -41,8 +41,9 @@ exports[`<MessageEditHistory /> should match the snapshot 1`] = `
<li>
<div
aria-label="Thu, Jan 1, 1970"
class="mx_TimelineSeparator"
class="_flex_4dswl_9 mx_TimelineSeparator _timelineSeparator_yq5ye_8"
role="separator"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
>
<hr
role="none"
@ -171,8 +172,9 @@ exports[`<MessageEditHistory /> should support events with 1`] = `
<li>
<div
aria-label="Thu, Jan 1, 1970"
class="mx_TimelineSeparator"
class="_flex_4dswl_9 mx_TimelineSeparator _timelineSeparator_yq5ye_8"
role="separator"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
>
<hr
role="none"

View File

@ -4,8 +4,9 @@ exports[`DateSeparator renders invalid date separator correctly 1`] = `
<DocumentFragment>
<div
aria-label="Invalid timestamp"
class="mx_TimelineSeparator"
class="_flex_4dswl_9 mx_TimelineSeparator _timelineSeparator_yq5ye_8"
role="separator"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
>
<hr
role="none"
@ -31,8 +32,9 @@ exports[`DateSeparator renders the date separator correctly 1`] = `
<DocumentFragment>
<div
aria-label="today"
class="mx_TimelineSeparator"
class="_flex_4dswl_9 mx_TimelineSeparator _timelineSeparator_yq5ye_8"
role="separator"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
>
<hr
role="none"
@ -58,8 +60,9 @@ exports[`DateSeparator when feature_jump_to_date is enabled renders the date sep
<DocumentFragment>
<div
aria-label="Fri, Dec 17, 2021"
class="mx_TimelineSeparator"
class="_flex_4dswl_9 mx_TimelineSeparator _timelineSeparator_yq5ye_8"
role="separator"
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: 0; --mx-flex-wrap: nowrap;"
>
<hr
role="none"

File diff suppressed because one or more lines are too long

View File

@ -1727,8 +1727,18 @@
yaml "^2.7.0"
"@element-hq/web-shared-components@link:packages/shared-components":
version "0.0.0"
uid ""
version "0.0.1"
dependencies:
"@element-hq/element-web-module-api" "^1.8.0"
"@matrix-org/spec" "^1.7.0"
"@vector-im/compound-design-tokens" "^6.4.3"
classnames "^2.5.1"
counterpart "^0.18.6"
lodash "^4.17.21"
matrix-web-i18n "3.6.0"
react-merge-refs "^3.0.2"
react-virtuoso "^4.14.0"
temporal-polyfill "^0.3.0"
"@emnapi/core@^1.4.3":
version "1.7.0"
@ -4500,7 +4510,6 @@
"@vector-im/matrix-wysiwyg-wasm@link:../../Library/Caches/Yarn/v6/npm-@vector-im-matrix-wysiwyg-2.40.0-53c9ca5ea907d91e4515da64f20a82e5586b882c-integrity/node_modules/bindings/wysiwyg-wasm":
version "0.0.0"
uid ""
"@vector-im/matrix-wysiwyg@2.40.0":
version "2.40.0"
@ -5564,10 +5573,10 @@ caniuse-api@^3.0.0:
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"
caniuse-lite@1.0.30001764, caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001759, caniuse-lite@^1.0.30001760:
version "1.0.30001764"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz#03206c56469f236103b90f9ae10bcb8b9e1f6005"
integrity sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==
caniuse-lite@1.0.30001766, caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001759, caniuse-lite@^1.0.30001760:
version "1.0.30001766"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz#b6f6b55cb25a2d888d9393104d14751c6a7d6f7a"
integrity sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==
chalk@4.1.2, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.2:
version "4.1.2"
@ -9802,9 +9811,9 @@ matrix-web-i18n@3.6.0:
walk "^2.3.15"
matrix-widget-api@^1.16.1:
version "1.16.1"
resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-1.16.1.tgz#a447f28f0af07e1bdc960881971de7d1ec9e6464"
integrity sha512-oCfTV4xNPo02qIgveqdkIyKQjOPpsjhF3bmJBotHrhr8TsrhVa7kx8PtuiUPnQTjz0tdBle7falR2Fw8VKsedw==
version "1.17.0"
resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-1.17.0.tgz#2336de2186fe70d8bd741c1603c162f60b2099c2"
integrity sha512-5FHoo3iEP3Bdlv5jsYPWOqj+pGdFQNLWnJLiB0V7Ygne7bb+Gsj3ibyFyHWC6BVw+Z+tSW4ljHpO17I9TwStwQ==
dependencies:
"@types/events" "^3.0.0"
events "^3.2.0"