mirror of
https://github.com/vector-im/element-web.git
synced 2026-05-08 13:46:16 +02:00
Merge branch 'develop' of ssh://github.com/element-hq/element-web into t3chguy/pnpm
# Conflicts: # yarn.lock
This commit is contained in:
commit
5dac43fac5
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
@ -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";
|
||||
|
||||
@ -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);
|
||||
}
|
||||
@ -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",
|
||||
};
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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;
|
||||
@ -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>
|
||||
`;
|
||||
@ -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";
|
||||
@ -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";
|
||||
|
||||
@ -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);
|
||||
}
|
||||
@ -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();
|
||||
|
||||
@ -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>,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
@ -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,
|
||||
|
||||
@ -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",
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
Loading…
x
Reference in New Issue
Block a user