Compare commits

..

3 commits

Author SHA1 Message Date
Florian Duros
1811aa6c98
Add recovery section 2024-12-11 18:22:25 +01:00
Florian Duros
8747e62103
Add encryption tab 2024-12-06 17:21:04 +01:00
Florian Duros
4958dad672
Refine SettingsSection & SettingsTab 2024-12-06 17:20:53 +01:00
72 changed files with 1364 additions and 769 deletions

View file

@ -104,7 +104,7 @@ jobs:
- name: Skip SonarCloud in merge queue - name: Skip SonarCloud in merge queue
if: github.event_name == 'merge_group' || inputs.disable_coverage == 'true' if: github.event_name == 'merge_group' || inputs.disable_coverage == 'true'
uses: guibranco/github-status-action-v2@d469d49426f5a7b8a1fbcac20ad274d3e4892321 uses: guibranco/github-status-action-v2@66088c44e212a906c32a047529a213d81809ec1c
with: with:
authToken: ${{ secrets.GITHUB_TOKEN }} authToken: ${{ secrets.GITHUB_TOKEN }}
state: success state: success

View file

@ -88,7 +88,7 @@
"@matrix-org/spec": "^1.7.0", "@matrix-org/spec": "^1.7.0",
"@sentry/browser": "^8.0.0", "@sentry/browser": "^8.0.0",
"@vector-im/compound-design-tokens": "^2.0.1", "@vector-im/compound-design-tokens": "^2.0.1",
"@vector-im/compound-web": "^7.5.0", "@vector-im/compound-web": "^7.4.0",
"@vector-im/matrix-wysiwyg": "2.37.13", "@vector-im/matrix-wysiwyg": "2.37.13",
"@zxcvbn-ts/core": "^3.0.4", "@zxcvbn-ts/core": "^3.0.4",
"@zxcvbn-ts/language-common": "^3.0.4", "@zxcvbn-ts/language-common": "^3.0.4",
@ -269,7 +269,7 @@
"postcss-preset-env": "^10.0.0", "postcss-preset-env": "^10.0.0",
"postcss-scss": "^4.0.4", "postcss-scss": "^4.0.4",
"postcss-simple-vars": "^7.0.1", "postcss-simple-vars": "^7.0.1",
"prettier": "3.4.2", "prettier": "3.4.1",
"process": "^0.11.10", "process": "^0.11.10",
"raw-loader": "^4.0.2", "raw-loader": "^4.0.2",
"rimraf": "^6.0.0", "rimraf": "^6.0.0",

View file

@ -8,7 +8,6 @@ Please see LICENSE files in the repository root for full details.
import { type Preset, type Visibility } from "matrix-js-sdk/src/matrix"; import { type Preset, type Visibility } from "matrix-js-sdk/src/matrix";
import type { Page } from "@playwright/test";
import { test, expect } from "../../element-web-test"; import { test, expect } from "../../element-web-test";
import { doTwoWaySasVerification, awaitVerifier } from "./utils"; import { doTwoWaySasVerification, awaitVerifier } from "./utils";
import { Client } from "../../pages/client"; import { Client } from "../../pages/client";
@ -39,8 +38,6 @@ test.describe("User verification", () => {
toasts, toasts,
room: { roomId: dmRoomId }, room: { roomId: dmRoomId },
}) => { }) => {
await waitForDeviceKeys(page);
// once Alice has joined, Bob starts the verification // once Alice has joined, Bob starts the verification
const bobVerificationRequest = await bob.evaluateHandle( const bobVerificationRequest = await bob.evaluateHandle(
async (client, { dmRoomId, aliceCredentials }) => { async (client, { dmRoomId, aliceCredentials }) => {
@ -90,8 +87,6 @@ test.describe("User verification", () => {
toasts, toasts,
room: { roomId: dmRoomId }, room: { roomId: dmRoomId },
}) => { }) => {
await waitForDeviceKeys(page);
// once Alice has joined, Bob starts the verification // once Alice has joined, Bob starts the verification
const bobVerificationRequest = await bob.evaluateHandle( const bobVerificationRequest = await bob.evaluateHandle(
async (client, { dmRoomId, aliceCredentials }) => { async (client, { dmRoomId, aliceCredentials }) => {
@ -154,15 +149,3 @@ async function createDMRoom(client: Client, userId: string): Promise<string> {
], ],
}); });
} }
/**
* Wait until we get the other user's device keys.
* In newer rust-crypto versions, the verification request will be ignored if we
* don't have the sender's device keys.
*/
async function waitForDeviceKeys(page: Page): Promise<void> {
await expect(page.getByRole("button", { name: "Avatar" })).toBeVisible();
const avatar = await page.getByRole("button", { name: "Avatar" });
await avatar.click();
await expect(page.getByText("1 session")).toBeVisible();
}

View file

@ -20,7 +20,7 @@ import { randB64Bytes } from "../../utils/rand";
// Docker tag to use for synapse docker image. // 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. // 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. // This digest is updated by the playwright-image-updates.yaml workflow periodically.
const DOCKER_TAG = "develop@sha256:6b82dba715fa7ae641010b4cc5e71edaeb9cc05a50ac5b9e4ff09afa9cd2a80d"; const DOCKER_TAG = "develop@sha256:6ff2b43b7412eb4155c0147441421b31fc4b31acd56be82cf27daf172ababa4d";
async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise<Omit<HomeserverConfig, "dockerUrl">> { async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise<Omit<HomeserverConfig, "dockerUrl">> {
const templateDir = path.join(__dirname, "templates", opts.template); const templateDir = path.join(__dirname, "templates", opts.template);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Before After
Before After

View file

@ -596,7 +596,9 @@ legend {
.mx_Dialog .mx_Dialog
button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not( button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not(
.mx_UserProfileSettings button .mx_UserProfileSettings button
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button), ):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(
.mx_EncryptionUserSettingsTab button
),
.mx_Dialog input[type="submit"], .mx_Dialog input[type="submit"],
.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton), .mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton),
.mx_Dialog_buttons input[type="submit"] { .mx_Dialog_buttons input[type="submit"] {
@ -616,8 +618,8 @@ legend {
.mx_Dialog .mx_Dialog
button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not( button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not(
.mx_UserProfileSettings button .mx_UserProfileSettings button
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not( ):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(
.mx_ShareDialog button .mx_EncryptionUserSettingsTab button
):last-child { ):last-child {
margin-right: 0px; margin-right: 0px;
} }
@ -625,7 +627,9 @@ legend {
.mx_Dialog .mx_Dialog
button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not( button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not(
.mx_UserProfileSettings button .mx_UserProfileSettings button
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):focus, ):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(
.mx_EncryptionUserSettingsTab button
):focus,
.mx_Dialog input[type="submit"]:focus, .mx_Dialog input[type="submit"]:focus,
.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):focus, .mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):focus,
.mx_Dialog_buttons input[type="submit"]:focus { .mx_Dialog_buttons input[type="submit"]:focus {
@ -637,7 +641,9 @@ legend {
.mx_Dialog_buttons .mx_Dialog_buttons
button.mx_Dialog_primary:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):not( button.mx_Dialog_primary:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):not(
.mx_UserProfileSettings button .mx_UserProfileSettings button
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button), ):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(
.mx_EncryptionUserSettingsTab button
),
.mx_Dialog_buttons input[type="submit"].mx_Dialog_primary { .mx_Dialog_buttons input[type="submit"].mx_Dialog_primary {
color: var(--cpd-color-text-on-solid-primary); color: var(--cpd-color-text-on-solid-primary);
background-color: var(--cpd-color-bg-action-primary-rest); background-color: var(--cpd-color-bg-action-primary-rest);
@ -650,7 +656,7 @@ legend {
.mx_Dialog_buttons .mx_Dialog_buttons
button.danger:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):not(.mx_UserProfileSettings button):not( button.danger:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):not(.mx_UserProfileSettings button):not(
.mx_ThemeChoicePanel_CustomTheme button .mx_ThemeChoicePanel_CustomTheme button
):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button), ):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(.mx_EncryptionUserSettingsTab button),
.mx_Dialog_buttons input[type="submit"].danger { .mx_Dialog_buttons input[type="submit"].danger {
background-color: var(--cpd-color-bg-critical-primary); background-color: var(--cpd-color-bg-critical-primary);
border: solid 1px var(--cpd-color-bg-critical-primary); border: solid 1px var(--cpd-color-bg-critical-primary);
@ -666,7 +672,9 @@ legend {
.mx_Dialog .mx_Dialog
button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not( button:not(.mx_Dialog_nonDialogButton):not([class|="maplibregl"]):not(.mx_AccessibleButton):not(
.mx_UserProfileSettings button .mx_UserProfileSettings button
):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):disabled, ):not(.mx_ThemeChoicePanel_CustomTheme button):not(.mx_UnpinAllDialog button):not(.mx_ShareDialog button):not(
.mx_EncryptionUserSettingsTab button
):disabled,
.mx_Dialog input[type="submit"]:disabled, .mx_Dialog input[type="submit"]:disabled,
.mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):disabled, .mx_Dialog_buttons button:not(.mx_Dialog_nonDialogButton):not(.mx_AccessibleButton):disabled,
.mx_Dialog_buttons input[type="submit"]:disabled { .mx_Dialog_buttons input[type="submit"]:disabled {

View file

@ -346,10 +346,14 @@
@import "./views/settings/_SetIdServer.pcss"; @import "./views/settings/_SetIdServer.pcss";
@import "./views/settings/_SetIntegrationManager.pcss"; @import "./views/settings/_SetIntegrationManager.pcss";
@import "./views/settings/_SettingsFieldset.pcss"; @import "./views/settings/_SettingsFieldset.pcss";
@import "./views/settings/_SettingsHeader.pcss";
@import "./views/settings/_SpellCheckLanguages.pcss"; @import "./views/settings/_SpellCheckLanguages.pcss";
@import "./views/settings/_ThemeChoicePanel.pcss"; @import "./views/settings/_ThemeChoicePanel.pcss";
@import "./views/settings/_UpdateCheckButton.pcss"; @import "./views/settings/_UpdateCheckButton.pcss";
@import "./views/settings/_UserProfileSettings.pcss"; @import "./views/settings/_UserProfileSettings.pcss";
@import "./views/settings/encryption/_ChangeRecoveryKey.pcss";
@import "./views/settings/encryption/_RecoveryPanel.pcss";
@import "./views/settings/encryption/_EncryptionCard.pcss";
@import "./views/settings/tabs/_SettingsBanner.pcss"; @import "./views/settings/tabs/_SettingsBanner.pcss";
@import "./views/settings/tabs/_SettingsIndent.pcss"; @import "./views/settings/tabs/_SettingsIndent.pcss";
@import "./views/settings/tabs/_SettingsSection.pcss"; @import "./views/settings/tabs/_SettingsSection.pcss";

View file

@ -0,0 +1,19 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
* Please see LICENSE files in the repository root for full details.
*/
.mx_SettingsHeader {
display: flex;
align-items: center;
gap: var(--cpd-space-2x);
/* Override margin from common.pcss */
margin: 0;
> span {
font: var(--cpd-font-body-sm-medium);
color: var(--cpd-color-text-action-accent);
}
}

View file

@ -0,0 +1,73 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
* Please see LICENSE files in the repository root for full details.
*/
.mx_ChangeRecoveryKey {
.mx_WarningPanel_description {
text-align: center;
}
.mx_ChangeRecoveryKey_Form {
display: flex;
flex-direction: column;
gap: var(--cpd-space-8x);
.mx_ChangeRecoveryKey_footer {
display: flex;
flex-direction: column;
gap: var(--cpd-space-4x);
justify-content: center;
}
}
.mx_KeyPanel {
display: grid;
grid-template:
"header button" auto
"content button" auto;
column-gap: var(--cpd-space-3x);
row-gap: var(--cpd-space-1x);
align-items: center;
> span {
grid-area: header;
}
> div {
grid-area: content;
display: flex;
flex-direction: column;
gap: var(--cpd-space-2x);
color: var(--cpd-color-text-secondary);
.mx_KeyPanel_key {
border-radius: var(--cpd-space-2x);
padding: var(--cpd-space-3x) var(--cpd-space-4x);
background-color: var(--cpd-color-bg-subtle-secondary);
}
}
> button {
margin: 0 var(--cpd-space-1x);
grid-area: button;
color: var(--cpd-color-icon-secondary-alpha);
}
}
.mx_KeyForm {
display: flex;
flex-direction: column;
gap: var(--cpd-space-8x);
}
.mx_ChangeRecoveryKey_footer {
display: flex;
flex-direction: column;
gap: var(--cpd-space-4x);
justify-content: center;
}
}

View file

@ -0,0 +1,33 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
* Please see LICENSE files in the repository root for full details.
*/
.mx_EncryptionCard {
display: flex;
flex-direction: column;
gap: var(--cpd-space-8x);
padding: var(--cpd-space-10x);
border-radius: var(--cpd-space-4x);
/* From figma */
box-shadow: 0 1.2px 2.4px 0 rgba(27, 29, 34, 0.15);
border: 1px solid var(--cpd-color-gray-400);
.mx_EncryptionCard_header {
display: flex;
flex-direction: column;
gap: var(--cpd-space-4x);
align-items: center;
> h2 {
margin: 0;
}
> span {
color: var(--cpd-color-text-secondary);
text-align: center;
}
}
}

View file

@ -0,0 +1,22 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
* Please see LICENSE files in the repository root for full details.
*/
.mx_RecoveryPanel {
.mx_RecoveryPanel_Subheader {
display: flex;
flex-direction: column;
gap: var(--cpd-space-2x);
> span {
display: flex;
align-items: center;
gap: var(--cpd-space-2x);
font: var(--cpd-font-body-sm-medium);
color: var(--cpd-color-text-success-primary);
}
}
}

View file

@ -15,6 +15,20 @@ Please see LICENSE files in the repository root for full details.
a { a {
color: $links; color: $links;
} }
&.mx_SettingsSection_newUi {
display: flex;
flex-direction: column;
gap: var(--cpd-space-6x);
align-items: start;
}
.mx_SettingsSection_header {
display: flex;
flex-direction: column;
gap: var(--cpd-space-3x);
color: var(--cpd-color-text-secondary);
}
} }
.mx_SettingsSection_subSections { .mx_SettingsSection_subSections {

View file

@ -14,7 +14,7 @@ Please see LICENSE files in the repository root for full details.
color: $links; color: $links;
} }
form { form:not(.mx_EncryptionUserSettingsTab form) {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: $spacing-8; gap: $spacing-8;

View file

@ -44,7 +44,6 @@ import { IConfigOptions } from "../IConfigOptions";
import { MatrixDispatcher } from "../dispatcher/dispatcher"; import { MatrixDispatcher } from "../dispatcher/dispatcher";
import { DeepReadonly } from "./common"; import { DeepReadonly } from "./common";
import MatrixChat from "../components/structures/MatrixChat"; import MatrixChat from "../components/structures/MatrixChat";
import { InitialCryptoSetupStore } from "../stores/InitialCryptoSetupStore";
/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/naming-convention */
@ -118,7 +117,6 @@ declare global {
mxPerformanceEntryNames: any; mxPerformanceEntryNames: any;
mxUIStore: UIStore; mxUIStore: UIStore;
mxSetupEncryptionStore?: SetupEncryptionStore; mxSetupEncryptionStore?: SetupEncryptionStore;
mxInitialCryptoStore?: InitialCryptoSetupStore;
mxRoomScrollStateStore?: RoomScrollStateStore; mxRoomScrollStateStore?: RoomScrollStateStore;
mxActiveWidgetStore?: ActiveWidgetStore; mxActiveWidgetStore?: ActiveWidgetStore;
mxOnRecaptchaLoaded?: () => void; mxOnRecaptchaLoaded?: () => void;

View file

@ -8,24 +8,25 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import React, { forwardRef, Ref } from "react"; import React, { ComponentProps, forwardRef, Ref } from "react";
import AccessibleButton, { ButtonProps } from "../../components/views/elements/AccessibleButton"; import AccessibleButton from "../../components/views/elements/AccessibleButton";
type Props<T extends keyof HTMLElementTagNameMap> = ButtonProps<T> & { type Props<T extends keyof JSX.IntrinsicElements> = ComponentProps<typeof AccessibleButton<T>> & {
label?: string; label?: string;
// whether the context menu is currently open // whether the context menu is currently open
isExpanded: boolean; isExpanded: boolean;
}; };
// Semantic component for representing the AccessibleButton which launches a <ContextMenu /> // Semantic component for representing the AccessibleButton which launches a <ContextMenu />
export const ContextMenuButton = forwardRef(function <T extends keyof HTMLElementTagNameMap>( export const ContextMenuButton = forwardRef(function <T extends keyof JSX.IntrinsicElements>(
{ label, isExpanded, children, onClick, onContextMenu, ...props }: Props<T>, { label, isExpanded, children, onClick, onContextMenu, element, ...props }: Props<T>,
ref: Ref<HTMLElementTagNameMap[T]>, ref: Ref<HTMLElement>,
) { ) {
return ( return (
<AccessibleButton <AccessibleButton
{...props} {...props}
element={element as keyof JSX.IntrinsicElements}
onClick={onClick} onClick={onClick}
onContextMenu={onContextMenu ?? onClick ?? undefined} onContextMenu={onContextMenu ?? onClick ?? undefined}
aria-label={label} aria-label={label}

View file

@ -8,23 +8,24 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import React, { forwardRef, Ref } from "react"; import React, { ComponentProps, forwardRef, Ref } from "react";
import AccessibleButton, { ButtonProps } from "../../components/views/elements/AccessibleButton"; import AccessibleButton from "../../components/views/elements/AccessibleButton";
type Props<T extends keyof HTMLElementTagNameMap> = ButtonProps<T> & { type Props<T extends keyof JSX.IntrinsicElements> = ComponentProps<typeof AccessibleButton<T>> & {
// whether the context menu is currently open // whether the context menu is currently open
isExpanded: boolean; isExpanded: boolean;
}; };
// Semantic component for representing the AccessibleButton which launches a <ContextMenu /> // Semantic component for representing the AccessibleButton which launches a <ContextMenu />
export const ContextMenuTooltipButton = forwardRef(function <T extends keyof HTMLElementTagNameMap>( export const ContextMenuTooltipButton = forwardRef(function <T extends keyof JSX.IntrinsicElements>(
{ isExpanded, children, onClick, onContextMenu, ...props }: Props<T>, { isExpanded, children, onClick, onContextMenu, element, ...props }: Props<T>,
ref: Ref<HTMLElementTagNameMap[T]>, ref: Ref<HTMLElement>,
) { ) {
return ( return (
<AccessibleButton <AccessibleButton
{...props} {...props}
element={element as keyof JSX.IntrinsicElements}
onClick={onClick} onClick={onClick}
onContextMenu={onContextMenu ?? onClick ?? undefined} onContextMenu={onContextMenu ?? onClick ?? undefined}
aria-haspopup={true} aria-haspopup={true}

View file

@ -6,33 +6,39 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import React, { RefObject } from "react"; import React, { ComponentProps } from "react";
import AccessibleButton, { ButtonProps } from "../../components/views/elements/AccessibleButton"; import AccessibleButton from "../../components/views/elements/AccessibleButton";
import { useRovingTabIndex } from "../RovingTabIndex"; import { useRovingTabIndex } from "../RovingTabIndex";
import { Ref } from "./types";
type Props<T extends keyof HTMLElementTagNameMap> = Omit<ButtonProps<T>, "tabIndex"> & { type Props<T extends keyof JSX.IntrinsicElements> = Omit<
inputRef?: RefObject<HTMLElementTagNameMap[T]>; ComponentProps<typeof AccessibleButton<T>>,
"inputRef" | "tabIndex"
> & {
inputRef?: Ref;
focusOnMouseOver?: boolean; focusOnMouseOver?: boolean;
}; };
// Wrapper to allow use of useRovingTabIndex for simple AccessibleButtons outside of React Functional Components. // Wrapper to allow use of useRovingTabIndex for simple AccessibleButtons outside of React Functional Components.
export const RovingAccessibleButton = <T extends keyof HTMLElementTagNameMap>({ export const RovingAccessibleButton = <T extends keyof JSX.IntrinsicElements>({
inputRef, inputRef,
onFocus, onFocus,
onMouseOver, onMouseOver,
focusOnMouseOver, focusOnMouseOver,
element,
...props ...props
}: Props<T>): JSX.Element => { }: Props<T>): JSX.Element => {
const [onFocusInternal, isActive, ref] = useRovingTabIndex<HTMLElementTagNameMap[T]>(inputRef); const [onFocusInternal, isActive, ref] = useRovingTabIndex(inputRef);
return ( return (
<AccessibleButton <AccessibleButton
{...props} {...props}
onFocus={(event: React.FocusEvent<never, never>) => { element={element as keyof JSX.IntrinsicElements}
onFocus={(event: React.FocusEvent) => {
onFocusInternal(); onFocusInternal();
onFocus?.(event); onFocus?.(event);
}} }}
onMouseOver={(event: React.MouseEvent<never, never>) => { onMouseOver={(event: React.MouseEvent) => {
if (focusOnMouseOver) onFocusInternal(); if (focusOnMouseOver) onFocusInternal();
onMouseOver?.(event); onMouseOver?.(event);
}} }}

View file

@ -132,7 +132,6 @@ import { SessionLockStolenView } from "./auth/SessionLockStolenView";
import { ConfirmSessionLockTheftView } from "./auth/ConfirmSessionLockTheftView"; import { ConfirmSessionLockTheftView } from "./auth/ConfirmSessionLockTheftView";
import { LoginSplashView } from "./auth/LoginSplashView"; import { LoginSplashView } from "./auth/LoginSplashView";
import { cleanUpDraftsIfRequired } from "../../DraftCleaner"; import { cleanUpDraftsIfRequired } from "../../DraftCleaner";
import { InitialCryptoSetupStore } from "../../stores/InitialCryptoSetupStore";
// legacy export // legacy export
export { default as Views } from "../../Views"; export { default as Views } from "../../Views";
@ -429,12 +428,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
!(await shouldSkipSetupEncryption(cli)) !(await shouldSkipSetupEncryption(cli))
) { ) {
// if cross-signing is not yet set up, do so now if possible. // if cross-signing is not yet set up, do so now if possible.
InitialCryptoSetupStore.sharedInstance().startInitialCryptoSetup(
cli,
Boolean(this.tokenLogin),
this.stores,
this.onCompleteSecurityE2eSetupFinished,
);
this.setStateForNewView({ view: Views.E2E_SETUP }); this.setStateForNewView({ view: Views.E2E_SETUP });
} else { } else {
this.onLoggedIn(); this.onLoggedIn();
@ -2080,7 +2073,14 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
} else if (this.state.view === Views.COMPLETE_SECURITY) { } else if (this.state.view === Views.COMPLETE_SECURITY) {
view = <CompleteSecurity onFinished={this.onCompleteSecurityE2eSetupFinished} />; view = <CompleteSecurity onFinished={this.onCompleteSecurityE2eSetupFinished} />;
} else if (this.state.view === Views.E2E_SETUP) { } else if (this.state.view === Views.E2E_SETUP) {
view = <E2eSetup onFinished={this.onCompleteSecurityE2eSetupFinished} />; view = (
<E2eSetup
matrixClient={MatrixClientPeg.safeGet()}
onFinished={this.onCompleteSecurityE2eSetupFinished}
accountPassword={this.stores.accountPasswordStore.getPassword()}
tokenLogin={!!this.tokenLogin}
/>
);
} else if (this.state.view === Views.LOGGED_IN) { } else if (this.state.view === Views.LOGGED_IN) {
// `ready` and `view==LOGGED_IN` may be set before `page_type` (because the // `ready` and `view==LOGGED_IN` may be set before `page_type` (because the
// latter is set via the dispatcher). If we don't yet have a `page_type`, // latter is set via the dispatcher). If we don't yet have a `page_type`,

View file

@ -7,13 +7,17 @@ Please see LICENSE files in the repository root for full details.
*/ */
import React from "react"; import React from "react";
import { MatrixClient } from "matrix-js-sdk/src/matrix";
import AuthPage from "../../views/auth/AuthPage"; import AuthPage from "../../views/auth/AuthPage";
import CompleteSecurityBody from "../../views/auth/CompleteSecurityBody"; import CompleteSecurityBody from "../../views/auth/CompleteSecurityBody";
import { InitialCryptoSetupDialog } from "../../views/dialogs/security/InitialCryptoSetupDialog"; import { InitialCryptoSetupDialog } from "../../views/dialogs/security/InitialCryptoSetupDialog";
interface IProps { interface IProps {
matrixClient: MatrixClient;
onFinished: () => void; onFinished: () => void;
accountPassword?: string;
tokenLogin: boolean;
} }
export default class E2eSetup extends React.Component<IProps> { export default class E2eSetup extends React.Component<IProps> {
@ -21,7 +25,12 @@ export default class E2eSetup extends React.Component<IProps> {
return ( return (
<AuthPage> <AuthPage>
<CompleteSecurityBody> <CompleteSecurityBody>
<InitialCryptoSetupDialog onFinished={this.props.onFinished} /> <InitialCryptoSetupDialog
matrixClient={this.props.matrixClient}
onFinished={this.props.onFinished}
accountPassword={this.props.accountPassword}
tokenLogin={this.props.tokenLogin}
/>
</CompleteSecurityBody> </CompleteSecurityBody>
</AuthPage> </AuthPage>
); );

View file

@ -235,7 +235,12 @@ export default class SoftLogout extends React.Component<IProps, IState> {
value={this.state.password} value={this.state.password}
disabled={this.state.busy} disabled={this.state.busy}
/> />
<AccessibleButton onClick={this.onPasswordLogin} kind="primary" disabled={this.state.busy}> <AccessibleButton
onClick={this.onPasswordLogin}
kind="primary"
type="submit"
disabled={this.state.busy}
>
{_t("action|sign_in")} {_t("action|sign_in")}
</AccessibleButton> </AccessibleButton>
<AccessibleButton onClick={this.onForgotPassword} kind="link"> <AccessibleButton onClick={this.onForgotPassword} kind="link">

View file

@ -910,7 +910,7 @@ export class SSOAuthEntry extends React.Component<ISSOAuthEntryProps, ISSOAuthEn
export class FallbackAuthEntry<T = {}> extends React.Component<IAuthEntryProps & T> { export class FallbackAuthEntry<T = {}> extends React.Component<IAuthEntryProps & T> {
protected popupWindow: Window | null; protected popupWindow: Window | null;
protected fallbackButton = createRef<HTMLDivElement>(); protected fallbackButton = createRef<HTMLButtonElement>();
public constructor(props: IAuthEntryProps & T) { public constructor(props: IAuthEntryProps & T) {
super(props); super(props);

View file

@ -15,6 +15,7 @@ import VisibilityOnIcon from "@vector-im/compound-design-tokens/assets/web/icons
import NotificationsIcon from "@vector-im/compound-design-tokens/assets/web/icons/notifications"; import NotificationsIcon from "@vector-im/compound-design-tokens/assets/web/icons/notifications";
import PreferencesIcon from "@vector-im/compound-design-tokens/assets/web/icons/preferences"; import PreferencesIcon from "@vector-im/compound-design-tokens/assets/web/icons/preferences";
import KeyboardIcon from "@vector-im/compound-design-tokens/assets/web/icons/keyboard"; import KeyboardIcon from "@vector-im/compound-design-tokens/assets/web/icons/keyboard";
import KeyIcon from "@vector-im/compound-design-tokens/assets/web/icons/key";
import SidebarIcon from "@vector-im/compound-design-tokens/assets/web/icons/sidebar"; import SidebarIcon from "@vector-im/compound-design-tokens/assets/web/icons/sidebar";
import MicOnIcon from "@vector-im/compound-design-tokens/assets/web/icons/mic-on"; import MicOnIcon from "@vector-im/compound-design-tokens/assets/web/icons/mic-on";
import LockIcon from "@vector-im/compound-design-tokens/assets/web/icons/lock"; import LockIcon from "@vector-im/compound-design-tokens/assets/web/icons/lock";
@ -44,6 +45,7 @@ import { NonEmptyArray } from "../../../@types/common";
import { SDKContext, SdkContextClass } from "../../../contexts/SDKContext"; import { SDKContext, SdkContextClass } from "../../../contexts/SDKContext";
import { useSettingValue } from "../../../hooks/useSettings"; import { useSettingValue } from "../../../hooks/useSettings";
import { ToastContext, useActiveToast } from "../../../contexts/ToastContext"; import { ToastContext, useActiveToast } from "../../../contexts/ToastContext";
import { EncryptionUserSettingsTab } from "../settings/tabs/user/EncryptionUserSettingsTab";
interface IProps { interface IProps {
initialTabId?: UserTab; initialTabId?: UserTab;
@ -75,6 +77,8 @@ function titleForTabID(tabId: UserTab): React.ReactNode {
return _t("settings|voip|dialog_title", undefined, subs); return _t("settings|voip|dialog_title", undefined, subs);
case UserTab.Security: case UserTab.Security:
return _t("settings|security|dialog_title", undefined, subs); return _t("settings|security|dialog_title", undefined, subs);
case UserTab.Encryption:
return _t("settings|encryption|dialog_title", undefined, subs);
case UserTab.Labs: case UserTab.Labs:
return _t("settings|labs|dialog_title", undefined, subs); return _t("settings|labs|dialog_title", undefined, subs);
case UserTab.Mjolnir: case UserTab.Mjolnir:
@ -179,6 +183,10 @@ export default function UserSettingsDialog(props: IProps): JSX.Element {
), ),
); );
tabs.push(
new Tab(UserTab.Encryption, _td("settings|encryption|title"), <KeyIcon />, <EncryptionUserSettingsTab />),
);
if (showLabsFlags() || SettingsStore.getFeatureSettingNames().some((k) => SettingsStore.getBetaInfo(k))) { if (showLabsFlags() || SettingsStore.getFeatureSettingNames().some((k) => SettingsStore.getBetaInfo(k))) {
tabs.push( tabs.push(
new Tab(UserTab.Labs, _td("common|labs"), <LabsIcon />, <LabsUserSettingsTab />, "UserSettingsLabs"), new Tab(UserTab.Labs, _td("common|labs"), <LabsIcon />, <LabsUserSettingsTab />, "UserSettingsLabs"),

View file

@ -15,6 +15,7 @@ export enum UserTab {
Sidebar = "USER_SIDEBAR_TAB", Sidebar = "USER_SIDEBAR_TAB",
Voice = "USER_VOICE_TAB", Voice = "USER_VOICE_TAB",
Security = "USER_SECURITY_TAB", Security = "USER_SECURITY_TAB",
Encryption = "USER_ENCRYPTION_TAB",
Labs = "USER_LABS_TAB", Labs = "USER_LABS_TAB",
Mjolnir = "USER_MJOLNIR_TAB", Mjolnir = "USER_MJOLNIR_TAB",
Help = "USER_HELP_TAB", Help = "USER_HELP_TAB",

View file

@ -298,7 +298,7 @@ const SettingsList: React.FC<ISettingsListProps> = ({ onBack, onView, onEdit })
<code>{i}</code> <code>{i}</code>
</AccessibleButton> </AccessibleButton>
<AccessibleButton <AccessibleButton
title={_t("devtools|edit_setting")} alt={_t("devtools|edit_setting")}
onClick={() => onEdit(i)} onClick={() => onEdit(i)}
className="mx_DevTools_SettingsExplorer_edit" className="mx_DevTools_SettingsExplorer_edit"
> >

View file

@ -7,15 +7,20 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import React, { useCallback } from "react"; import React, { useCallback, useEffect, useState } from "react";
import { logger } from "matrix-js-sdk/src/logger";
import { MatrixClient } from "matrix-js-sdk/src/matrix";
import { _t } from "../../../../languageHandler"; import { _t } from "../../../../languageHandler";
import DialogButtons from "../../elements/DialogButtons"; import DialogButtons from "../../elements/DialogButtons";
import BaseDialog from "../BaseDialog"; import BaseDialog from "../BaseDialog";
import Spinner from "../../elements/Spinner"; import Spinner from "../../elements/Spinner";
import { InitialCryptoSetupStore, useInitialCryptoSetupStatus } from "../../../../stores/InitialCryptoSetupStore"; import { createCrossSigning } from "../../../../CreateCrossSigning";
interface Props { interface Props {
matrixClient: MatrixClient;
accountPassword?: string;
tokenLogin: boolean;
onFinished: (success?: boolean) => void; onFinished: (success?: boolean) => void;
} }
@ -24,27 +29,54 @@ interface Props {
* In most cases, only a spinner is shown, but for more * In most cases, only a spinner is shown, but for more
* complex auth like SSO, the user may need to complete some steps to proceed. * complex auth like SSO, the user may need to complete some steps to proceed.
*/ */
export const InitialCryptoSetupDialog: React.FC<Props> = ({ onFinished }) => { export const InitialCryptoSetupDialog: React.FC<Props> = ({
const onRetryClick = useCallback(() => { matrixClient,
InitialCryptoSetupStore.sharedInstance().retry(); accountPassword,
}, []); tokenLogin,
onFinished,
}) => {
const [error, setError] = useState(false);
const onCancelClick = useCallback(() => { const doSetup = useCallback(async () => {
const cryptoApi = matrixClient.getCrypto();
if (!cryptoApi) return;
setError(false);
try {
await createCrossSigning(matrixClient, tokenLogin, accountPassword);
onFinished(true);
} catch (e) {
if (tokenLogin) {
// ignore any failures, we are relying on grace period here
onFinished(false);
return;
}
setError(true);
logger.error("Error bootstrapping cross-signing", e);
}
}, [matrixClient, tokenLogin, accountPassword, onFinished]);
const onCancel = useCallback(() => {
onFinished(false); onFinished(false);
}, [onFinished]); }, [onFinished]);
const status = useInitialCryptoSetupStatus(InitialCryptoSetupStore.sharedInstance()); useEffect(() => {
doSetup();
}, [doSetup]);
let content; let content;
if (status === "error") { if (error) {
content = ( content = (
<div> <div>
<p>{_t("encryption|unable_to_setup_keys_error")}</p> <p>{_t("encryption|unable_to_setup_keys_error")}</p>
<div className="mx_Dialog_buttons"> <div className="mx_Dialog_buttons">
<DialogButtons <DialogButtons
primaryButton={_t("action|retry")} primaryButton={_t("action|retry")}
onPrimaryButtonClick={onRetryClick} onPrimaryButtonClick={doSetup}
onCancel={onCancelClick} onCancel={onCancel}
/> />
</div> </div>
</div> </div>

View file

@ -1253,7 +1253,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
<span>{filterToLabel(filter)}</span> <span>{filterToLabel(filter)}</span>
<AccessibleButton <AccessibleButton
tabIndex={-1} tabIndex={-1}
title={_t("spotlight_dialog|remove_filter", { alt={_t("spotlight_dialog|remove_filter", {
filter: filterToLabel(filter), filter: filterToLabel(filter),
})} })}
className="mx_SpotlightDialog_filter--close" className="mx_SpotlightDialog_filter--close"

View file

@ -13,15 +13,15 @@ import { useRovingTabIndex } from "../../../../accessibility/RovingTabIndex";
import AccessibleButton, { ButtonProps } from "../../elements/AccessibleButton"; import AccessibleButton, { ButtonProps } from "../../elements/AccessibleButton";
import { Ref } from "../../../../accessibility/roving/types"; import { Ref } from "../../../../accessibility/roving/types";
type TooltipOptionProps<T extends keyof HTMLElementTagNameMap> = ButtonProps<T> & { type TooltipOptionProps<T extends keyof JSX.IntrinsicElements> = ButtonProps<T> & {
className?: string;
endAdornment?: ReactNode; endAdornment?: ReactNode;
inputRef?: Ref; inputRef?: Ref;
}; };
export const TooltipOption = <T extends keyof HTMLElementTagNameMap>({ export const TooltipOption = <T extends keyof JSX.IntrinsicElements>({
inputRef, inputRef,
className, className,
element,
...props ...props
}: TooltipOptionProps<T>): JSX.Element => { }: TooltipOptionProps<T>): JSX.Element => {
const [onFocus, isActive, ref] = useRovingTabIndex(inputRef); const [onFocus, isActive, ref] = useRovingTabIndex(inputRef);
@ -34,6 +34,7 @@ export const TooltipOption = <T extends keyof HTMLElementTagNameMap>({
tabIndex={-1} tabIndex={-1}
aria-selected={isActive} aria-selected={isActive}
role="option" role="option"
element={element as keyof JSX.IntrinsicElements}
/> />
); );
}; };

View file

@ -168,7 +168,7 @@ export const NetworkDropdown: React.FC<IProps> = ({ protocols, config, setConfig
adornment: ( adornment: (
<AccessibleButton <AccessibleButton
className="mx_NetworkDropdown_removeServer" className="mx_NetworkDropdown_removeServer"
title={_t("spotlight|public_rooms|network_dropdown_remove_server_adornment", { roomServer })} alt={_t("spotlight|public_rooms|network_dropdown_remove_server_adornment", { roomServer })}
onClick={() => setUserDefinedServers(without(userDefinedServers, roomServer))} onClick={() => setUserDefinedServers(without(userDefinedServers, roomServer))}
/> />
), ),

View file

@ -6,15 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import React, { import React, { ComponentProps, forwardRef, FunctionComponent, HTMLAttributes, InputHTMLAttributes, Ref } from "react";
ComponentProps,
ComponentPropsWithoutRef,
forwardRef,
FunctionComponent,
ReactElement,
KeyboardEvent,
Ref,
} from "react";
import classnames from "classnames"; import classnames from "classnames";
import { Tooltip } from "@vector-im/compound-web"; import { Tooltip } from "@vector-im/compound-web";
@ -46,8 +38,20 @@ export type AccessibleButtonKind =
| "icon_primary" | "icon_primary"
| "icon_primary_outline"; | "icon_primary_outline";
type ElementType = keyof HTMLElementTagNameMap; /**
const defaultElement = "div"; * This type construct allows us to specifically pass those props down to the element were creating that the element
* actually supports.
*
* e.g., if element is set to "a", well support href and target, if its set to "input", we support type.
*
* To remain compatible with existing code, well continue to support InputHTMLAttributes<Element>
*/
type DynamicHtmlElementProps<T extends keyof JSX.IntrinsicElements> =
JSX.IntrinsicElements[T] extends HTMLAttributes<{}> ? DynamicElementProps<T> : DynamicElementProps<"div">;
type DynamicElementProps<T extends keyof JSX.IntrinsicElements> = Partial<
Omit<JSX.IntrinsicElements[T], "ref" | "onClick" | "onMouseDown" | "onKeyUp" | "onKeyDown">
> &
Omit<InputHTMLAttributes<Element>, "onClick">;
type TooltipProps = ComponentProps<typeof Tooltip>; type TooltipProps = ComponentProps<typeof Tooltip>;
@ -56,7 +60,7 @@ type TooltipProps = ComponentProps<typeof Tooltip>;
* *
* Extends props accepted by the underlying element specified using the `element` prop. * Extends props accepted by the underlying element specified using the `element` prop.
*/ */
type Props<T extends ElementType = "div"> = { type Props<T extends keyof JSX.IntrinsicElements> = DynamicHtmlElementProps<T> & {
/** /**
* The base element type. "div" by default. * The base element type. "div" by default.
*/ */
@ -101,12 +105,14 @@ type Props<T extends ElementType = "div"> = {
disableTooltip?: TooltipProps["disabled"]; disableTooltip?: TooltipProps["disabled"];
}; };
export type ButtonProps<T extends ElementType> = Props<T> & Omit<ComponentPropsWithoutRef<T>, keyof Props<T>>; export type ButtonProps<T extends keyof JSX.IntrinsicElements> = Props<T>;
/** /**
* Type of the props passed to the element that is rendered by AccessibleButton. * Type of the props passed to the element that is rendered by AccessibleButton.
*/ */
type RenderedElementProps<T extends ElementType> = React.InputHTMLAttributes<Element> & RefProp<T>; interface RenderedElementProps extends React.InputHTMLAttributes<Element> {
ref?: React.Ref<Element>;
}
/** /**
* AccessibleButton is a generic wrapper for any element that should be treated * AccessibleButton is a generic wrapper for any element that should be treated
@ -118,9 +124,9 @@ type RenderedElementProps<T extends ElementType> = React.InputHTMLAttributes<Ele
* @param {Object} props react element properties * @param {Object} props react element properties
* @returns {Object} rendered react * @returns {Object} rendered react
*/ */
const AccessibleButton = forwardRef(function <T extends ElementType = typeof defaultElement>( const AccessibleButton = forwardRef(function <T extends keyof JSX.IntrinsicElements>(
{ {
element, element = "div" as T,
onClick, onClick,
children, children,
kind, kind,
@ -135,10 +141,10 @@ const AccessibleButton = forwardRef(function <T extends ElementType = typeof def
onTooltipOpenChange, onTooltipOpenChange,
disableTooltip, disableTooltip,
...restProps ...restProps
}: ButtonProps<T>, }: Props<T>,
ref: Ref<HTMLElementTagNameMap[T]>, ref: Ref<HTMLElement>,
): JSX.Element { ): JSX.Element {
const newProps = restProps as RenderedElementProps<T>; const newProps: RenderedElementProps = restProps;
newProps["aria-label"] = newProps["aria-label"] ?? title; newProps["aria-label"] = newProps["aria-label"] ?? title;
if (disabled) { if (disabled) {
newProps["aria-disabled"] = true; newProps["aria-disabled"] = true;
@ -156,7 +162,7 @@ const AccessibleButton = forwardRef(function <T extends ElementType = typeof def
// And divs which we report as role button to assistive technologies. // And divs which we report as role button to assistive technologies.
// Browsers handle space and enter key presses differently and we are only adjusting to the // Browsers handle space and enter key presses differently and we are only adjusting to the
// inconsistencies here // inconsistencies here
newProps.onKeyDown = (e: KeyboardEvent<never>) => { newProps.onKeyDown = (e) => {
const action = getKeyBindingsManager().getAccessibilityAction(e); const action = getKeyBindingsManager().getAccessibilityAction(e);
switch (action) { switch (action) {
@ -172,7 +178,7 @@ const AccessibleButton = forwardRef(function <T extends ElementType = typeof def
onKeyDown?.(e); onKeyDown?.(e);
} }
}; };
newProps.onKeyUp = (e: KeyboardEvent<never>) => { newProps.onKeyUp = (e) => {
const action = getKeyBindingsManager().getAccessibilityAction(e); const action = getKeyBindingsManager().getAccessibilityAction(e);
switch (action) { switch (action) {
@ -201,7 +207,7 @@ const AccessibleButton = forwardRef(function <T extends ElementType = typeof def
}); });
// React.createElement expects InputHTMLAttributes // React.createElement expects InputHTMLAttributes
const button = React.createElement(element ?? defaultElement, newProps, children); const button = React.createElement(element, newProps, children);
if (title) { if (title) {
return ( return (
@ -227,15 +233,4 @@ const AccessibleButton = forwardRef(function <T extends ElementType = typeof def
}; };
(AccessibleButton as FunctionComponent).displayName = "AccessibleButton"; (AccessibleButton as FunctionComponent).displayName = "AccessibleButton";
interface RefProp<T extends ElementType> { export default AccessibleButton;
ref?: Ref<HTMLElementTagNameMap[T]>;
}
interface ButtonComponent {
// With the explicit `element` prop
<C extends ElementType>(props: { element?: C } & ButtonProps<C> & RefProp<C>): ReactElement;
// Without the explicit `element` prop
(props: ButtonProps<"div"> & RefProp<"div">): ReactElement;
}
export default AccessibleButton as ButtonComponent;

View file

@ -133,7 +133,12 @@ export default class EditableItemList<P = {}> extends React.PureComponent<IProps
onChange={this.onNewItemChanged} onChange={this.onNewItemChanged}
list={this.props.suggestionsListId} list={this.props.suggestionsListId}
/> />
<AccessibleButton onClick={this.onItemAdded} kind="primary" disabled={!this.props.newItem}> <AccessibleButton
onClick={this.onItemAdded}
kind="primary"
type="submit"
disabled={!this.props.newItem}
>
{_t("action|add")} {_t("action|add")}
</AccessibleButton> </AccessibleButton>
</form> </form>

View file

@ -31,7 +31,7 @@ class Emoji extends React.PureComponent<IProps> {
return ( return (
<RovingAccessibleButton <RovingAccessibleButton
id={this.props.id} id={this.props.id}
onClick={(ev: ButtonEvent) => onClick(ev, emoji)} onClick={(ev) => onClick(ev, emoji)}
onMouseEnter={() => onMouseEnter(emoji)} onMouseEnter={() => onMouseEnter(emoji)}
onMouseLeave={() => onMouseLeave(emoji)} onMouseLeave={() => onMouseLeave(emoji)}
className="mx_EmojiPicker_item_wrapper" className="mx_EmojiPicker_item_wrapper"

View file

@ -90,7 +90,7 @@ export const MPollEndBody = React.forwardRef<any, IBodyProps>(({ mxEvent, ...pro
const { pollStartEvent, isLoadingPollStartEvent } = usePollStartEvent(mxEvent); const { pollStartEvent, isLoadingPollStartEvent } = usePollStartEvent(mxEvent);
if (!pollStartEvent) { if (!pollStartEvent) {
const pollEndFallbackMessage = M_TEXT.findIn<string>(mxEvent.getContent()) || textForEvent(mxEvent, cli); const pollEndFallbackMessage = M_TEXT.findIn(mxEvent.getContent()) || textForEvent(mxEvent, cli);
return ( return (
<> <>
<PollIcon className="mx_MPollEndBody_icon" /> <PollIcon className="mx_MPollEndBody_icon" />

View file

@ -435,7 +435,7 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
<RovingAccessibleButton <RovingAccessibleButton
className="mx_MessageActionBar_iconButton" className="mx_MessageActionBar_iconButton"
title={isPinned ? _t("action|unpin") : _t("action|pin")} title={isPinned ? _t("action|unpin") : _t("action|pin")}
onClick={(e: ButtonEvent) => this.onPinClick(e, isPinned)} onClick={(e) => this.onPinClick(e, isPinned)}
onContextMenu={(e: ButtonEvent) => this.onPinClick(e, isPinned)} onContextMenu={(e: ButtonEvent) => this.onPinClick(e, isPinned)}
key="pin" key="pin"
placement="left" placement="left"

View file

@ -407,6 +407,7 @@ export default class SetIdServer extends React.Component<IProps, IState> {
forceValidity={this.state.error ? false : undefined} forceValidity={this.state.error ? false : undefined}
/> />
<AccessibleButton <AccessibleButton
type="submit"
kind="primary_sm" kind="primary_sm"
onClick={this.checkIdServer} onClick={this.checkIdServer}
disabled={!this.idServerChangeEnabled()} disabled={!this.idServerChangeEnabled()}

View file

@ -0,0 +1,33 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
* Please see LICENSE files in the repository root for full details.
*/
import React, { JSX } from "react";
import { Heading } from "@vector-im/compound-web";
import { _t } from "../../../languageHandler";
/**
* The heading for a settings section.
*/
interface SettingsHeaderProps {
/**
* Whether the user has a recommended tag.
*/
hasRecommendedTag?: boolean;
/**
* The label for the header.
*/
label: string;
}
export function SettingsHeader({ hasRecommendedTag = false, label }: SettingsHeaderProps): JSX.Element {
return (
<Heading className="mx_SettingsHeader" as="h2" size="sm" weight="semibold">
{label} {hasRecommendedTag && <span>{_t("common|recommended")}</span>}
</Heading>
);
}

View file

@ -7,21 +7,23 @@ Please see LICENSE files in the repository root for full details.
*/ */
import classNames from "classnames"; import classNames from "classnames";
import React from "react"; import React, { ComponentProps } from "react";
import { ChevronDownIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; import { ChevronDownIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { _t } from "../../../../languageHandler"; import { _t } from "../../../../languageHandler";
import AccessibleButton, { ButtonProps } from "../../elements/AccessibleButton"; import AccessibleButton from "../../elements/AccessibleButton";
type Props<T extends keyof HTMLElementTagNameMap> = Omit< type Props<T extends keyof JSX.IntrinsicElements> = Omit<
ButtonProps<T>, ComponentProps<typeof AccessibleButton<T>>,
"aria-label" | "title" | "kind" | "className" | "element" "aria-label" | "title" | "kind" | "className" | "onClick" | "element"
> & { > & {
isExpanded: boolean; isExpanded: boolean;
onClick: () => void;
}; };
export const DeviceExpandDetailsButton = <T extends keyof HTMLElementTagNameMap>({ export const DeviceExpandDetailsButton = <T extends keyof JSX.IntrinsicElements>({
isExpanded, isExpanded,
onClick,
...rest ...rest
}: Props<T>): JSX.Element => { }: Props<T>): JSX.Element => {
const label = isExpanded ? _t("settings|sessions|hide_details") : _t("settings|sessions|show_details"); const label = isExpanded ? _t("settings|sessions|hide_details") : _t("settings|sessions|show_details");
@ -34,6 +36,7 @@ export const DeviceExpandDetailsButton = <T extends keyof HTMLElementTagNameMap>
className={classNames("mx_DeviceExpandDetailsButton", { className={classNames("mx_DeviceExpandDetailsButton", {
mx_DeviceExpandDetailsButton_expanded: isExpanded, mx_DeviceExpandDetailsButton_expanded: isExpanded,
})} })}
onClick={onClick}
> >
<ChevronDownIcon className="mx_DeviceExpandDetailsButton_icon" /> <ChevronDownIcon className="mx_DeviceExpandDetailsButton_icon" />
</AccessibleButton> </AccessibleButton>

View file

@ -0,0 +1,312 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
* Please see LICENSE files in the repository root for full details.
*/
import React, { FormEventHandler, JSX, MouseEventHandler, useState } from "react";
import {
Breadcrumb,
IconButton,
Button,
Root,
TextControl,
Field,
Label,
ErrorMessage,
Text,
} from "@vector-im/compound-web";
import CopyIcon from "@vector-im/compound-design-tokens/assets/web/icons/copy";
import { logger } from "matrix-js-sdk/src/logger";
import { _t } from "../../../../languageHandler.tsx";
import { EncryptionCard } from "./EncryptionCard.tsx";
import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext.tsx";
import { useAsyncMemo } from "../../../../hooks/useAsyncMemo.ts";
import { copyPlaintext } from "../../../../utils/strings.ts";
import { withSecretStorageKeyCache } from "../../../../SecurityManager.ts";
/**
* The possible states of the component.
* - `warn_user`: The user is warned about the consequences of changing the recovery key.
* - `save_key_setup_flow`: The user is asked to save the new recovery key during the setup flow.
* - `save_key_change_flow`: The user is asked to save the new recovery key during the chang key flow.
* - `confirm`: The user is asked to confirm the new recovery key.
*/
type State = "warn_user" | "save_key_setup_flow" | "save_key_change_flow" | "confirm";
interface ChangeRecoveryKeyProps {
/**
* If true, the component will display the flow to set up a new recovery key.
* If false, the component will display the flow to change the recovery key.
*/
isSetupFlow: boolean;
/**
* Called when the recovery key is successfully changed.
*/
onFinish: () => void;
/**
* Called when the cancel button is clicked or when we go back in the breadcrumbs.
*/
onCancelClick: () => void;
}
export function ChangeRecoveryKey({
isSetupFlow,
onFinish,
onCancelClick,
}: ChangeRecoveryKeyProps): JSX.Element | null {
console.log("ChangeRecoveryKey.tsx: ChangeRecoveryKey");
const matrixClient = useMatrixClientContext();
const [state, setState] = useState<State>(isSetupFlow ? "warn_user" : "save_key_change_flow");
const labels = getLabels(state);
const recoveryKey = useAsyncMemo(() => {
const crypto = matrixClient.getCrypto();
if (!crypto) return Promise.resolve(undefined);
return crypto.createRecoveryKeyFromPassphrase();
}, []);
console.log(recoveryKey);
if (!recoveryKey?.encodedPrivateKey) return null;
console.log("ChangeRecoveryKey.tsx: ChangeRecoveryKey");
let content: JSX.Element;
switch (state) {
case "warn_user":
content = (
<WarningPanel onContinueClick={() => setState("save_key_setup_flow")} onCancelClick={onCancelClick} />
);
break;
case "save_key_setup_flow":
content = (
<KeyPanel
recoveryKey={recoveryKey?.encodedPrivateKey}
onConfirmClick={() => setState("confirm")}
onCancelClick={onCancelClick}
/>
);
break;
case "save_key_change_flow":
content = (
<KeyPanel
recoveryKey={recoveryKey?.encodedPrivateKey}
onConfirmClick={() => setState("confirm")}
onCancelClick={onCancelClick}
/>
);
break;
case "confirm":
content = (
<KeyForm
recoveryKey={recoveryKey.encodedPrivateKey}
onCancelClick={onCancelClick}
onSubmit={async () => {
const crypto = matrixClient.getCrypto();
if (!crypto) return onFinish();
try {
// We need to enable the cache to avoid to prompt the user to enter the new key
// when we will try to access the secret storage during the bootstrap
await withSecretStorageKeyCache(() =>
crypto.bootstrapSecretStorage({
setupNewKeyBackup: isSetupFlow,
setupNewSecretStorage: true,
createSecretStorageKey: async () => recoveryKey,
}),
);
onFinish();
} catch (e) {
logger.error("Failed to bootstrap secret storage", e);
}
}}
/>
);
}
const pages = [
_t("settings|encryption|title"),
isSetupFlow
? _t("settings|encryption|recovery|set_up_recovery")
: _t("settings|encryption|recovery|change_recovery_key"),
];
return (
<>
<Breadcrumb
backLabel={_t("action|back")}
onBackClick={onCancelClick}
pages={pages}
onPageClick={onCancelClick}
/>
<EncryptionCard title={labels.title} description={labels.description} className="mx_ChangeRecoveryKey">
{content}
</EncryptionCard>
</>
);
}
type Labels = {
title: string;
description: string;
};
function getLabels(state: State): Labels {
switch (state) {
case "warn_user":
return {
title: _t("settings|encryption|recovery|set_up_recovery"),
description: _t("settings|encryption|recovery|set_up_recovery_description", {
changeRecoveryKeyButton: _t("settings|encryption|recovery|change_recovery_key"),
}),
};
case "save_key_setup_flow":
return {
title: _t("settings|encryption|recovery|set_up_recovery_save_key_title"),
description: _t("settings|encryption|recovery|set_up_recovery_save_key_description"),
};
case "save_key_change_flow":
return {
title: _t("settings|encryption|recovery|change_recovery_key_title"),
description: _t("settings|encryption|recovery|change_recovery_key_description"),
};
case "confirm":
return {
title: _t("settings|encryption|recovery|confirm_title"),
description: _t("settings|encryption|recovery|confirm_description"),
};
}
}
interface WarningPanelProps {
/**
* Called when the continue button is clicked.
*/
onContinueClick: MouseEventHandler<HTMLButtonElement>;
/**
* Called when the cancel button is clicked.
*/
onCancelClick: MouseEventHandler<HTMLButtonElement>;
}
function WarningPanel({ onContinueClick, onCancelClick }: WarningPanelProps): JSX.Element {
return (
<>
<Text as="span" weight="medium" className="mx_WarningPanel_description">
{_t("settings|encryption|recovery|set_up_recovery_secondary_description")}
</Text>
<div className="mx_ChangeRecoveryKey_footer">
<Button onClick={onContinueClick}>{_t("action|continue")}</Button>
<Button kind="tertiary" onClick={onCancelClick}>
{_t("action|cancel")}
</Button>
</div>
</>
);
}
interface KeyPanelProps {
/**
* Called when the confirm button is clicked.
*/
onConfirmClick: MouseEventHandler;
/**
* Called when the cancel button is clicked.
*/
onCancelClick: MouseEventHandler;
/**
* The recovery key to display.
*/
recoveryKey: string;
}
/**
* The panel to display the recovery key.
*/
function KeyPanel({ recoveryKey, onConfirmClick, onCancelClick }: KeyPanelProps): JSX.Element {
return (
<>
<div className="mx_KeyPanel">
<Text as="span" weight="medium">
{_t("settings|encryption|recovery|save_key_title")}
</Text>
<div>
<Text as="span" className="mx_KeyPanel_key">
{recoveryKey}
</Text>
<Text as="span" size="sm">
{_t("settings|encryption|recovery|save_key_description")}
</Text>
</div>
<IconButton size="28px" onClick={() => copyPlaintext(recoveryKey)}>
<CopyIcon />
</IconButton>
</div>
<div className="mx_ChangeRecoveryKey_footer">
<Button onClick={onConfirmClick}>{_t("action|continue")}</Button>
<Button kind="tertiary" onClick={onCancelClick}>
{_t("action|cancel")}
</Button>
</div>
</>
);
}
interface KeyFormProps {
/**
* Called when the cancel button is clicked.
*/
onCancelClick: MouseEventHandler;
/**
* Called when the form is submitted.
*/
onSubmit: FormEventHandler;
/**
* The recovery key to confirm.
*/
recoveryKey: string;
}
function KeyForm({ onCancelClick, onSubmit, recoveryKey }: KeyFormProps): JSX.Element {
// Undefined by default, as the key is not filled yet
const [isKeyValid, setIsKeyValid] = useState<boolean>();
const isKeyInvalidAndFilled = isKeyValid === false;
return (
<Root
className="mx_KeyForm"
onSubmit={(evt) => {
evt.preventDefault();
onSubmit(evt);
}}
onChange={async (evt) => {
evt.preventDefault();
evt.stopPropagation();
// We don't have any file in the form, we can cast it as string safely
const filledKey = new FormData(evt.currentTarget).get("recoveryKey") as string | "";
setIsKeyValid(filledKey.trim() === recoveryKey);
}}
>
<Field name="recoveryKey" serverInvalid={isKeyInvalidAndFilled}>
<Label>{_t("settings|encryption|recovery|enter_key_title")}</Label>
<TextControl required={true} />
{isKeyInvalidAndFilled && (
<ErrorMessage>{_t("settings|encryption|recovery|enter_key_error")}</ErrorMessage>
)}
</Field>
<div className="mx_ChangeRecoveryKey_footer">
<Button disabled={!isKeyValid}>{_t("settings|encryption|recovery|confirm_finish")}</Button>
<Button kind="tertiary" onClick={onCancelClick}>
{_t("action|cancel")}
</Button>
</div>
</Root>
);
}

View file

@ -0,0 +1,51 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
* Please see LICENSE files in the repository root for full details.
*/
import React, { JSX, PropsWithChildren } from "react";
import { BigIcon, Heading } from "@vector-im/compound-web";
import KeyIcon from "@vector-im/compound-design-tokens/assets/web/icons/key-solid";
import classNames from "classnames";
interface EncryptionCardProps {
/**
* CSS class name to apply to the card.
*/
className?: string;
/**
* The title of the card.
*/
title: string;
/**
* The description of the card.
*/
description: string;
}
/**
* A styled card for encryption settings.
*/
export function EncryptionCard({
title,
description,
className,
children,
}: PropsWithChildren<EncryptionCardProps>): JSX.Element {
return (
<div className={classNames("mx_EncryptionCard", className)}>
<div className="mx_EncryptionCard_header">
<BigIcon>
<KeyIcon />
</BigIcon>
<Heading as="h2" size="sm" weight="semibold">
{title}
</Heading>
<span>{description}</span>
</div>
{children}
</div>
);
}

View file

@ -0,0 +1,124 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
* Please see LICENSE files in the repository root for full details.
*/
import React, { JSX, MouseEventHandler, useEffect, useState } from "react";
import { Button, InlineSpinner } from "@vector-im/compound-web";
import KeyIcon from "@vector-im/compound-design-tokens/assets/web/icons/key";
import CheckCircleIcon from "@vector-im/compound-design-tokens/assets/web/icons/check-circle-solid";
import { SettingsSection } from "../shared/SettingsSection";
import { _t } from "../../../../languageHandler";
import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext";
import { SettingsHeader } from "../SettingsHeader";
type State = "loading" | "missing_backup" | "secrets_not_cached" | "good";
interface RecoveryPanelProps {
/**
* Callback for when the user clicks the button to set up their recovery key.
*/
onSetUpRecoveryClick: MouseEventHandler<HTMLButtonElement>;
/**
* Callback for when the user clicks the button to change their recovery key.
*/
onChangingRecoveryKeyClick: MouseEventHandler<HTMLButtonElement>;
}
/**
* This component allows the user to set up or change their recovery key.
*/
export function RecoveryPanel({ onSetUpRecoveryClick, onChangingRecoveryKeyClick }: RecoveryPanelProps): JSX.Element {
const [state, setState] = useState<State>("loading");
const isGood = state === "good";
const isMissingBackup = state === "missing_backup";
const areSecretsNotCached = state === "secrets_not_cached";
const hasError = isMissingBackup || areSecretsNotCached;
const matrixClient = useMatrixClientContext();
useEffect(() => {
const check = async (): Promise<void> => {
const crypto = matrixClient.getCrypto();
if (!crypto) return;
console.log("Recovery Panel: Checking recovery key status");
const hasBackup = (await crypto.getKeyBackupInfo()) && (await crypto.getSessionBackupPrivateKey());
if (!hasBackup) return setState("missing_backup");
const cachedSecrets = (await crypto.getCrossSigningStatus()).privateKeysCachedLocally;
const secretsOk = cachedSecrets.masterKey && cachedSecrets.selfSigningKey && cachedSecrets.userSigningKey;
if (!secretsOk) return setState("secrets_not_cached");
setState("good");
};
check();
}, [matrixClient]);
let content: JSX.Element;
switch (state) {
case "loading":
content = <InlineSpinner />;
break;
case "missing_backup":
content = (
<Button size="sm" kind="primary" Icon={KeyIcon} onClick={onSetUpRecoveryClick}>
{_t("settings|encryption|recovery|set_up_recovery")}
</Button>
);
break;
case "secrets_not_cached":
content = (
<Button size="sm" kind="primary" Icon={KeyIcon}>
{_t("settings|encryption|recovery|confirm_recovery_key")}
</Button>
);
break;
default:
content = (
<Button size="sm" kind="secondary" Icon={KeyIcon} onClick={onChangingRecoveryKeyClick}>
{_t("settings|encryption|recovery|change_recovery_key")}
</Button>
);
}
return (
<SettingsSection
legacy={false}
heading={<SettingsHeader hasRecommendedTag={hasError} label={_t("settings|encryption|recovery|title")} />}
subHeading={<Subheader hasRecoveryKey={isGood} />}
className="mx_RecoveryPanel"
>
{content}
</SettingsSection>
);
}
/**
* The subheader for the recovery panel.
*/
interface SubheaderProps {
/**
* Whether the user has a recovery key.
* If null, the recovery key is still fetching.
*/
hasRecoveryKey: boolean | null;
}
function Subheader({ hasRecoveryKey }: SubheaderProps): JSX.Element {
if (!hasRecoveryKey) return <>{_t("settings|encryption|recovery|description")}</>;
return (
<div className="mx_RecoveryPanel_Subheader">
{_t("settings|encryption|recovery|description")}
<span>
<CheckCircleIcon width="20" height="20" />
{_t("settings|encryption|recovery|key_active")}
</span>
</div>
);
}

View file

@ -10,19 +10,24 @@ import classnames from "classnames";
import React, { HTMLAttributes } from "react"; import React, { HTMLAttributes } from "react";
import Heading from "../../typography/Heading"; import Heading from "../../typography/Heading";
import { SettingsHeader } from "../SettingsHeader";
export interface SettingsSectionProps extends HTMLAttributes<HTMLDivElement> { export interface SettingsSectionProps extends HTMLAttributes<HTMLDivElement> {
heading?: string | React.ReactNode; heading?: string | React.ReactNode;
subHeading?: string | React.ReactNode;
children?: React.ReactNode; children?: React.ReactNode;
legacy?: boolean;
} }
function renderHeading(heading: string | React.ReactNode | undefined): React.ReactNode | undefined { function renderHeading(heading: string | React.ReactNode | undefined, legacy: boolean): React.ReactNode | undefined {
switch (typeof heading) { switch (typeof heading) {
case "string": case "string":
return ( return legacy ? (
<Heading as="h2" size="3"> <Heading as="h2" size="3">
{heading} {heading}
</Heading> </Heading>
) : (
<SettingsHeader label={heading} />
); );
case "undefined": case "undefined":
return undefined; return undefined;
@ -31,6 +36,15 @@ function renderHeading(heading: string | React.ReactNode | undefined): React.Rea
} }
} }
function renderSubHeading(subHeading: string | React.ReactNode | undefined): React.ReactNode | undefined {
switch (typeof subHeading) {
case "undefined":
return undefined;
default:
return subHeading;
}
}
/** /**
* A section of settings content * A section of settings content
* A SettingsTab may contain one or more SettingsSections * A SettingsTab may contain one or more SettingsSections
@ -48,9 +62,29 @@ function renderHeading(heading: string | React.ReactNode | undefined): React.Rea
* </SettingsTab> * </SettingsTab>
* ``` * ```
*/ */
export const SettingsSection: React.FC<SettingsSectionProps> = ({ className, heading, children, ...rest }) => ( export const SettingsSection: React.FC<SettingsSectionProps> = ({
<div {...rest} className={classnames("mx_SettingsSection", className)}> className,
{renderHeading(heading)} heading,
<div className="mx_SettingsSection_subSections">{children}</div> subHeading,
legacy = true,
children,
...rest
}) => (
<div
{...rest}
className={classnames("mx_SettingsSection", className, {
mx_SettingsSection_newUi: !legacy,
})}
>
{heading &&
(subHeading ? (
<div className="mx_SettingsSection_header">
{renderHeading(heading, legacy)}
{renderSubHeading(subHeading)}
</div>
) : (
renderHeading(heading, legacy)
))}
{legacy ? <div className="mx_SettingsSection_subSections">{children}</div> : children}
</div> </div>
); );

View file

@ -6,8 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details. Please see LICENSE files in the repository root for full details.
*/ */
import React, { HTMLAttributes } from "react"; import React, { HTMLAttributes } from "react";
import classNames from "classnames";
export interface SettingsTabProps extends Omit<HTMLAttributes<HTMLDivElement>, "className"> { export interface SettingsTabProps extends HTMLAttributes<HTMLDivElement> {
children?: React.ReactNode; children?: React.ReactNode;
} }
@ -29,8 +30,8 @@ export interface SettingsTabProps extends Omit<HTMLAttributes<HTMLDivElement>, "
* </SettingsTab> * </SettingsTab>
* ``` * ```
*/ */
const SettingsTab: React.FC<SettingsTabProps> = ({ children, ...rest }) => ( const SettingsTab: React.FC<SettingsTabProps> = ({ children, className, ...rest }) => (
<div {...rest} className="mx_SettingsTab"> <div {...rest} className={classNames("mx_SettingsTab", className)}>
<div className="mx_SettingsTab_sections">{children}</div> <div className="mx_SettingsTab_sections">{children}</div>
</div> </div>
); );

View file

@ -0,0 +1,50 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
* Please see LICENSE files in the repository root for full details.
*/
import React, { JSX, useState } from "react";
import SettingsTab from "../SettingsTab";
import { RecoveryPanel } from "../../encryption/RecoveryPanel";
import { ChangeRecoveryKey } from "../../encryption/ChangeRecoveryKey.tsx";
type Panel = "main" | "change_recovery_key" | "set_recovery_key";
export function EncryptionUserSettingsTab(): JSX.Element {
const [panel, setPanel] = useState<Panel>("main");
let content: JSX.Element;
switch (panel) {
case "main":
content = (
<RecoveryPanel
onChangingRecoveryKeyClick={() => setPanel("change_recovery_key")}
onSetUpRecoveryClick={() => setPanel("set_recovery_key")}
/>
);
break;
case "set_recovery_key":
content = (
<ChangeRecoveryKey
isSetupFlow={true}
onCancelClick={() => setPanel("main")}
onFinish={() => setPanel("main")}
/>
);
break;
case "change_recovery_key":
content = (
<ChangeRecoveryKey
isSetupFlow={false}
onCancelClick={() => setPanel("main")}
onFinish={() => setPanel("main")}
/>
);
break;
}
return <SettingsTab className="mx_EncryptionUserSettingsTab">{content}</SettingsTab>;
}

View file

@ -268,6 +268,7 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState>
onChange={this.onPersonalRuleChanged} onChange={this.onPersonalRuleChanged}
/> />
<AccessibleButton <AccessibleButton
type="submit"
kind="primary" kind="primary"
onClick={this.onAddPersonalRule} onClick={this.onAddPersonalRule}
disabled={this.state.busy} disabled={this.state.busy}
@ -294,7 +295,12 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState>
value={this.state.newList} value={this.state.newList}
onChange={this.onNewListChanged} onChange={this.onNewListChanged}
/> />
<AccessibleButton kind="primary" onClick={this.onSubscribeList} disabled={this.state.busy}> <AccessibleButton
type="submit"
kind="primary"
onClick={this.onSubscribeList}
disabled={this.state.busy}
>
{_t("action|subscribe")} {_t("action|subscribe")}
</AccessibleButton> </AccessibleButton>
</form> </form>

View file

@ -71,6 +71,7 @@ export const SpaceAvatar: React.FC<Pick<IProps, "avatarUrl" | "avatarDisabled" |
<AccessibleButton <AccessibleButton
className="mx_SpaceBasicSettings_avatar" className="mx_SpaceBasicSettings_avatar"
onClick={() => avatarUploadRef.current?.click()} onClick={() => avatarUploadRef.current?.click()}
alt=""
/> />
<AccessibleButton <AccessibleButton
onClick={() => avatarUploadRef.current?.click()} onClick={() => avatarUploadRef.current?.click()}

View file

@ -221,7 +221,7 @@ const CreateSpaceButton: React.FC<Pick<IInnerSpacePanelProps, "isPanelCollapsed"
isPanelCollapsed, isPanelCollapsed,
setPanelCollapsed, setPanelCollapsed,
}) => { }) => {
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu<HTMLDivElement>(); const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu<HTMLElement>();
useEffect(() => { useEffect(() => {
if (!isPanelCollapsed && menuDisplayed) { if (!isPanelCollapsed && menuDisplayed) {

View file

@ -30,7 +30,7 @@ import defaultDispatcher from "../../../dispatcher/dispatcher";
import { Action } from "../../../dispatcher/actions"; import { Action } from "../../../dispatcher/actions";
import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton"; import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton";
import { toRightOf, useContextMenu } from "../../structures/ContextMenu"; import { toRightOf, useContextMenu } from "../../structures/ContextMenu";
import AccessibleButton, { ButtonEvent, ButtonProps as AccessibleButtonProps } from "../elements/AccessibleButton"; import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState"; import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
import { NotificationLevel } from "../../../stores/notifications/NotificationLevel"; import { NotificationLevel } from "../../../stores/notifications/NotificationLevel";
import { getKeyBindingsManager } from "../../../KeyBindingsManager"; import { getKeyBindingsManager } from "../../../KeyBindingsManager";
@ -39,8 +39,8 @@ import SpaceContextMenu from "../context_menus/SpaceContextMenu";
import { useRovingTabIndex } from "../../../accessibility/RovingTabIndex"; import { useRovingTabIndex } from "../../../accessibility/RovingTabIndex";
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts"; import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
type ButtonProps<T extends keyof HTMLElementTagNameMap> = Omit< type ButtonProps<T extends keyof JSX.IntrinsicElements> = Omit<
AccessibleButtonProps<T>, ComponentProps<typeof AccessibleButton<T>>,
"title" | "onClick" | "size" | "element" "title" | "onClick" | "size" | "element"
> & { > & {
space?: Room; space?: Room;
@ -52,12 +52,12 @@ type ButtonProps<T extends keyof HTMLElementTagNameMap> = Omit<
notificationState?: NotificationState; notificationState?: NotificationState;
isNarrow?: boolean; isNarrow?: boolean;
size: string; size: string;
innerRef?: RefObject<HTMLDivElement>; innerRef?: RefObject<HTMLElement>;
ContextMenuComponent?: ComponentType<ComponentProps<typeof SpaceContextMenu>>; ContextMenuComponent?: ComponentType<ComponentProps<typeof SpaceContextMenu>>;
onClick?(ev?: ButtonEvent): void; onClick?(ev?: ButtonEvent): void;
}; };
export const SpaceButton = <T extends keyof HTMLElementTagNameMap>({ export const SpaceButton = <T extends keyof JSX.IntrinsicElements>({
space, space,
spaceKey: _spaceKey, spaceKey: _spaceKey,
className, className,
@ -72,8 +72,8 @@ export const SpaceButton = <T extends keyof HTMLElementTagNameMap>({
ContextMenuComponent, ContextMenuComponent,
...props ...props
}: ButtonProps<T>): JSX.Element => { }: ButtonProps<T>): JSX.Element => {
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu<HTMLDivElement>(innerRef); const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu<HTMLElement>(innerRef);
const [onFocus, isActive, ref] = useRovingTabIndex<HTMLDivElement>(handle); const [onFocus, isActive, ref] = useRovingTabIndex(handle);
const tabIndex = isActive ? 0 : -1; const tabIndex = isActive ? 0 : -1;
const spaceKey = _spaceKey ?? space?.roomId; const spaceKey = _spaceKey ?? space?.roomId;

View file

@ -69,7 +69,7 @@ interface IDropdownButtonProps extends ButtonProps {
} }
const LegacyCallViewDropdownButton: React.FC<IDropdownButtonProps> = ({ state, deviceKinds, ...props }) => { const LegacyCallViewDropdownButton: React.FC<IDropdownButtonProps> = ({ state, deviceKinds, ...props }) => {
const [menuDisplayed, buttonRef, openMenu, closeMenu] = useContextMenu<HTMLDivElement>(); const [menuDisplayed, buttonRef, openMenu, closeMenu] = useContextMenu();
const [hoveringDropdown, setHoveringDropdown] = useState(false); const [hoveringDropdown, setHoveringDropdown] = useState(false);
const classes = classNames("mx_LegacyCallViewButtons_button", "mx_LegacyCallViewButtons_dropdownButton", { const classes = classNames("mx_LegacyCallViewButtons_button", "mx_LegacyCallViewButtons_dropdownButton", {

View file

@ -542,6 +542,7 @@
"qr_code": "QR Code", "qr_code": "QR Code",
"random": "Random", "random": "Random",
"reactions": "Reactions", "reactions": "Reactions",
"recommended": "Recommended",
"report_a_bug": "Report a bug", "report_a_bug": "Report a bug",
"room": "Room", "room": "Room",
"room_name": "Room name", "room_name": "Room name",
@ -2461,6 +2462,31 @@
"emoji_autocomplete": "Enable Emoji suggestions while typing", "emoji_autocomplete": "Enable Emoji suggestions while typing",
"enable_markdown": "Enable Markdown", "enable_markdown": "Enable Markdown",
"enable_markdown_description": "Start messages with <code>/plain</code> to send without markdown.", "enable_markdown_description": "Start messages with <code>/plain</code> to send without markdown.",
"encryption": {
"dialog_title": "<strong>Settings:</strong> Encryption",
"recovery": {
"change_recovery_key": "Change recovery key",
"change_recovery_key_description": "Get a new recovery key if you've lost your existing one. After changing your recovery key, your old one will no longer work.",
"change_recovery_key_title": "Change recovery key?",
"confirm_description": "Enter the recovery key shown on the previous screen to finish setting up recovery.",
"confirm_finish": "Finish set up",
"confirm_recovery_key": "Confirm recovery key",
"confirm_title": "Enter your recovery key to confirm",
"description": "Recover your cryptographic identity and message history with a recovery key if youve lost all your existing devices.",
"enter_key_error": "The recovery key you entered is not correct.",
"enter_key_title": "Enter recovery key",
"key_active": "Recovery key active",
"save_key_description": "Do not share this with anyone!",
"save_key_title": "Recovery key",
"set_up_recovery": "Set up recovery",
"set_up_recovery_description": "Your key storage is protected by a recovery key. If you need a new recovery key after setup, you can recreate it by selecting %(changeRecoveryKeyButton)s.",
"set_up_recovery_save_key_description": "Write down this recovery key somewhere safe, like a password manager, encrypted note, or a physical safe.",
"set_up_recovery_save_key_title": "Save your recovery key somewhere safe",
"set_up_recovery_secondary_description": "After clicking continue, well generate a recovery key for you.",
"title": "Recovery"
},
"title": "Encryption"
},
"general": { "general": {
"account_management_section": "Account management", "account_management_section": "Account management",
"account_section": "Account", "account_section": "Account",

View file

@ -1,140 +0,0 @@
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import EventEmitter from "events";
import { MatrixClient } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import { useEffect, useState } from "react";
import { createCrossSigning } from "../CreateCrossSigning";
import { SdkContextClass } from "../contexts/SDKContext";
type Status = "in_progress" | "complete" | "error" | undefined;
export const useInitialCryptoSetupStatus = (store: InitialCryptoSetupStore): Status => {
const [status, setStatus] = useState<Status>(store.getStatus());
useEffect(() => {
const update = (): void => {
setStatus(store.getStatus());
};
store.on("update", update);
return () => {
store.off("update", update);
};
}, [store]);
return status;
};
/**
* Logic for setting up crypto state that's done immediately after
* a user registers. Should be transparent to the user, not requiring
* interaction in most cases.
* As distinct from SetupEncryptionStore which is for setting up
* 4S or verifying the device, will always require interaction
* from the user in some form.
*/
export class InitialCryptoSetupStore extends EventEmitter {
private status: Status = undefined;
private client?: MatrixClient;
private isTokenLogin?: boolean;
private stores?: SdkContextClass;
private onFinished?: (success: boolean) => void;
public static sharedInstance(): InitialCryptoSetupStore {
if (!window.mxInitialCryptoStore) window.mxInitialCryptoStore = new InitialCryptoSetupStore();
return window.mxInitialCryptoStore;
}
public getStatus(): Status {
return this.status;
}
/**
* Start the initial crypto setup process.
*
* @param {MatrixClient} client The client to use for the setup
* @param {boolean} isTokenLogin True if the user logged in via a token login, otherwise false
* @param {SdkContextClass} stores The stores to use for the setup
*/
public startInitialCryptoSetup(
client: MatrixClient,
isTokenLogin: boolean,
stores: SdkContextClass,
onFinished: (success: boolean) => void,
): void {
this.client = client;
this.isTokenLogin = isTokenLogin;
this.stores = stores;
this.onFinished = onFinished;
// We just start this process: it's progress is tracked by the events rather
// than returning a promise, so we don't bother.
this.doSetup().catch(() => logger.error("Initial crypto setup failed"));
}
/**
* Retry the initial crypto setup process.
*
* If no crypto setup is currently in process, this will return false.
*
* @returns {boolean} True if a retry was initiated, otherwise false
*/
public retry(): boolean {
if (this.client === undefined || this.isTokenLogin === undefined || this.stores == undefined) return false;
this.doSetup().catch(() => logger.error("Initial crypto setup failed"));
return true;
}
private reset(): void {
this.client = undefined;
this.isTokenLogin = undefined;
this.stores = undefined;
}
private async doSetup(): Promise<void> {
if (this.client === undefined || this.isTokenLogin === undefined || this.stores == undefined) {
throw new Error("No setup is in progress");
}
const cryptoApi = this.client.getCrypto();
if (!cryptoApi) throw new Error("No crypto module found!");
this.status = "in_progress";
this.emit("update");
try {
await createCrossSigning(this.client, this.isTokenLogin, this.stores.accountPasswordStore.getPassword());
this.reset();
this.status = "complete";
this.emit("update");
this.onFinished?.(true);
} catch (e) {
if (this.isTokenLogin) {
// ignore any failures, we are relying on grace period here
this.reset();
this.status = "complete";
this.emit("update");
this.onFinished?.(true);
return;
}
logger.error("Error bootstrapping cross-signing", e);
this.status = "error";
this.emit("update");
}
}
}

View file

@ -33,11 +33,6 @@ export enum Phase {
ConfirmReset = 6, ConfirmReset = 6,
} }
/**
* Logic for setting up 4S and/or verifying the user's device: a process requiring
* ongoing interaction with the user, as distinct from InitialCryptoSetupStore which
* a (usually) non-interactive process that happens immediately after registration.
*/
export class SetupEncryptionStore extends EventEmitter { export class SetupEncryptionStore extends EventEmitter {
private started?: boolean; private started?: boolean;
public phase?: Phase; public phase?: Phase;

View file

@ -194,7 +194,6 @@ export class StopGapWidgetDriver extends WidgetDriver {
EventType.CallSDPStreamMetadataChanged, EventType.CallSDPStreamMetadataChanged,
EventType.CallSDPStreamMetadataChangedPrefix, EventType.CallSDPStreamMetadataChangedPrefix,
EventType.CallReplaces, EventType.CallReplaces,
EventType.CallEncryptionKeysPrefix,
]; ];
for (const eventType of sendRecvToDevice) { for (const eventType of sendRecvToDevice) {
this.allowedCapabilities.add( this.allowedCapabilities.add(

View file

@ -7,22 +7,31 @@ Please see LICENSE files in the repository root for full details.
*/ */
import React from "react"; import React from "react";
import { render, screen } from "jest-matrix-react"; import { render, screen, waitFor } from "jest-matrix-react";
import userEvent from "@testing-library/user-event"; import { mocked } from "jest-mock";
import { MatrixClient } from "matrix-js-sdk/src/matrix";
import { createCrossSigning } from "../../../../../src/CreateCrossSigning";
import { InitialCryptoSetupDialog } from "../../../../../src/components/views/dialogs/security/InitialCryptoSetupDialog"; import { InitialCryptoSetupDialog } from "../../../../../src/components/views/dialogs/security/InitialCryptoSetupDialog";
import { InitialCryptoSetupStore } from "../../../../../src/stores/InitialCryptoSetupStore"; import { createTestClient } from "../../../../test-utils";
jest.mock("../../../../../src/CreateCrossSigning", () => ({
createCrossSigning: jest.fn(),
}));
describe("InitialCryptoSetupDialog", () => { describe("InitialCryptoSetupDialog", () => {
const storeMock = { let client: MatrixClient;
getStatus: jest.fn(), let createCrossSigningResolve: () => void;
retry: jest.fn(), let createCrossSigningReject: (e: Error) => void;
on: jest.fn(),
off: jest.fn(),
};
beforeEach(() => { beforeEach(() => {
jest.spyOn(InitialCryptoSetupStore, "sharedInstance").mockReturnValue(storeMock as any); client = createTestClient();
mocked(createCrossSigning).mockImplementation(() => {
return new Promise((resolve, reject) => {
createCrossSigningResolve = resolve;
createCrossSigningReject = reject;
});
});
}); });
afterEach(() => { afterEach(() => {
@ -30,32 +39,93 @@ describe("InitialCryptoSetupDialog", () => {
jest.restoreAllMocks(); jest.restoreAllMocks();
}); });
it("should show a spinner while the setup is in progress", async () => { it("should call createCrossSigning and show a spinner while it runs", async () => {
const onFinished = jest.fn(); const onFinished = jest.fn();
storeMock.getStatus.mockReturnValue("in_progress"); render(
<InitialCryptoSetupDialog
render(<InitialCryptoSetupDialog onFinished={onFinished} />); matrixClient={client}
accountPassword="hunter2"
tokenLogin={false}
onFinished={onFinished}
/>,
);
expect(createCrossSigning).toHaveBeenCalledWith(client, false, "hunter2");
expect(screen.getByTestId("spinner")).toBeInTheDocument(); expect(screen.getByTestId("spinner")).toBeInTheDocument();
createCrossSigningResolve!();
await waitFor(() => expect(onFinished).toHaveBeenCalledWith(true));
}); });
it("should display an error if setup has failed", async () => { it("should display an error if createCrossSigning fails", async () => {
storeMock.getStatus.mockReturnValue("error"); render(
<InitialCryptoSetupDialog
matrixClient={client}
accountPassword="hunter2"
tokenLogin={false}
onFinished={jest.fn()}
/>,
);
render(<InitialCryptoSetupDialog onFinished={jest.fn()} />); createCrossSigningReject!(new Error("generic error message"));
await expect(await screen.findByRole("button", { name: "Retry" })).toBeInTheDocument(); await expect(await screen.findByRole("button", { name: "Retry" })).toBeInTheDocument();
}); });
it("calls retry when retry button pressed", async () => { it("ignores failures when tokenLogin is true", async () => {
const onFinished = jest.fn(); const onFinished = jest.fn();
storeMock.getStatus.mockReturnValue("error");
render(<InitialCryptoSetupDialog onFinished={onFinished} />); render(
<InitialCryptoSetupDialog
matrixClient={client}
accountPassword="hunter2"
tokenLogin={true}
onFinished={onFinished}
/>,
);
await userEvent.click(await screen.findByRole("button", { name: "Retry" })); createCrossSigningReject!(new Error("generic error message"));
expect(storeMock.retry).toHaveBeenCalled(); await waitFor(() => expect(onFinished).toHaveBeenCalledWith(false));
});
it("cancels the dialog when the cancel button is clicked", async () => {
const onFinished = jest.fn();
render(
<InitialCryptoSetupDialog
matrixClient={client}
accountPassword="hunter2"
tokenLogin={false}
onFinished={onFinished}
/>,
);
createCrossSigningReject!(new Error("generic error message"));
const cancelButton = await screen.findByRole("button", { name: "Cancel" });
cancelButton.click();
expect(onFinished).toHaveBeenCalledWith(false);
});
it("should retry when the retry button is clicked", async () => {
render(
<InitialCryptoSetupDialog
matrixClient={client}
accountPassword="hunter2"
tokenLogin={false}
onFinished={jest.fn()}
/>,
);
createCrossSigningReject!(new Error("generic error message"));
const retryButton = await screen.findByRole("button", { name: "Retry" });
retryButton.click();
expect(createCrossSigning).toHaveBeenCalledTimes(2);
}); });
}); });

View file

@ -314,6 +314,7 @@ exports[`<MatrixChat /> with a soft-logged-out session should show the soft-logo
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary" class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
role="button" role="button"
tabindex="0" tabindex="0"
type="submit"
> >
Sign in Sign in
</div> </div>

View file

@ -135,9 +135,8 @@ exports[`<RoomSummaryCard /> has button to edit topic 1`] = `
style="--mx-box-flex: 1;" style="--mx-box-flex: 1;"
> >
<a <a
class="_link_ue21z_17" class="_link_1mzip_17"
data-kind="primary" data-kind="primary"
data-size="medium"
rel="noreferrer noopener" rel="noreferrer noopener"
> >
<p <p
@ -753,9 +752,8 @@ exports[`<RoomSummaryCard /> renders the room summary 1`] = `
style="--mx-box-flex: 1;" style="--mx-box-flex: 1;"
> >
<a <a
class="_link_ue21z_17" class="_link_1mzip_17"
data-kind="primary" data-kind="primary"
data-size="medium"
rel="noreferrer noopener" rel="noreferrer noopener"
> >
<p <p
@ -1408,9 +1406,8 @@ exports[`<RoomSummaryCard /> renders the room topic in the summary 1`] = `
style="--mx-box-flex: 1;" style="--mx-box-flex: 1;"
> >
<a <a
class="_link_ue21z_17" class="_link_1mzip_17"
data-kind="primary" data-kind="primary"
data-size="medium"
rel="noreferrer noopener" rel="noreferrer noopener"
> >
<p <p

View file

@ -10,7 +10,6 @@ import React, { ComponentProps } from "react";
import { render, screen, waitFor } from "jest-matrix-react"; import { render, screen, waitFor } from "jest-matrix-react";
import { RoomMember } from "matrix-js-sdk/src/matrix"; import { RoomMember } from "matrix-js-sdk/src/matrix";
import userEvent from "@testing-library/user-event"; import userEvent from "@testing-library/user-event";
import { mocked } from "jest-mock";
import { import {
determineAvatarPosition, determineAvatarPosition,
@ -21,9 +20,6 @@ import * as languageHandler from "../../../../../src/languageHandler";
import { stubClient } from "../../../../test-utils"; import { stubClient } from "../../../../test-utils";
import dispatcher from "../../../../../src/dispatcher/dispatcher"; import dispatcher from "../../../../../src/dispatcher/dispatcher";
import { Action } from "../../../../../src/dispatcher/actions"; import { Action } from "../../../../../src/dispatcher/actions";
import { formatDate } from "../../../../../src/DateUtils";
jest.mock("../../../../../src/DateUtils");
describe("ReadReceiptGroup", () => { describe("ReadReceiptGroup", () => {
describe("TooltipText", () => { describe("TooltipText", () => {
@ -91,10 +87,6 @@ describe("ReadReceiptGroup", () => {
describe("<ReadReceiptPerson />", () => { describe("<ReadReceiptPerson />", () => {
stubClient(); stubClient();
// We pick a fixed time but this can still vary depending on the locale
// the tests are run in. We are not testing date formatting here, so stub it out.
mocked(formatDate).mockReturnValue("==MOCK FORMATTED DATE==");
const ROOM_ID = "roomId"; const ROOM_ID = "roomId";
const USER_ID = "@alice:example.org"; const USER_ID = "@alice:example.org";

View file

@ -84,7 +84,7 @@ exports[`ReadReceiptGroup <ReadReceiptPerson /> should render 1`] = `
<p <p
class="mx_ReadReceiptGroup_secondary" class="mx_ReadReceiptGroup_secondary"
> >
==MOCK FORMATTED DATE== Wed, 15 May, 0:00
</p> </p>
</div> </div>
</div> </div>

View file

@ -35,7 +35,7 @@ describe("SetIntegrationManager", () => {
deleteThreePid: jest.fn(), deleteThreePid: jest.fn(),
}); });
let stores!: SdkContextClass; let stores: SdkContextClass;
const getComponent = () => ( const getComponent = () => (
<MatrixClientContext.Provider value={mockClient}> <MatrixClientContext.Provider value={mockClient}>

View file

@ -19,14 +19,14 @@ exports[`<LayoutSwitcher /> should render 1`] = `
class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi" class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi"
> >
<form <form
class="_root_ssths_24 mx_LayoutSwitcher_LayoutSelector" class="_root_dgy0u_24 mx_LayoutSwitcher_LayoutSelector"
> >
<div <div
class="_field_ssths_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio" class="_field_dgy0u_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
> >
<label <label
aria-label="Modern" aria-label="Modern"
class="_label_ssths_67" class="_label_dgy0u_67"
for="radix-:r0:" for="radix-:r0:"
> >
<div <div
@ -149,11 +149,11 @@ exports[`<LayoutSwitcher /> should render 1`] = `
</label> </label>
</div> </div>
<div <div
class="_field_ssths_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio" class="_field_dgy0u_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
> >
<label <label
aria-label="Message bubbles" aria-label="Message bubbles"
class="_label_ssths_67" class="_label_dgy0u_67"
for="radix-:r9:" for="radix-:r9:"
> >
<div <div
@ -275,11 +275,11 @@ exports[`<LayoutSwitcher /> should render 1`] = `
</label> </label>
</div> </div>
<div <div
class="_field_ssths_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio" class="_field_dgy0u_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
> >
<label <label
aria-label="IRC (experimental)" aria-label="IRC (experimental)"
class="_label_ssths_67" class="_label_dgy0u_67"
for="radix-:ri:" for="radix-:ri:"
> >
<div <div
@ -402,13 +402,13 @@ exports[`<LayoutSwitcher /> should render 1`] = `
</div> </div>
</form> </form>
<form <form
class="_root_ssths_24" class="_root_dgy0u_24"
> >
<div <div
class="_inline-field_ssths_40" class="_inline-field_dgy0u_40"
> >
<div <div
class="_inline-field-control_ssths_52" class="_inline-field-control_dgy0u_52"
> >
<div <div
class="_container_qnvru_18" class="_container_qnvru_18"
@ -427,16 +427,16 @@ exports[`<LayoutSwitcher /> should render 1`] = `
</div> </div>
</div> </div>
<div <div
class="_inline-field-body_ssths_46" class="_inline-field-body_dgy0u_46"
> >
<label <label
class="_label_ssths_67" class="_label_dgy0u_67"
for="radix-:rr:" for="radix-:rr:"
> >
Show compact text and messages Show compact text and messages
</label> </label>
<span <span
class="_message_ssths_93 _help-message_ssths_99" class="_message_dgy0u_98 _help-message_dgy0u_104"
id="radix-:rs:" id="radix-:rs:"
> >
Modern layout must be selected to use this feature. Modern layout must be selected to use this feature.

View file

@ -19,13 +19,13 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi" class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi"
> >
<form <form
class="_root_ssths_24" class="_root_dgy0u_24"
> >
<div <div
class="_inline-field_ssths_40" class="_inline-field_dgy0u_40"
> >
<div <div
class="_inline-field-control_ssths_52" class="_inline-field-control_dgy0u_52"
> >
<div <div
class="_container_qnvru_18" class="_container_qnvru_18"
@ -43,10 +43,10 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
</div> </div>
</div> </div>
<div <div
class="_inline-field-body_ssths_46" class="_inline-field-body_dgy0u_46"
> >
<label <label
class="_label_ssths_67" class="_label_dgy0u_67"
for="radix-:r28:" for="radix-:r28:"
> >
Match system theme Match system theme
@ -55,13 +55,13 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
</div> </div>
</form> </form>
<form <form
class="_root_ssths_24 mx_ThemeChoicePanel_ThemeSelectors" class="_root_dgy0u_24 mx_ThemeChoicePanel_ThemeSelectors"
> >
<div <div
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_enabled cpd-theme-light" class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_enabled cpd-theme-light"
> >
<div <div
class="_inline-field-control_ssths_52" class="_inline-field-control_dgy0u_52"
> >
<div <div
class="_container_1vw5h_18" class="_container_1vw5h_18"
@ -81,10 +81,10 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
</div> </div>
</div> </div>
<div <div
class="_inline-field-body_ssths_46" class="_inline-field-body_dgy0u_46"
> >
<label <label
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label" class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
for="radix-:r29:" for="radix-:r29:"
> >
Light Light
@ -92,10 +92,10 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
</div> </div>
</div> </div>
<div <div
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark" class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
> >
<div <div
class="_inline-field-control_ssths_52" class="_inline-field-control_dgy0u_52"
> >
<div <div
class="_container_1vw5h_18" class="_container_1vw5h_18"
@ -114,10 +114,10 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
</div> </div>
</div> </div>
<div <div
class="_inline-field-body_ssths_46" class="_inline-field-body_dgy0u_46"
> >
<label <label
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label" class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
for="radix-:r2a:" for="radix-:r2a:"
> >
Dark Dark
@ -125,10 +125,10 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
</div> </div>
</div> </div>
<div <div
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector cpd-theme-light" class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector cpd-theme-light"
> >
<div <div
class="_inline-field-control_ssths_52" class="_inline-field-control_dgy0u_52"
> >
<div <div
class="_container_1vw5h_18" class="_container_1vw5h_18"
@ -147,10 +147,10 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
</div> </div>
</div> </div>
<div <div
class="_inline-field-body_ssths_46" class="_inline-field-body_dgy0u_46"
> >
<label <label
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label" class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
for="radix-:r2b:" for="radix-:r2b:"
> >
High contrast High contrast
@ -158,10 +158,10 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
</div> </div>
</div> </div>
<div <div
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark" class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
> >
<div <div
class="_inline-field-control_ssths_52" class="_inline-field-control_dgy0u_52"
> >
<div <div
class="_container_1vw5h_18" class="_container_1vw5h_18"
@ -180,10 +180,10 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
</div> </div>
</div> </div>
<div <div
class="_inline-field-body_ssths_46" class="_inline-field-body_dgy0u_46"
> >
<label <label
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label" class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
for="radix-:r2c:" for="radix-:r2c:"
> >
Alice theme Alice theme
@ -195,13 +195,13 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
class="mx_ThemeChoicePanel_CustomTheme" class="mx_ThemeChoicePanel_CustomTheme"
> >
<form <form
class="_root_ssths_24 mx_ThemeChoicePanel_CustomTheme_EditInPlace" class="_root_dgy0u_24 mx_ThemeChoicePanel_CustomTheme_EditInPlace"
> >
<div <div
class="_field_ssths_34" class="_field_dgy0u_34"
> >
<label <label
class="_label_ssths_67" class="_label_dgy0u_67"
for="radix-:r2d:" for="radix-:r2d:"
> >
Add custom theme Add custom theme
@ -219,7 +219,7 @@ exports[`<ThemeChoicePanel /> custom theme should display custom theme 1`] = `
/> />
</div> </div>
<span <span
class="_message_ssths_93 _help-message_ssths_99" class="_message_dgy0u_98 _help-message_dgy0u_104"
id="radix-:r2e:" id="radix-:r2e:"
> >
Enter the URL of a custom theme you want to apply. Enter the URL of a custom theme you want to apply.
@ -296,13 +296,13 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi" class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi"
> >
<form <form
class="_root_ssths_24" class="_root_dgy0u_24"
> >
<div <div
class="_inline-field_ssths_40" class="_inline-field_dgy0u_40"
> >
<div <div
class="_inline-field-control_ssths_52" class="_inline-field-control_dgy0u_52"
> >
<div <div
class="_container_qnvru_18" class="_container_qnvru_18"
@ -320,10 +320,10 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
</div> </div>
</div> </div>
<div <div
class="_inline-field-body_ssths_46" class="_inline-field-body_dgy0u_46"
> >
<label <label
class="_label_ssths_67" class="_label_dgy0u_67"
for="radix-:r10:" for="radix-:r10:"
> >
Match system theme Match system theme
@ -332,13 +332,13 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
</div> </div>
</form> </form>
<form <form
class="_root_ssths_24 mx_ThemeChoicePanel_ThemeSelectors" class="_root_dgy0u_24 mx_ThemeChoicePanel_ThemeSelectors"
> >
<div <div
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_enabled cpd-theme-light" class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_enabled cpd-theme-light"
> >
<div <div
class="_inline-field-control_ssths_52" class="_inline-field-control_dgy0u_52"
> >
<div <div
class="_container_1vw5h_18" class="_container_1vw5h_18"
@ -358,10 +358,10 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
</div> </div>
</div> </div>
<div <div
class="_inline-field-body_ssths_46" class="_inline-field-body_dgy0u_46"
> >
<label <label
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label" class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
for="radix-:r11:" for="radix-:r11:"
> >
Light Light
@ -369,10 +369,10 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
</div> </div>
</div> </div>
<div <div
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark" class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
> >
<div <div
class="_inline-field-control_ssths_52" class="_inline-field-control_dgy0u_52"
> >
<div <div
class="_container_1vw5h_18" class="_container_1vw5h_18"
@ -391,10 +391,10 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
</div> </div>
</div> </div>
<div <div
class="_inline-field-body_ssths_46" class="_inline-field-body_dgy0u_46"
> >
<label <label
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label" class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
for="radix-:r12:" for="radix-:r12:"
> >
Dark Dark
@ -402,10 +402,10 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
</div> </div>
</div> </div>
<div <div
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector cpd-theme-light" class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector cpd-theme-light"
> >
<div <div
class="_inline-field-control_ssths_52" class="_inline-field-control_dgy0u_52"
> >
<div <div
class="_container_1vw5h_18" class="_container_1vw5h_18"
@ -424,10 +424,10 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
</div> </div>
</div> </div>
<div <div
class="_inline-field-body_ssths_46" class="_inline-field-body_dgy0u_46"
> >
<label <label
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label" class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
for="radix-:r13:" for="radix-:r13:"
> >
High contrast High contrast
@ -435,10 +435,10 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
</div> </div>
</div> </div>
<div <div
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark" class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
> >
<div <div
class="_inline-field-control_ssths_52" class="_inline-field-control_dgy0u_52"
> >
<div <div
class="_container_1vw5h_18" class="_container_1vw5h_18"
@ -457,10 +457,10 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
</div> </div>
</div> </div>
<div <div
class="_inline-field-body_ssths_46" class="_inline-field-body_dgy0u_46"
> >
<label <label
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label" class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
for="radix-:r14:" for="radix-:r14:"
> >
Alice theme Alice theme
@ -472,13 +472,13 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
class="mx_ThemeChoicePanel_CustomTheme" class="mx_ThemeChoicePanel_CustomTheme"
> >
<form <form
class="_root_ssths_24 mx_ThemeChoicePanel_CustomTheme_EditInPlace" class="_root_dgy0u_24 mx_ThemeChoicePanel_CustomTheme_EditInPlace"
> >
<div <div
class="_field_ssths_34" class="_field_dgy0u_34"
> >
<label <label
class="_label_ssths_67" class="_label_dgy0u_67"
for="radix-:r15:" for="radix-:r15:"
> >
Add custom theme Add custom theme
@ -496,7 +496,7 @@ exports[`<ThemeChoicePanel /> custom theme should render the custom theme sectio
/> />
</div> </div>
<span <span
class="_message_ssths_93 _help-message_ssths_99" class="_message_dgy0u_98 _help-message_dgy0u_104"
id="radix-:r16:" id="radix-:r16:"
> >
Enter the URL of a custom theme you want to apply. Enter the URL of a custom theme you want to apply.
@ -573,13 +573,13 @@ exports[`<ThemeChoicePanel /> renders the theme choice UI 1`] = `
class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi" class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi"
> >
<form <form
class="_root_ssths_24" class="_root_dgy0u_24"
> >
<div <div
class="_inline-field_ssths_40" class="_inline-field_dgy0u_40"
> >
<div <div
class="_inline-field-control_ssths_52" class="_inline-field-control_dgy0u_52"
> >
<div <div
class="_container_qnvru_18" class="_container_qnvru_18"
@ -597,10 +597,10 @@ exports[`<ThemeChoicePanel /> renders the theme choice UI 1`] = `
</div> </div>
</div> </div>
<div <div
class="_inline-field-body_ssths_46" class="_inline-field-body_dgy0u_46"
> >
<label <label
class="_label_ssths_67" class="_label_dgy0u_67"
for="radix-:r0:" for="radix-:r0:"
> >
Match system theme Match system theme
@ -609,13 +609,13 @@ exports[`<ThemeChoicePanel /> renders the theme choice UI 1`] = `
</div> </div>
</form> </form>
<form <form
class="_root_ssths_24 mx_ThemeChoicePanel_ThemeSelectors" class="_root_dgy0u_24 mx_ThemeChoicePanel_ThemeSelectors"
> >
<div <div
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_enabled cpd-theme-light" class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_enabled cpd-theme-light"
> >
<div <div
class="_inline-field-control_ssths_52" class="_inline-field-control_dgy0u_52"
> >
<div <div
class="_container_1vw5h_18" class="_container_1vw5h_18"
@ -635,10 +635,10 @@ exports[`<ThemeChoicePanel /> renders the theme choice UI 1`] = `
</div> </div>
</div> </div>
<div <div
class="_inline-field-body_ssths_46" class="_inline-field-body_dgy0u_46"
> >
<label <label
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label" class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
for="radix-:r1:" for="radix-:r1:"
> >
Light Light
@ -646,10 +646,10 @@ exports[`<ThemeChoicePanel /> renders the theme choice UI 1`] = `
</div> </div>
</div> </div>
<div <div
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark" class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector cpd-theme-dark"
> >
<div <div
class="_inline-field-control_ssths_52" class="_inline-field-control_dgy0u_52"
> >
<div <div
class="_container_1vw5h_18" class="_container_1vw5h_18"
@ -668,10 +668,10 @@ exports[`<ThemeChoicePanel /> renders the theme choice UI 1`] = `
</div> </div>
</div> </div>
<div <div
class="_inline-field-body_ssths_46" class="_inline-field-body_dgy0u_46"
> >
<label <label
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label" class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
for="radix-:r2:" for="radix-:r2:"
> >
Dark Dark
@ -679,10 +679,10 @@ exports[`<ThemeChoicePanel /> renders the theme choice UI 1`] = `
</div> </div>
</div> </div>
<div <div
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector cpd-theme-light" class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector cpd-theme-light"
> >
<div <div
class="_inline-field-control_ssths_52" class="_inline-field-control_dgy0u_52"
> >
<div <div
class="_container_1vw5h_18" class="_container_1vw5h_18"
@ -701,10 +701,10 @@ exports[`<ThemeChoicePanel /> renders the theme choice UI 1`] = `
</div> </div>
</div> </div>
<div <div
class="_inline-field-body_ssths_46" class="_inline-field-body_dgy0u_46"
> >
<label <label
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label" class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
for="radix-:r3:" for="radix-:r3:"
> >
High contrast High contrast

View file

@ -32,13 +32,13 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi" class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi"
> >
<form <form
class="_root_ssths_24 mx_ThemeChoicePanel_ThemeSelectors" class="_root_dgy0u_24 mx_ThemeChoicePanel_ThemeSelectors"
> >
<div <div
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_disabled cpd-theme-light" class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_disabled cpd-theme-light"
> >
<div <div
class="_inline-field-control_ssths_52" class="_inline-field-control_dgy0u_52"
> >
<div <div
class="_container_1vw5h_18" class="_container_1vw5h_18"
@ -58,10 +58,10 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
</div> </div>
</div> </div>
<div <div
class="_inline-field-body_ssths_46" class="_inline-field-body_dgy0u_46"
> >
<label <label
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label" class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
for="radix-:r0:" for="radix-:r0:"
> >
Light Light
@ -69,10 +69,10 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
</div> </div>
</div> </div>
<div <div
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_disabled cpd-theme-dark" class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_disabled cpd-theme-dark"
> >
<div <div
class="_inline-field-control_ssths_52" class="_inline-field-control_dgy0u_52"
> >
<div <div
class="_container_1vw5h_18" class="_container_1vw5h_18"
@ -92,10 +92,10 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
</div> </div>
</div> </div>
<div <div
class="_inline-field-body_ssths_46" class="_inline-field-body_dgy0u_46"
> >
<label <label
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label" class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
for="radix-:r1:" for="radix-:r1:"
> >
Dark Dark
@ -103,10 +103,10 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
</div> </div>
</div> </div>
<div <div
class="_inline-field_ssths_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_disabled cpd-theme-light" class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_disabled cpd-theme-light"
> >
<div <div
class="_inline-field-control_ssths_52" class="_inline-field-control_dgy0u_52"
> >
<div <div
class="_container_1vw5h_18" class="_container_1vw5h_18"
@ -126,10 +126,10 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
</div> </div>
</div> </div>
<div <div
class="_inline-field-body_ssths_46" class="_inline-field-body_dgy0u_46"
> >
<label <label
class="_label_ssths_67 mx_ThemeChoicePanel_themeSelector_Label" class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
for="radix-:r2:" for="radix-:r2:"
> >
High contrast High contrast
@ -162,14 +162,14 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi" class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi"
> >
<form <form
class="_root_ssths_24 mx_LayoutSwitcher_LayoutSelector" class="_root_dgy0u_24 mx_LayoutSwitcher_LayoutSelector"
> >
<div <div
class="_field_ssths_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio" class="_field_dgy0u_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
> >
<label <label
aria-label="Modern" aria-label="Modern"
class="_label_ssths_67" class="_label_dgy0u_67"
for="radix-:r3:" for="radix-:r3:"
> >
<div <div
@ -292,11 +292,11 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
</label> </label>
</div> </div>
<div <div
class="_field_ssths_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio" class="_field_dgy0u_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
> >
<label <label
aria-label="Message bubbles" aria-label="Message bubbles"
class="_label_ssths_67" class="_label_dgy0u_67"
for="radix-:rc:" for="radix-:rc:"
> >
<div <div
@ -418,11 +418,11 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
</label> </label>
</div> </div>
<div <div
class="_field_ssths_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio" class="_field_dgy0u_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
> >
<label <label
aria-label="IRC (experimental)" aria-label="IRC (experimental)"
class="_label_ssths_67" class="_label_dgy0u_67"
for="radix-:rl:" for="radix-:rl:"
> >
<div <div
@ -545,13 +545,13 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
</div> </div>
</form> </form>
<form <form
class="_root_ssths_24" class="_root_dgy0u_24"
> >
<div <div
class="_inline-field_ssths_40" class="_inline-field_dgy0u_40"
> >
<div <div
class="_inline-field-control_ssths_52" class="_inline-field-control_dgy0u_52"
> >
<div <div
class="_container_qnvru_18" class="_container_qnvru_18"
@ -570,16 +570,16 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
</div> </div>
</div> </div>
<div <div
class="_inline-field-body_ssths_46" class="_inline-field-body_dgy0u_46"
> >
<label <label
class="_label_ssths_67" class="_label_dgy0u_67"
for="radix-:ru:" for="radix-:ru:"
> >
Show compact text and messages Show compact text and messages
</label> </label>
<span <span
class="_message_ssths_93 _help-message_ssths_99" class="_message_dgy0u_98 _help-message_dgy0u_104"
id="radix-:rv:" id="radix-:rv:"
> >
Modern layout must be selected to use this feature. Modern layout must be selected to use this feature.

View file

@ -85,6 +85,7 @@ exports[`<MjolnirUserSettingsTab /> renders correctly when user has no ignored u
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary" class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
role="button" role="button"
tabindex="0" tabindex="0"
type="submit"
> >
Ignore Ignore
</div> </div>
@ -149,6 +150,7 @@ exports[`<MjolnirUserSettingsTab /> renders correctly when user has no ignored u
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary" class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
role="button" role="button"
tabindex="0" tabindex="0"
type="submit"
> >
Subscribe Subscribe
</div> </div>

View file

@ -8,7 +8,6 @@ exports[`<SpacePanel /> should show all activated MetaSpaces in the correct orde
> >
<div <div
class="mx_UserMenu" class="mx_UserMenu"
data-floating-ui-inert=""
> >
<div <div
aria-expanded="false" aria-expanded="false"
@ -43,7 +42,6 @@ exports[`<SpacePanel /> should show all activated MetaSpaces in the correct orde
<ul <ul
aria-label="Spaces" aria-label="Spaces"
class="mx_AutoHideScrollbar mx_SpaceTreeLevel" class="mx_AutoHideScrollbar mx_SpaceTreeLevel"
data-floating-ui-inert=""
data-rbd-droppable-context-id="0" data-rbd-droppable-context-id="0"
data-rbd-droppable-id="top-level-spaces" data-rbd-droppable-id="top-level-spaces"
role="tree" role="tree"
@ -238,7 +236,6 @@ exports[`<SpacePanel /> should show all activated MetaSpaces in the correct orde
aria-label="Threads" aria-label="Threads"
aria-labelledby=":r14:" aria-labelledby=":r14:"
class="_icon-button_bh2qc_17 mx_ThreadsActivityCentreButton" class="_icon-button_bh2qc_17 mx_ThreadsActivityCentreButton"
data-floating-ui-inert=""
role="button" role="button"
style="--cpd-icon-button-size: 32px;" style="--cpd-icon-button-size: 32px;"
tabindex="0" tabindex="0"
@ -263,7 +260,6 @@ exports[`<SpacePanel /> should show all activated MetaSpaces in the correct orde
</button> </button>
<span <span
aria-hidden="true" aria-hidden="true"
data-floating-ui-inert=""
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;" style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
tabindex="-1" tabindex="-1"
/> />
@ -276,7 +272,6 @@ exports[`<SpacePanel /> should show all activated MetaSpaces in the correct orde
/> />
<span <span
aria-owns=":r19:" aria-owns=":r19:"
data-floating-ui-inert=""
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;" style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
/> />
<span <span
@ -291,7 +286,6 @@ exports[`<SpacePanel /> should show all activated MetaSpaces in the correct orde
aria-expanded="false" aria-expanded="false"
aria-label="Quick settings" aria-label="Quick settings"
class="mx_AccessibleButton mx_QuickSettingsButton" class="mx_AccessibleButton mx_QuickSettingsButton"
data-floating-ui-inert=""
role="button" role="button"
tabindex="0" tabindex="0"
/> />

View file

@ -477,7 +477,9 @@ exports[`ThreadsActivityCentre should order the room with the same notification
exports[`ThreadsActivityCentre should render the release announcement 1`] = ` exports[`ThreadsActivityCentre should render the release announcement 1`] = `
<body> <body>
<div> <div
data-floating-ui-inert=""
>
<div <div
class="mx_ThreadsActivityCentre_container" class="mx_ThreadsActivityCentre_container"
> >
@ -489,7 +491,6 @@ exports[`ThreadsActivityCentre should render the release announcement 1`] = `
aria-label="Threads" aria-label="Threads"
aria-labelledby=":rc:" aria-labelledby=":rc:"
class="_icon-button_bh2qc_17 mx_ThreadsActivityCentreButton" class="_icon-button_bh2qc_17 mx_ThreadsActivityCentreButton"
data-floating-ui-inert=""
role="button" role="button"
style="--cpd-icon-button-size: 32px;" style="--cpd-icon-button-size: 32px;"
tabindex="0" tabindex="0"
@ -514,7 +515,6 @@ exports[`ThreadsActivityCentre should render the release announcement 1`] = `
</button> </button>
<span <span
aria-hidden="true" aria-hidden="true"
data-floating-ui-inert=""
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;" style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
tabindex="-1" tabindex="-1"
/> />
@ -527,7 +527,6 @@ exports[`ThreadsActivityCentre should render the release announcement 1`] = `
/> />
<span <span
aria-owns=":rh:" aria-owns=":rh:"
data-floating-ui-inert=""
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;" style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
/> />
<span <span
@ -586,6 +585,7 @@ exports[`ThreadsActivityCentre should render the release announcement 1`] = `
> >
<span <span
data-floating-ui-focus-guard="" data-floating-ui-focus-guard=""
data-floating-ui-inert=""
data-type="inside" data-type="inside"
role="button" role="button"
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;" style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
@ -648,6 +648,7 @@ exports[`ThreadsActivityCentre should render the release announcement 1`] = `
</div> </div>
<span <span
data-floating-ui-focus-guard="" data-floating-ui-focus-guard=""
data-floating-ui-inert=""
data-type="inside" data-type="inside"
role="button" role="button"
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;" style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"

View file

@ -1,85 +0,0 @@
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import { mocked } from "jest-mock";
import { MatrixClient } from "matrix-js-sdk/src/matrix";
import { waitFor } from "jest-matrix-react";
import { createCrossSigning } from "../../../src/CreateCrossSigning";
import { InitialCryptoSetupStore } from "../../../src/stores/InitialCryptoSetupStore";
import { SdkContextClass } from "../../../src/contexts/SDKContext";
import { createTestClient } from "../../test-utils";
import { AccountPasswordStore } from "../../../src/stores/AccountPasswordStore";
jest.mock("../../../src/CreateCrossSigning", () => ({
createCrossSigning: jest.fn(),
}));
describe("InitialCryptoSetupStore", () => {
let testStore: InitialCryptoSetupStore;
let client: MatrixClient;
let stores: SdkContextClass;
let createCrossSigningResolve: () => void;
let createCrossSigningReject: (e: Error) => void;
beforeEach(() => {
testStore = new InitialCryptoSetupStore();
client = createTestClient();
stores = {
accountPasswordStore: {
getPassword: jest.fn(),
} as unknown as AccountPasswordStore,
} as unknown as SdkContextClass;
mocked(createCrossSigning).mockImplementation(() => {
return new Promise<void>((resolve, reject) => {
createCrossSigningResolve = resolve;
createCrossSigningReject = reject;
});
});
});
it("should call createCrossSigning when startInitialCryptoSetup is called", async () => {
testStore.startInitialCryptoSetup(client, false, stores, jest.fn());
await waitFor(() => expect(createCrossSigning).toHaveBeenCalled());
});
it("emits an update event when createCrossSigning resolves", async () => {
const updateSpy = jest.fn();
testStore.on("update", updateSpy);
testStore.startInitialCryptoSetup(client, false, stores, jest.fn());
createCrossSigningResolve();
await waitFor(() => expect(updateSpy).toHaveBeenCalled());
expect(testStore.getStatus()).toBe("complete");
});
it("emits an update event when createCrossSigning rejects", async () => {
const updateSpy = jest.fn();
testStore.on("update", updateSpy);
testStore.startInitialCryptoSetup(client, false, stores, jest.fn());
createCrossSigningReject(new Error("Test error"));
await waitFor(() => expect(updateSpy).toHaveBeenCalled());
expect(testStore.getStatus()).toBe("error");
});
it("should ignore failures if tokenLogin is true", async () => {
const updateSpy = jest.fn();
testStore.on("update", updateSpy);
testStore.startInitialCryptoSetup(client, true, stores, jest.fn());
createCrossSigningReject(new Error("Test error"));
await waitFor(() => expect(updateSpy).toHaveBeenCalled());
expect(testStore.getStatus()).toBe("complete");
});
});

View file

@ -178,26 +178,22 @@ describe("formatDate", () => {
it("should return time string if date is within same day", () => { it("should return time string if date is within same day", () => {
const date = new Date(REPEATABLE_DATE.getTime() + 2 * HOUR_MS + 12 * MINUTE_MS); const date = new Date(REPEATABLE_DATE.getTime() + 2 * HOUR_MS + 12 * MINUTE_MS);
// We use en-US for these tests because there was a change in Node 22.12 which removed expect(formatDate(date, false, "en-GB")).toMatchInlineSnapshot(`"19:10"`);
// the comma after the weekday for en-GB which makes the test output different things
// on different node versions. I'm not sure what a better fix would be, so let's just use
// a locale that happens to have a more stable formatting right now.
expect(formatDate(date, false, "en-US")).toMatchInlineSnapshot(`"19:10"`);
}); });
it("should return time string with weekday if date is within last 6 days", () => { it("should return time string with weekday if date is within last 6 days", () => {
const date = new Date(REPEATABLE_DATE.getTime() - 6 * DAY_MS + 2 * HOUR_MS + 12 * MINUTE_MS); const date = new Date(REPEATABLE_DATE.getTime() - 6 * DAY_MS + 2 * HOUR_MS + 12 * MINUTE_MS);
expect(formatDate(date, false, "en-US")).toMatchInlineSnapshot(`"Fri 19:10"`); expect(formatDate(date, false, "en-GB")).toMatchInlineSnapshot(`"Fri 19:10"`);
}); });
it("should return time & date string without year if it is within the same year", () => { it("should return time & date string without year if it is within the same year", () => {
const date = new Date(REPEATABLE_DATE.getTime() - 66 * DAY_MS + 2 * HOUR_MS + 12 * MINUTE_MS); const date = new Date(REPEATABLE_DATE.getTime() - 66 * DAY_MS + 2 * HOUR_MS + 12 * MINUTE_MS);
expect(formatDate(date, false, "en-US")).toMatchInlineSnapshot(`"Mon, Sep 12, 19:10"`); expect(formatDate(date, false, "en-GB")).toMatchInlineSnapshot(`"Mon, 12 Sept, 19:10"`);
}); });
it("should return full time & date string otherwise", () => { it("should return full time & date string otherwise", () => {
const date = new Date(REPEATABLE_DATE.getTime() - 666 * DAY_MS + 2 * HOUR_MS + 12 * MINUTE_MS); const date = new Date(REPEATABLE_DATE.getTime() - 666 * DAY_MS + 2 * HOUR_MS + 12 * MINUTE_MS);
expect(formatDate(date, false, "en-US")).toMatchInlineSnapshot(`"Wed, Jan 20, 2021, 19:10"`); expect(formatDate(date, false, "en-GB")).toMatchInlineSnapshot(`"Wed, 20 Jan 2021, 19:10"`);
}); });
}); });

375
yarn.lock
View file

@ -34,7 +34,7 @@
dependencies: dependencies:
axe-core "~4.10.2" axe-core "~4.10.2"
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0", "@babel/code-frame@^7.26.2": "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0":
version "7.26.2" version "7.26.2"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85"
integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==
@ -51,16 +51,11 @@
"@babel/highlight" "^7.25.7" "@babel/highlight" "^7.25.7"
picocolors "^1.0.0" picocolors "^1.0.0"
"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.26.0": "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.25.9", "@babel/compat-data@^7.26.0":
version "7.26.2" version "7.26.2"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.2.tgz#278b6b13664557de95b8f35b90d96785850bb56e" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.2.tgz#278b6b13664557de95b8f35b90d96785850bb56e"
integrity sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg== integrity sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==
"@babel/compat-data@^7.25.9":
version "7.26.3"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.3.tgz#99488264a56b2aded63983abd6a417f03b92ed02"
integrity sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==
"@babel/core@^7.0.0", "@babel/core@^7.11.6", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.18.5", "@babel/core@^7.21.3", "@babel/core@^7.23.9": "@babel/core@^7.0.0", "@babel/core@^7.11.6", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.18.5", "@babel/core@^7.21.3", "@babel/core@^7.23.9":
version "7.26.0" version "7.26.0"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.0.tgz#d78b6023cc8f3114ccf049eb219613f74a747b40" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.0.tgz#d78b6023cc8f3114ccf049eb219613f74a747b40"
@ -108,13 +103,13 @@
"@jridgewell/trace-mapping" "^0.3.25" "@jridgewell/trace-mapping" "^0.3.25"
jsesc "^3.0.2" jsesc "^3.0.2"
"@babel/generator@^7.26.0", "@babel/generator@^7.26.3": "@babel/generator@^7.25.9", "@babel/generator@^7.26.0":
version "7.26.3" version "7.26.2"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.3.tgz#ab8d4360544a425c90c248df7059881f4b2ce019" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.2.tgz#87b75813bec87916210e5e01939a4c823d6bb74f"
integrity sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ== integrity sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==
dependencies: dependencies:
"@babel/parser" "^7.26.3" "@babel/parser" "^7.26.2"
"@babel/types" "^7.26.3" "@babel/types" "^7.26.0"
"@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/gen-mapping" "^0.3.5"
"@jridgewell/trace-mapping" "^0.3.25" "@jridgewell/trace-mapping" "^0.3.25"
jsesc "^3.0.2" jsesc "^3.0.2"
@ -308,12 +303,12 @@
dependencies: dependencies:
"@babel/types" "^7.25.8" "@babel/types" "^7.25.8"
"@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.3": "@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.2":
version "7.26.3" version "7.26.2"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.3.tgz#8c51c5db6ddf08134af1ddbacf16aaab48bac234" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.2.tgz#fd7b6f487cfea09889557ef5d4eeb9ff9a5abd11"
integrity sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA== integrity sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==
dependencies: dependencies:
"@babel/types" "^7.26.3" "@babel/types" "^7.26.0"
"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.9": "@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.9":
version "7.25.9" version "7.25.9"
@ -1065,9 +1060,9 @@
esutils "^2.0.2" esutils "^2.0.2"
"@babel/preset-react@^7.12.10", "@babel/preset-react@^7.18.6": "@babel/preset-react@^7.12.10", "@babel/preset-react@^7.18.6":
version "7.26.3" version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.26.3.tgz#7c5e028d623b4683c1f83a0bd4713b9100560caa" resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.25.9.tgz#5f473035dc2094bcfdbc7392d0766bd42dce173e"
integrity sha512-Nl03d6T9ky516DGK2YMxrTqvnpUW63TnJMOMonj+Zae0JiPC5BC9xPMSL6L8fiSpA5vP88qfygavVQvnLp+6Cw== integrity sha512-D3to0uSPiWE7rBrdIICCd0tJSIGpLaaGptna2+w7Pft5xMqLpA1sz99DK5TZ1TjGbdQ/VI1eCSZ06dv3lT4JOw==
dependencies: dependencies:
"@babel/helper-plugin-utils" "^7.25.9" "@babel/helper-plugin-utils" "^7.25.9"
"@babel/helper-validator-option" "^7.25.9" "@babel/helper-validator-option" "^7.25.9"
@ -1126,15 +1121,15 @@
globals "^11.1.0" globals "^11.1.0"
"@babel/traverse@^7.25.9": "@babel/traverse@^7.25.9":
version "7.26.4" version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.4.tgz#ac3a2a84b908dde6d463c3bfa2c5fdc1653574bd" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.9.tgz#a50f8fe49e7f69f53de5bea7e413cd35c5e13c84"
integrity sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w== integrity sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==
dependencies: dependencies:
"@babel/code-frame" "^7.26.2" "@babel/code-frame" "^7.25.9"
"@babel/generator" "^7.26.3" "@babel/generator" "^7.25.9"
"@babel/parser" "^7.26.3" "@babel/parser" "^7.25.9"
"@babel/template" "^7.25.9" "@babel/template" "^7.25.9"
"@babel/types" "^7.26.3" "@babel/types" "^7.25.9"
debug "^4.3.1" debug "^4.3.1"
globals "^11.1.0" globals "^11.1.0"
@ -1147,7 +1142,7 @@
"@babel/helper-validator-identifier" "^7.25.7" "@babel/helper-validator-identifier" "^7.25.7"
to-fast-properties "^2.0.0" to-fast-properties "^2.0.0"
"@babel/types@^7.25.7", "@babel/types@^7.4.4": "@babel/types@^7.25.7", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.4.4":
version "7.26.0" version "7.26.0"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.0.tgz#deabd08d6b753bc8e0f198f8709fb575e31774ff" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.0.tgz#deabd08d6b753bc8e0f198f8709fb575e31774ff"
integrity sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA== integrity sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==
@ -1155,14 +1150,6 @@
"@babel/helper-string-parser" "^7.25.9" "@babel/helper-string-parser" "^7.25.9"
"@babel/helper-validator-identifier" "^7.25.9" "@babel/helper-validator-identifier" "^7.25.9"
"@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.3":
version "7.26.3"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.3.tgz#37e79830f04c2b5687acc77db97fbc75fb81f3c0"
integrity sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==
dependencies:
"@babel/helper-string-parser" "^7.25.9"
"@babel/helper-validator-identifier" "^7.25.9"
"@bcoe/v8-coverage@^0.2.3": "@bcoe/v8-coverage@^0.2.3":
version "0.2.3" version "0.2.3"
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
@ -1584,10 +1571,10 @@
dependencies: dependencies:
"@floating-ui/dom" "^1.0.0" "@floating-ui/dom" "^1.0.0"
"@floating-ui/react@^0.27.0": "@floating-ui/react@^0.26.24":
version "0.27.0" version "0.26.25"
resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.27.0.tgz#e0931fd09374ab4b8ce1a1af5cb44d1ccd1bb95a" resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.25.tgz#cf4c8a2b89fab1a71712d15e6551df3bfbd2ea1d"
integrity sha512-WLEksq7fJapXSJbmfiyq9pAW0a7ZFMEJToFE4oTDESxGjoa+nZu3YMjmZE2KvoUtQhqOK2yMMfWQFZyeWD0wGQ== integrity sha512-hZOmgN0NTOzOuZxI1oIrDu3Gcl8WViIkvPMpB4xdd4QD6xAMtwgwr3VPoiyH/bLtRcS1cDnhxLSD1NsMJmwh/A==
dependencies: dependencies:
"@floating-ui/react-dom" "^2.1.2" "@floating-ui/react-dom" "^2.1.2"
"@floating-ui/utils" "^0.2.8" "@floating-ui/utils" "^0.2.8"
@ -1608,37 +1595,36 @@
resolved "https://registry.yarnpkg.com/@fontsource/inter/-/inter-5.1.0.tgz#ab629b2c662457022d2d6a29854b8dc8ba538c47" resolved "https://registry.yarnpkg.com/@fontsource/inter/-/inter-5.1.0.tgz#ab629b2c662457022d2d6a29854b8dc8ba538c47"
integrity sha512-zKZR3kf1G0noIes1frLfOHP5EXVVm0M7sV/l9f/AaYf+M/DId35FO4LkigWjqWYjTJZGgplhdv4cB+ssvCqr5A== integrity sha512-zKZR3kf1G0noIes1frLfOHP5EXVVm0M7sV/l9f/AaYf+M/DId35FO4LkigWjqWYjTJZGgplhdv4cB+ssvCqr5A==
"@formatjs/ecma402-abstract@2.3.1": "@formatjs/ecma402-abstract@2.2.4":
version "2.3.1" version "2.2.4"
resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.1.tgz#cdeb3ffe1aeea9c4284b85b7e37e8e8615314c39" resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.2.4.tgz#355e42d375678229d46dc8ad7a7139520dd03e7b"
integrity sha512-Ip9uV+/MpLXWRk03U/GzeJMuPeOXpJBSB5V1tjA6kJhvqssye5J5LoYLc7Z5IAHb7nR62sRoguzrFiVCP/hnzw== integrity sha512-lFyiQDVvSbQOpU+WFd//ILolGj4UgA/qXrKeZxdV14uKiAUiPAtX6XAn7WBCRi7Mx6I7EybM9E5yYn4BIpZWYg==
dependencies: dependencies:
"@formatjs/fast-memoize" "2.2.5" "@formatjs/fast-memoize" "2.2.3"
"@formatjs/intl-localematcher" "0.5.9" "@formatjs/intl-localematcher" "0.5.8"
decimal.js "10"
tslib "2" tslib "2"
"@formatjs/fast-memoize@2.2.5": "@formatjs/fast-memoize@2.2.3":
version "2.2.5" version "2.2.3"
resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-2.2.5.tgz#54a4a1793d773b72c372d3dcab3595149aee7880" resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-2.2.3.tgz#74e64109279d5244f9fc281f3ae90c407cece823"
integrity sha512-6PoewUMrrcqxSoBXAOJDiW1m+AmkrAj0RiXnOMD59GRaswjXhm3MDhgepXPBgonc09oSirAJTsAggzAGQf6A6g== integrity sha512-3jeJ+HyOfu8osl3GNSL4vVHUuWFXR03Iz9jjgI7RwjG6ysu/Ymdr0JRCPHfF5yGbTE6JCrd63EpvX1/WybYRbA==
dependencies: dependencies:
tslib "2" tslib "2"
"@formatjs/intl-localematcher@0.5.9": "@formatjs/intl-localematcher@0.5.8":
version "0.5.9" version "0.5.8"
resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.5.9.tgz#43c6ee22be85b83340bcb09bdfed53657a2720db" resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.5.8.tgz#b11bbd04bd3551f7cadcb1ef1e231822d0e3c97e"
integrity sha512-8zkGu/sv5euxbjfZ/xmklqLyDGQSxsLqg8XOq88JW3cmJtzhCP8EtSJXlaKZnVO4beEaoiT9wj4eIoCQ9smwxA== integrity sha512-I+WDNWWJFZie+jkfkiK5Mp4hEDyRSEvmyfYadflOno/mmKJKcB17fEpEH0oJu/OWhhCJ8kJBDz2YMd/6cDl7Mg==
dependencies: dependencies:
tslib "2" tslib "2"
"@formatjs/intl-segmenter@^11.5.7": "@formatjs/intl-segmenter@^11.5.7":
version "11.7.7" version "11.7.4"
resolved "https://registry.yarnpkg.com/@formatjs/intl-segmenter/-/intl-segmenter-11.7.7.tgz#8a5aaa316e11ca2d31b99222e6fcf1ab539b085e" resolved "https://registry.yarnpkg.com/@formatjs/intl-segmenter/-/intl-segmenter-11.7.4.tgz#f99d87ee3f98515069285438a4913681fc243252"
integrity sha512-610J5xz5DxtEpa16zNR89CrvA9qWHxQFkUB3FKiGao0Nwn7i8cl+oyBhuH9SvtXF9j2LUOM9VMdVCMzJkVANNw== integrity sha512-pyHgFO86/CReKl20oK9jgaTMzSaG/nIMteMW8YuwUcS22EoMI1qbGTZ65oQ38KMT05SiHiMee2CP3WZvCi8YSQ==
dependencies: dependencies:
"@formatjs/ecma402-abstract" "2.3.1" "@formatjs/ecma402-abstract" "2.2.4"
"@formatjs/intl-localematcher" "0.5.9" "@formatjs/intl-localematcher" "0.5.8"
tslib "2" tslib "2"
"@humanwhocodes/config-array@^0.13.0": "@humanwhocodes/config-array@^0.13.0":
@ -1881,9 +1867,9 @@
chalk "^4.0.0" chalk "^4.0.0"
"@jridgewell/gen-mapping@^0.3.5": "@jridgewell/gen-mapping@^0.3.5":
version "0.3.8" version "0.3.5"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36"
integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA== integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==
dependencies: dependencies:
"@jridgewell/set-array" "^1.2.1" "@jridgewell/set-array" "^1.2.1"
"@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/sourcemap-codec" "^1.4.10"
@ -2433,10 +2419,10 @@
"@sentry/core" "8.40.0" "@sentry/core" "8.40.0"
"@sentry/types" "8.40.0" "@sentry/types" "8.40.0"
"@sentry/babel-plugin-component-annotate@2.22.7": "@sentry/babel-plugin-component-annotate@2.22.6":
version "2.22.7" version "2.22.6"
resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-2.22.7.tgz#604c7e33d48528a13477e7af597c4d5fca51b8bd" resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-2.22.6.tgz#829d6caf2c95c1c46108336de4e1049e6521435e"
integrity sha512-aa7XKgZMVl6l04NY+3X7BP7yvQ/s8scn8KzQfTLrGRarziTlMGrsCOBQtCNWXOPEbtxAIHpZ9dsrAn5EJSivOQ== integrity sha512-V2g1Y1I5eSe7dtUVMBvAJr8BaLRr4CLrgNgtPaZyMT4Rnps82SrZ5zqmEkLXPumlXhLUWR6qzoMNN2u+RXVXfQ==
"@sentry/browser@^8.0.0": "@sentry/browser@^8.0.0":
version "8.40.0" version "8.40.0"
@ -2450,14 +2436,14 @@
"@sentry/core" "8.40.0" "@sentry/core" "8.40.0"
"@sentry/types" "8.40.0" "@sentry/types" "8.40.0"
"@sentry/bundler-plugin-core@2.22.7": "@sentry/bundler-plugin-core@2.22.6":
version "2.22.7" version "2.22.6"
resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-2.22.7.tgz#28204a224cd1fef58d157e5beeb2493947a9bc35" resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-2.22.6.tgz#a1ea1fd43700a3ece9e7db016997e79a2782b87d"
integrity sha512-ouQh5sqcB8vsJ8yTTe0rf+iaUkwmeUlGNFi35IkCFUQlWJ22qS6OfvNjOqFI19e6eGUXks0c/2ieFC4+9wJ+1g== integrity sha512-1esQdgSUCww9XAntO4pr7uAM5cfGhLsgTK9MEwAKNfvpMYJi9NUTYa3A7AZmdA8V6107Lo4OD7peIPrDRbaDCg==
dependencies: dependencies:
"@babel/core" "^7.18.5" "@babel/core" "^7.18.5"
"@sentry/babel-plugin-component-annotate" "2.22.7" "@sentry/babel-plugin-component-annotate" "2.22.6"
"@sentry/cli" "2.39.1" "@sentry/cli" "^2.36.1"
dotenv "^16.3.1" dotenv "^16.3.1"
find-up "^5.0.0" find-up "^5.0.0"
glob "^9.3.2" glob "^9.3.2"
@ -2499,7 +2485,7 @@
resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.39.1.tgz#1a874a5570c6d162b35d9d001c96e5389d07d2cb" resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.39.1.tgz#1a874a5570c6d162b35d9d001c96e5389d07d2cb"
integrity sha512-xv0R2CMf/X1Fte3cMWie1NXuHmUyQPDBfCyIt6k6RPFPxAYUgcqgMPznYwVMwWEA1W43PaOkSn3d8ZylsDaETw== integrity sha512-xv0R2CMf/X1Fte3cMWie1NXuHmUyQPDBfCyIt6k6RPFPxAYUgcqgMPznYwVMwWEA1W43PaOkSn3d8ZylsDaETw==
"@sentry/cli@2.39.1": "@sentry/cli@^2.36.1":
version "2.39.1" version "2.39.1"
resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.39.1.tgz#916bb5b7567ccf7fdf94ef6cf8a2b9ab78370d29" resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.39.1.tgz#916bb5b7567ccf7fdf94ef6cf8a2b9ab78370d29"
integrity sha512-JIb3e9vh0+OmQ0KxmexMXg9oZsR/G7HMwxt5BUIKAXZ9m17Xll4ETXTRnRUBT3sf7EpNGAmlQk1xEmVN9pYZYQ== integrity sha512-JIb3e9vh0+OmQ0KxmexMXg9oZsR/G7HMwxt5BUIKAXZ9m17Xll4ETXTRnRUBT3sf7EpNGAmlQk1xEmVN9pYZYQ==
@ -2531,11 +2517,11 @@
integrity sha512-nuCf3U3deolPM9BjNnwCc33UtFl9ec15/r74ngAkNccn+A2JXdIAsDkGJMO/9mgSFykLe1QyeJ0pQFRisCGOiA== integrity sha512-nuCf3U3deolPM9BjNnwCc33UtFl9ec15/r74ngAkNccn+A2JXdIAsDkGJMO/9mgSFykLe1QyeJ0pQFRisCGOiA==
"@sentry/webpack-plugin@^2.7.1": "@sentry/webpack-plugin@^2.7.1":
version "2.22.7" version "2.22.6"
resolved "https://registry.yarnpkg.com/@sentry/webpack-plugin/-/webpack-plugin-2.22.7.tgz#992c6c782c736f22e72eb318745e28cc24aabad7" resolved "https://registry.yarnpkg.com/@sentry/webpack-plugin/-/webpack-plugin-2.22.6.tgz#8c9d27d5cd89153a5b6e08cc9dcb3048b122ffbc"
integrity sha512-j5h5LZHWDlm/FQCCmEghQ9FzYXwfZdlOf3FE/X6rK6lrtx0JCAkq+uhMSasoyP4XYKL4P4vRS6WFSos4jxf/UA== integrity sha512-BiLhAzQYAz/9kCXKj2LeUKWf/9GBVn2dD0DeYK89s+sjDEaxjbcLBBiLlLrzT7eC9QVj2tUZRKOi6puCfc8ysw==
dependencies: dependencies:
"@sentry/bundler-plugin-core" "2.22.7" "@sentry/bundler-plugin-core" "2.22.6"
unplugin "1.0.1" unplugin "1.0.1"
uuid "^9.0.0" uuid "^9.0.0"
@ -3048,9 +3034,9 @@
"@types/node" "*" "@types/node" "*"
"@types/jsrsasign@^10.5.4": "@types/jsrsasign@^10.5.4":
version "10.5.15" version "10.5.14"
resolved "https://registry.yarnpkg.com/@types/jsrsasign/-/jsrsasign-10.5.15.tgz#5cf1ee506b2fa2435b6e1786a873285c7110eb82" resolved "https://registry.yarnpkg.com/@types/jsrsasign/-/jsrsasign-10.5.14.tgz#61d1dbd791ecd11db556c1ca5d82453fc7207338"
integrity sha512-3stUTaSRtN09PPzVWR6aySD9gNnuymz+WviNHoTb85dKu+BjaV4uBbWWGykBBJkfwPtcNZVfTn2lbX00U+yhpQ== integrity sha512-lppSlfK6etu+cuKs40K4rg8As79PH6hzIB+v55zSqImbSH3SE6Fm8MBHCiI91cWlAP3Z4igtJK1VL3fSN09blQ==
"@types/katex@^0.16.0": "@types/katex@^0.16.0":
version "0.16.7" version "0.16.7"
@ -3114,9 +3100,9 @@
undici-types "~6.20.0" undici-types "~6.20.0"
"@types/node@18": "@types/node@18":
version "18.19.68" version "18.19.66"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.68.tgz#f4f10d9927a7eaf3568c46a6d739cc0967ccb701" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.66.tgz#0937a47904ceba5994eedf5cf4b6d503d8d6136c"
integrity sha512-QGtpFH1vB99ZmTa63K4/FU8twThj4fuVSBkGddTp7uIL/cuoLWIUSL2RcOaigBhfR+hg5pgGkBnkoOxrTVBMKw== integrity sha512-14HmtUdGxFUalGRfLLn9Gc1oNWvWh5zNbsyOLo5JV6WARSeN1QcEBKRnZm9QqNfrutgsl/hY4eJW63aZ44aBCg==
dependencies: dependencies:
undici-types "~5.26.4" undici-types "~5.26.4"
@ -3401,12 +3387,12 @@
resolved "https://registry.yarnpkg.com/@vector-im/compound-design-tokens/-/compound-design-tokens-2.1.1.tgz#d6175a99fe4b97688464126f255386990f3048d6" resolved "https://registry.yarnpkg.com/@vector-im/compound-design-tokens/-/compound-design-tokens-2.1.1.tgz#d6175a99fe4b97688464126f255386990f3048d6"
integrity sha512-QnUi2K14D9KTXxcLQKUU3V75cforZLMwhaaJDNftT8F5mG86950hAM+qhgDNEpEU+pkTffQj0/g/5859YmqWzQ== integrity sha512-QnUi2K14D9KTXxcLQKUU3V75cforZLMwhaaJDNftT8F5mG86950hAM+qhgDNEpEU+pkTffQj0/g/5859YmqWzQ==
"@vector-im/compound-web@^7.5.0": "@vector-im/compound-web@^7.4.0":
version "7.5.0" version "7.4.0"
resolved "https://registry.yarnpkg.com/@vector-im/compound-web/-/compound-web-7.5.0.tgz#1547af5f0ee27b94f79ab11eee006059f3d09707" resolved "https://registry.yarnpkg.com/@vector-im/compound-web/-/compound-web-7.4.0.tgz#a5af8af6346f8ff6c14c70f5d4eb2eab7357a7cc"
integrity sha512-Xhef8H5WrRmPuanzRBs8rnl+hwbcQnC7nKSCupUczAQ5hjlieBx4vcQYQ/nMkrs4rMGjgfFtR3E18wT5LlML/A== integrity sha512-ZRBUeEGNmj/fTkIRa8zGnyVN7ytowpfOtHChqNm+m/+OTJN3o/lOMuQHDV8jeSEW2YwPJqGvPuG/dRr89IcQkA==
dependencies: dependencies:
"@floating-ui/react" "^0.27.0" "@floating-ui/react" "^0.26.24"
"@radix-ui/react-context-menu" "^2.2.1" "@radix-ui/react-context-menu" "^2.2.1"
"@radix-ui/react-dropdown-menu" "^2.1.1" "@radix-ui/react-dropdown-menu" "^2.1.1"
"@radix-ui/react-form" "^0.1.0" "@radix-ui/react-form" "^0.1.0"
@ -3424,7 +3410,7 @@
dependencies: dependencies:
eslint-plugin-unicorn "^54.0.0" eslint-plugin-unicorn "^54.0.0"
"@webassemblyjs/ast@1.14.1", "@webassemblyjs/ast@^1.14.1": "@webassemblyjs/ast@1.14.1", "@webassemblyjs/ast@^1.12.1":
version "1.14.1" version "1.14.1"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.14.1.tgz#a9f6a07f2b03c95c8d38c4536a1fdfb521ff55b6" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.14.1.tgz#a9f6a07f2b03c95c8d38c4536a1fdfb521ff55b6"
integrity sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ== integrity sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==
@ -3490,7 +3476,7 @@
resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.13.2.tgz#917a20e93f71ad5602966c2d685ae0c6c21f60f1" resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.13.2.tgz#917a20e93f71ad5602966c2d685ae0c6c21f60f1"
integrity sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ== integrity sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==
"@webassemblyjs/wasm-edit@^1.14.1": "@webassemblyjs/wasm-edit@^1.12.1":
version "1.14.1" version "1.14.1"
resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz#ac6689f502219b59198ddec42dcd496b1004d597" resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz#ac6689f502219b59198ddec42dcd496b1004d597"
integrity sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ== integrity sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==
@ -3525,7 +3511,7 @@
"@webassemblyjs/wasm-gen" "1.14.1" "@webassemblyjs/wasm-gen" "1.14.1"
"@webassemblyjs/wasm-parser" "1.14.1" "@webassemblyjs/wasm-parser" "1.14.1"
"@webassemblyjs/wasm-parser@1.14.1", "@webassemblyjs/wasm-parser@^1.14.1": "@webassemblyjs/wasm-parser@1.14.1", "@webassemblyjs/wasm-parser@^1.12.1":
version "1.14.1" version "1.14.1"
resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz#b3e13f1893605ca78b52c68e54cf6a865f90b9fb" resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz#b3e13f1893605ca78b52c68e54cf6a865f90b9fb"
integrity sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ== integrity sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==
@ -4211,15 +4197,7 @@ bytes@3.1.2:
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
call-bind-apply-helpers@^1.0.0: call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7:
version "1.0.1"
resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz#32e5892e6361b29b0b545ba6f7763378daca2840"
integrity sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==
dependencies:
es-errors "^1.3.0"
function-bind "^1.1.2"
call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6:
version "1.0.7" version "1.0.7"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9"
integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==
@ -4230,16 +4208,6 @@ call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6:
get-intrinsic "^1.2.4" get-intrinsic "^1.2.4"
set-function-length "^1.2.1" set-function-length "^1.2.1"
call-bind@^1.0.7:
version "1.0.8"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c"
integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==
dependencies:
call-bind-apply-helpers "^1.0.0"
es-define-property "^1.0.0"
get-intrinsic "^1.2.4"
set-function-length "^1.2.2"
callsites@^3.0.0: callsites@^3.0.0:
version "3.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
@ -4821,11 +4789,11 @@ css-tree@^2.3.1:
source-map-js "^1.0.1" source-map-js "^1.0.1"
css-tree@^3.0.0, css-tree@^3.0.1: css-tree@^3.0.0, css-tree@^3.0.1:
version "3.1.0" version "3.0.1"
resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-3.1.0.tgz#7aabc035f4e66b5c86f54570d55e05b1346eb0fd" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-3.0.1.tgz#bea6deaea60bb5bcf416adfb1ecf607a8d9471f6"
integrity sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w== integrity sha512-8Fxxv+tGhORlshCdCwnNJytvlvq46sOLSYEx2ZIGurahWvMucSRnyjPA3AmrMq4VPRYbHVpWj5VkiVasrM2H4Q==
dependencies: dependencies:
mdn-data "2.12.2" mdn-data "2.12.1"
source-map-js "^1.0.1" source-map-js "^1.0.1"
css-tree@~2.2.0: css-tree@~2.2.0:
@ -5002,10 +4970,10 @@ debug@2.6.9:
dependencies: dependencies:
ms "2.0.0" ms "2.0.0"
debug@4, debug@^4.1.0, debug@^4.3.1: debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.7, debug@~4.3.6:
version "4.4.0" version "4.3.7"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52"
integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
dependencies: dependencies:
ms "^2.1.3" ms "^2.1.3"
@ -5016,19 +4984,12 @@ debug@^3.2.7:
dependencies: dependencies:
ms "^2.1.1" ms "^2.1.1"
debug@^4.1.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.7, debug@~4.3.6:
version "4.3.7"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52"
integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
dependencies:
ms "^2.1.3"
decamelize@^1.2.0: decamelize@^1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
decimal.js@10, decimal.js@^10.4.2: decimal.js@^10.4.2:
version "10.4.3" version "10.4.3"
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23"
integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==
@ -5280,18 +5241,9 @@ dot-case@^3.0.4:
tslib "^2.0.3" tslib "^2.0.3"
dotenv@^16.0.2, dotenv@^16.3.1: dotenv@^16.0.2, dotenv@^16.3.1:
version "16.4.7" version "16.4.5"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f"
integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ== integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==
dunder-proto@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.0.tgz#c2fce098b3c8f8899554905f4377b6d85dabaa80"
integrity sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==
dependencies:
call-bind-apply-helpers "^1.0.0"
es-errors "^1.3.0"
gopd "^1.2.0"
duplexer@^0.1.2: duplexer@^0.1.2:
version "0.1.2" version "0.1.2"
@ -5330,9 +5282,9 @@ ejs@^3.1.8:
jake "^10.8.5" jake "^10.8.5"
electron-to-chromium@^1.5.41: electron-to-chromium@^1.5.41:
version "1.5.72" version "1.5.67"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.72.tgz#a732805986d3a5b5fedd438ddf4616c7d78ac2df" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.67.tgz#66ebd2be4a77469ac2760ef5e9e460ba9a43a845"
integrity sha512-ZpSAUOZ2Izby7qnZluSrAlGgGQzucmFbN0n64dYzocYxnxV5ufurpj3VgEe4cUp7ir9LmeLxNYo8bVnlM8bQHw== integrity sha512-nz88NNBsD7kQSAGGJyp8hS6xSPtWwqNogA0mjtc2nUYeEf3nURK9qpV18TuBdDmEDgVWotS8Wkzf+V52dSQ/LQ==
emittery@^0.13.1: emittery@^0.13.1:
version "0.13.1" version "0.13.1"
@ -5481,10 +5433,12 @@ es-abstract@^1.17.5, es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23
unbox-primitive "^1.0.2" unbox-primitive "^1.0.2"
which-typed-array "^1.1.15" which-typed-array "^1.1.15"
es-define-property@^1.0.0, es-define-property@^1.0.1: es-define-property@^1.0.0:
version "1.0.1" version "1.0.0"
resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845"
integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==
dependencies:
get-intrinsic "^1.2.4"
es-errors@^1.2.1, es-errors@^1.3.0: es-errors@^1.2.1, es-errors@^1.3.0:
version "1.3.0" version "1.3.0"
@ -5967,9 +5921,9 @@ expect@^29.0.0, expect@^29.7.0:
jest-util "^29.7.0" jest-util "^29.7.0"
express@^4.18.2, express@^4.19.2: express@^4.18.2, express@^4.19.2:
version "4.21.2" version "4.21.1"
resolved "https://registry.yarnpkg.com/express/-/express-4.21.2.tgz#cf250e48362174ead6cea4a566abef0162c1ec32" resolved "https://registry.yarnpkg.com/express/-/express-4.21.1.tgz#9dae5dda832f16b4eec941a4e44aa89ec481b281"
integrity sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA== integrity sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==
dependencies: dependencies:
accepts "~1.3.8" accepts "~1.3.8"
array-flatten "1.1.1" array-flatten "1.1.1"
@ -5990,7 +5944,7 @@ express@^4.18.2, express@^4.19.2:
methods "~1.1.2" methods "~1.1.2"
on-finished "2.4.1" on-finished "2.4.1"
parseurl "~1.3.3" parseurl "~1.3.3"
path-to-regexp "0.1.12" path-to-regexp "0.1.10"
proxy-addr "~2.0.7" proxy-addr "~2.0.7"
qs "6.13.0" qs "6.13.0"
range-parser "~1.2.1" range-parser "~1.2.1"
@ -6351,7 +6305,7 @@ get-east-asian-width@^1.0.0:
resolved "https://registry.yarnpkg.com/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz#21b4071ee58ed04ee0db653371b55b4299875389" resolved "https://registry.yarnpkg.com/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz#21b4071ee58ed04ee0db653371b55b4299875389"
integrity sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ== integrity sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==
get-intrinsic@^1.2.1, get-intrinsic@^1.2.3: get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4:
version "1.2.4" version "1.2.4"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd"
integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==
@ -6362,20 +6316,6 @@ get-intrinsic@^1.2.1, get-intrinsic@^1.2.3:
has-symbols "^1.0.3" has-symbols "^1.0.3"
hasown "^2.0.0" hasown "^2.0.0"
get-intrinsic@^1.2.4:
version "1.2.5"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.5.tgz#dfe7dd1b30761b464fe51bf4bb00ac7c37b681e7"
integrity sha512-Y4+pKa7XeRUPWFNvOOYHkRYrfzW07oraURSvjDmRVOJ748OrVmeXtpE4+GCEHncjCjkTxPNRt8kEbxDhsn6VTg==
dependencies:
call-bind-apply-helpers "^1.0.0"
dunder-proto "^1.0.0"
es-define-property "^1.0.1"
es-errors "^1.3.0"
function-bind "^1.1.2"
gopd "^1.2.0"
has-symbols "^1.1.0"
hasown "^2.0.2"
get-nonce@^1.0.0: get-nonce@^1.0.0:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3" resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3"
@ -6552,12 +6492,7 @@ globjoin@^0.1.4:
resolved "https://registry.yarnpkg.com/globjoin/-/globjoin-0.1.4.tgz#2f4494ac8919e3767c5cbb691e9f463324285d43" resolved "https://registry.yarnpkg.com/globjoin/-/globjoin-0.1.4.tgz#2f4494ac8919e3767c5cbb691e9f463324285d43"
integrity sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg== integrity sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==
gopd@^1.0.1, gopd@^1.2.0: gopd@^1.0.1, gopd@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1"
integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
gopd@^1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.1.0.tgz#df8f0839c2d48caefc32a025a49294d39606c912" resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.1.0.tgz#df8f0839c2d48caefc32a025a49294d39606c912"
integrity sha512-FQoVQnqcdk4hVM4JN1eromaun4iuS34oStkdlLENLdpULsuQcTyXj8w7ayhuUfPwEYZ1ZOooOTT6fdA9Vmx/RA== integrity sha512-FQoVQnqcdk4hVM4JN1eromaun4iuS34oStkdlLENLdpULsuQcTyXj8w7ayhuUfPwEYZ1ZOooOTT6fdA9Vmx/RA==
@ -6608,30 +6543,18 @@ has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2:
dependencies: dependencies:
es-define-property "^1.0.0" es-define-property "^1.0.0"
has-proto@^1.0.1: has-proto@^1.0.1, has-proto@^1.0.3:
version "1.2.0"
resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.2.0.tgz#5de5a6eabd95fdffd9818b43055e8065e39fe9d5"
integrity sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==
dependencies:
dunder-proto "^1.0.0"
has-proto@^1.0.3:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.1.0.tgz#deb10494cbbe8809bce168a3b961f42969f5ed43" resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.1.0.tgz#deb10494cbbe8809bce168a3b961f42969f5ed43"
integrity sha512-QLdzI9IIO1Jg7f9GT1gXpPpXArAn6cS31R1eEZqz08Gc+uQ8/XiqHWt17Fiw+2p6oTTIq5GXEpQkAlA88YRl/Q== integrity sha512-QLdzI9IIO1Jg7f9GT1gXpPpXArAn6cS31R1eEZqz08Gc+uQ8/XiqHWt17Fiw+2p6oTTIq5GXEpQkAlA88YRl/Q==
dependencies: dependencies:
call-bind "^1.0.7" call-bind "^1.0.7"
has-symbols@^1.0.2: has-symbols@^1.0.2, has-symbols@^1.0.3:
version "1.0.3" version "1.0.3"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
has-symbols@^1.0.3, has-symbols@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338"
integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==
has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: has-tostringtag@^1.0.0, has-tostringtag@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc"
@ -7829,21 +7752,16 @@ jsdom@^20.0.0:
ws "^8.11.0" ws "^8.11.0"
xml-name-validator "^4.0.0" xml-name-validator "^4.0.0"
jsesc@^3.0.2: jsesc@^3.0.2, jsesc@~3.0.2:
version "3.1.0" version "3.0.2"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e"
integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==
jsesc@~0.5.0: jsesc@~0.5.0:
version "0.5.0" version "0.5.0"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==
jsesc@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e"
integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==
json-buffer@3.0.1: json-buffer@3.0.1:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
@ -7960,9 +7878,9 @@ kleur@^3.0.3:
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
knip@^5.36.2: knip@^5.36.2:
version "5.39.2" version "5.38.3"
resolved "https://registry.yarnpkg.com/knip/-/knip-5.39.2.tgz#1faacd8d8ef36b509b2f6e396cce85b645abb04e" resolved "https://registry.yarnpkg.com/knip/-/knip-5.38.3.tgz#a139e0c6215c9958d213ce1a9e3985cf5de2774e"
integrity sha512-BuvuWRllLWV/r2G4m9ggNH+DZ6gouP/dhtJPXVlMbWNF++w9/EfrF6k2g7YBKCwjzCC+PXmYtpH8S2t8RjnY4Q== integrity sha512-pg3CMzWlZy4mnuwxieGoK74oOgzFPvsUR/aE8NSqx2oQr56soXTzmw8GsHR277pU52Fe0h4/pho2PMhVeEvj8g==
dependencies: dependencies:
"@nodelib/fs.walk" "1.2.8" "@nodelib/fs.walk" "1.2.8"
"@snyk/github-codeowners" "1.1.0" "@snyk/github-codeowners" "1.1.0"
@ -8368,7 +8286,12 @@ mdn-data@2.0.30:
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc"
integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA== integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==
mdn-data@2.12.2, mdn-data@^2.12.2: mdn-data@2.12.1:
version "2.12.1"
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.12.1.tgz#10cb462215c13d95c92ff60d0fb3becac1bbb924"
integrity sha512-rsfnCbOHjqrhWxwt5/wtSLzpoKTzW7OXdT5lLOIH1OTYhWu9rRJveGq0sKvDZODABH7RX+uoR+DYcpFnq4Tf6Q==
mdn-data@^2.12.2:
version "2.12.2" version "2.12.2"
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.12.2.tgz#9ae6c41a9e65adf61318b32bff7b64fbfb13f8cf" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.12.2.tgz#9ae6c41a9e65adf61318b32bff7b64fbfb13f8cf"
integrity sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA== integrity sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==
@ -8652,9 +8575,9 @@ node-int64@^0.4.0:
integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==
node-releases@^2.0.18: node-releases@^2.0.18:
version "2.0.19" version "2.0.18"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f"
integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==
normalize-package-data@^2.5.0: normalize-package-data@^2.5.0:
version "2.5.0" version "2.5.0"
@ -9040,10 +8963,10 @@ path-scurry@^2.0.0:
lru-cache "^11.0.0" lru-cache "^11.0.0"
minipass "^7.1.2" minipass "^7.1.2"
path-to-regexp@0.1.12: path-to-regexp@0.1.10:
version "0.1.12" version "0.1.10"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b"
integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ== integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==
path-to-regexp@^2.2.1: path-to-regexp@^2.2.1:
version "2.4.0" version "2.4.0"
@ -9781,10 +9704,10 @@ prelude-ls@^1.2.1:
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
prettier@3.4.2: prettier@3.4.1:
version "3.4.2" version "3.4.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.4.2.tgz#a5ce1fb522a588bf2b78ca44c6e6fe5aa5a2b13f" resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.4.1.tgz#e211d451d6452db0a291672ca9154bc8c2579f7b"
integrity sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ== integrity sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg==
pretty-error@^4.0.0: pretty-error@^4.0.0:
version "4.0.0" version "4.0.0"
@ -10597,7 +10520,7 @@ set-blocking@^2.0.0:
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==
set-function-length@^1.2.1, set-function-length@^1.2.2: set-function-length@^1.2.1:
version "1.2.2" version "1.2.2"
resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449"
integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==
@ -11257,7 +11180,7 @@ terser-webpack-plugin@^5.3.10, terser-webpack-plugin@^5.3.9:
serialize-javascript "^6.0.1" serialize-javascript "^6.0.1"
terser "^5.26.0" terser "^5.26.0"
terser@^5.10.0: terser@^5.10.0, terser@^5.26.0:
version "5.36.0" version "5.36.0"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.36.0.tgz#8b0dbed459ac40ff7b4c9fd5a3a2029de105180e" resolved "https://registry.yarnpkg.com/terser/-/terser-5.36.0.tgz#8b0dbed459ac40ff7b4c9fd5a3a2029de105180e"
integrity sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w== integrity sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==
@ -11267,16 +11190,6 @@ terser@^5.10.0:
commander "^2.20.0" commander "^2.20.0"
source-map-support "~0.5.20" source-map-support "~0.5.20"
terser@^5.26.0:
version "5.37.0"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.37.0.tgz#38aa66d1cfc43d0638fab54e43ff8a4f72a21ba3"
integrity sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==
dependencies:
"@jridgewell/source-map" "^0.3.3"
acorn "^8.8.2"
commander "^2.20.0"
source-map-support "~0.5.20"
test-exclude@^6.0.0: test-exclude@^6.0.0:
version "6.0.0" version "6.0.0"
resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e"
@ -11972,15 +11885,15 @@ webpack-virtual-modules@^0.5.0:
integrity sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw== integrity sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==
webpack@^5.89.0: webpack@^5.89.0:
version "5.97.1" version "5.96.1"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.97.1.tgz#972a8320a438b56ff0f1d94ade9e82eac155fa58" resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.96.1.tgz#3676d1626d8312b6b10d0c18cc049fba7ac01f0c"
integrity sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg== integrity sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA==
dependencies: dependencies:
"@types/eslint-scope" "^3.7.7" "@types/eslint-scope" "^3.7.7"
"@types/estree" "^1.0.6" "@types/estree" "^1.0.6"
"@webassemblyjs/ast" "^1.14.1" "@webassemblyjs/ast" "^1.12.1"
"@webassemblyjs/wasm-edit" "^1.14.1" "@webassemblyjs/wasm-edit" "^1.12.1"
"@webassemblyjs/wasm-parser" "^1.14.1" "@webassemblyjs/wasm-parser" "^1.12.1"
acorn "^8.14.0" acorn "^8.14.0"
browserslist "^4.24.0" browserslist "^4.24.0"
chrome-trace-event "^1.0.2" chrome-trace-event "^1.0.2"
@ -12319,6 +12232,6 @@ zod-validation-error@^3.0.3:
integrity sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ== integrity sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ==
zod@^3.22.4: zod@^3.22.4:
version "3.24.0" version "3.23.8"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.0.tgz#babb32313f7c5f4a99812feee806d186b4f76bde" resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d"
integrity sha512-Hz+wiY8yD0VLA2k/+nsg2Abez674dDGTai33SwNvMPuf9uIrBC9eFgIMQxBBbHFxVXi8W+5nX9DcAh9YNSQm/w== integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==