Compare commits

..

11 Commits

Author SHA1 Message Date
Erik Johnston
4a66a22c14 Join policy server PoC 2025-03-22 18:53:11 +00:00
ElementRobot
64e2a843c3 [create-pull-request] automated change (#29574)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-03-22 06:15:38 +00:00
Julien CLEMENT
fba59381a0 Generate/load pickle key on SSO (#29568)
* Generate/load pickle key when logged in with SSO

* add comments

* Refactor pickle key loading/creation

* Coding style fixes and fix racy loadOrCreatePickleKey

* fix outdated documentation comment

* fix prettier

Signed-off-by: Julien CLEMENT <julien.clement@epita.fr>

---------

Signed-off-by: Julien CLEMENT <julien.clement@epita.fr>
2025-03-21 19:10:34 +00:00
Will Hunt
e1970df704 Add report room dialog button/dialog. (#29513)
* Add report room dialog button/dialog.

* Update copy

* fixup tests / lint

* Fix title in test.

* update snapshot

* Add unit tests for dialog

* lint
2025-03-21 17:08:37 +00:00
R Midhun Suresh
b54122884c RoomListViewModel: Make the active room sticky in the list (#29551)
* Add new hook for sticky room

This hook takes the filtered, sorted rooms and returns a new list of
rooms such that the active room is kept in the same index even when the
list has changes.

* Use new hook in view model

* Add tests

* Use single * in comments
2025-03-21 12:11:59 +00:00
Michael Telatynski
0d28df0f67 Reuse PushProcessor from MatrixClient (#29561)
* Reuse PushProcessor from MatrixClient

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Reuse PushProcessor getPushRuleGlobRegex

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* delint

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update regex handling

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2025-03-21 11:34:06 +00:00
R Midhun Suresh
3a39486468 RoomListViewModel: Reset any primary filter on secondary filter change (#29562)
* Reset primary filter when secondary filter is applied

* Add test
2025-03-21 10:46:20 +00:00
R Midhun Suresh
0dc295e3b8 RoomListStore: Unread filter should only filter rooms having unread counts (#29555)
* Use `hasUnreadCount` instead of `isUnread`

* Fix broken test

* Write test
2025-03-21 08:28:00 +00:00
ElementRobot
5a6c9a4c9a [create-pull-request] automated change (#29559)
Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com>
2025-03-21 06:22:19 +00:00
Will Hunt
599112e122 Replace checkboxes with Compound checkboxes, and appropriately label each checkbox. (#29363)
* Fix labelling of avatar menu

* Make the integration manager toggle more clear.

* fix label

* lint

* Update snapshots.

* Refactor many cases of checkbox to use the new compound component.

* Remove non-checkbox related changes

* Reset some things

* Remove usages of mx_checkbox* styling.

* Use label locators for apperance tests.

* small linter tweaks

* lint

* update screenshot

* Test updates

* lint

* Realign checkboxes for device selection.

* Fixup QuickSettings styling

* remove comment

* lint

* flex comment

* remove unused label

* remove redundant classes

* add test for spaces

* lint

* Copyright

* fixup spaces test

* spaces lint

* Replace pin with compound pin.

* Realign icons

* Remove hack for colouring icons

* Adjust existing rooms component to correctly label room.

* Add test for adding an existing room to an existing space.

* Set deterministic sort order for rooms

* lint
2025-03-20 15:35:54 +00:00
Andy Balaam
170dcd1c0e In force-verify mode, prevent bypassing by cancelling device verification (#29487)
* In force-verify mode, prevent bypassing by cancelling device verification

* Don't show the after-login screen if we are racing with forced verification

* Unit test for not bypassing verification by cancelling device verify
2025-03-20 15:10:08 +00:00
85 changed files with 3566 additions and 1800 deletions

View File

@@ -93,4 +93,41 @@ test.describe("Room list", () => {
await filters.getByRole("option", { name: "People" }).click();
await expect(roomListView.getByRole("gridcell", { name: "Open room room0" })).toBeVisible();
});
test("unread filter should only match unread rooms that have a count", async ({ page, app, bot }) => {
const roomListView = getRoomList(page);
// Let's create a new room and invite the bot
const room1Id = await app.client.createRoom({
name: "Unread Room 1",
invite: [bot.credentials?.userId],
});
await bot.awaitRoomMembership(room1Id);
// Let's create another room as well
const room2Id = await app.client.createRoom({
name: "Unread Room 2",
invite: [bot.credentials?.userId],
});
await bot.awaitRoomMembership(room2Id);
// Let's configure unread room 1 so that we only get notification for mentions and keywords
await app.viewRoomById(room1Id);
await app.settings.openRoomSettings("Notifications");
await page.getByText("@mentions & keywords").click();
await app.settings.closeDialog();
// Let's open a room other than room 1 or room 2
await roomListView.getByRole("gridcell", { name: "Open room room29" }).click();
// Let's make the bot send a new message in both room 1 and room 2
await bot.sendMessage(room1Id, "Hello!");
await bot.sendMessage(room2Id, "Hello!");
// Let's activate the unread filter now
await page.getByRole("option", { name: "Unread" }).click();
// Unread filter should only show room 2!!
await expect(roomListView.getByRole("gridcell", { name: "Open room Unread Room 2" })).toBeVisible();
await expect(roomListView.getByRole("gridcell", { name: "Open room Unread Room 1" })).not.toBeVisible();
});
});

View File

@@ -13,6 +13,7 @@ import { selectHomeserver } from "../utils";
import { type Credentials, type HomeserverInstance } from "../../plugins/homeserver";
import { consentHomeserver } from "../../plugins/homeserver/synapse/consentHomeserver.ts";
import { isDendrite } from "../../plugins/homeserver/dendrite";
import { createBot } from "../crypto/utils.ts";
// This test requires fixed credentials for the device signing keys below to work
const username = "user1234";
@@ -258,6 +259,34 @@ test.describe("Login", () => {
await expect(h1.locator(".mx_CompleteSecurity_skip")).toHaveCount(0);
});
test("Continues to show verification prompt after cancelling device verification", async ({
page,
homeserver,
credentials,
}) => {
// Create a different device which is cross-signed, meaning we need to verify this device
await createBot(page, homeserver, credentials, true);
// Wait to avoid homeserver rate limit on logins
await page.waitForTimeout(100);
// Load the page and see that we are asked to verify
await page.goto("/#/welcome");
await login(page, homeserver, credentials);
let h1 = page.getByRole("heading", { name: "Verify this device", level: 1 });
await expect(h1).toBeVisible();
// Click "Verify with another device"
await page.getByRole("button", { name: "Verify with another device" }).click();
// Cancel the new dialog
await page.getByRole("button", { name: "Close dialog" }).click();
// Check that we are still being asked to verify
h1 = page.getByRole("heading", { name: "Verify this device", level: 1 });
await expect(h1).toBeVisible();
});
});
});
});

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024, 2025 New Vector Ltd.
Copyright 2022 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -10,6 +10,7 @@ import { type Locator, type Page } from "@playwright/test";
import { test, expect } from "../../element-web-test";
import { checkRoomSummaryCard, viewRoomSummaryByName } from "./utils";
import { isDendrite } from "../../plugins/homeserver/dendrite";
const ROOM_NAME = "Test room";
const ROOM_NAME_LONG =
@@ -133,6 +134,17 @@ test.describe("RightPanel", () => {
await page.getByLabel("Room info").nth(1).click();
await checkRoomSummaryCard(page, ROOM_NAME);
});
test.describe("room reporting", () => {
test.skip(isDendrite, "Dendrite does not implement room reporting");
test("should handle reporting a room", async ({ page, app }) => {
await viewRoomSummaryByName(page, app, ROOM_NAME);
await page.getByRole("menuitem", { name: "Report room" }).click();
const dialog = await page.getByRole("dialog", { name: "Report Room" });
await dialog.getByLabel("reason").fill("This room should be reported");
await dialog.getByRole("button", { name: "Send report" }).click();
await expect(page.getByText("Your report was sent.")).toBeVisible();
});
});
});
test.describe("in spaces", () => {

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024,2025 New Vector Ltd.
Copyright 2023 Suguru Hirahara
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -50,8 +50,8 @@ test.describe("Appearance user settings tab", () => {
// Click "Show advanced" link button
await tab.getByRole("button", { name: "Show advanced" }).click();
await tab.locator(".mx_Checkbox", { hasText: "Use bundled emoji font" }).click();
await tab.locator(".mx_Checkbox", { hasText: "Use a system font" }).click();
await tab.getByLabel("Use bundled emoji font").click();
await tab.getByLabel("Use a system font").click();
// Assert that the font-family value was removed
await expect(page.locator("body")).toHaveCSS("font-family", '""');

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024,2025 New Vector Ltd.
Copyright 2022 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -35,17 +35,18 @@ function spaceCreateOptions(spaceName: string, roomIds: string[] = []): ICreateR
name: spaceName,
},
},
...roomIds.map(spaceChildInitialState),
...roomIds.map((r) => spaceChildInitialState(r)),
],
};
}
function spaceChildInitialState(roomId: string): ICreateRoomOpts["initial_state"]["0"] {
function spaceChildInitialState(roomId: string, order?: string): ICreateRoomOpts["initial_state"]["0"] {
return {
type: "m.space.child",
state_key: roomId,
content: {
via: [roomId.split(":")[1]],
order,
},
};
}
@@ -121,9 +122,10 @@ test.describe("Spaces", () => {
await page.getByRole("button", { name: "Skip for now" }).click();
// Assert rooms exist in the room list
await expect(page.getByRole("treeitem", { name: "General", exact: true })).toBeVisible();
await expect(page.getByRole("treeitem", { name: "Random", exact: true })).toBeVisible();
await expect(page.getByRole("treeitem", { name: "Projects", exact: true })).toBeVisible();
const roomList = page.getByRole("tree", { name: "Rooms" });
await expect(roomList.getByRole("treeitem", { name: "General", exact: true })).toBeVisible();
await expect(roomList.getByRole("treeitem", { name: "Random", exact: true })).toBeVisible();
await expect(roomList.getByRole("treeitem", { name: "Projects", exact: true })).toBeVisible();
// Assert rooms exist in the space explorer
await expect(
@@ -155,7 +157,7 @@ test.describe("Spaces", () => {
await page.getByRole("button", { name: "Just me" }).click();
await page.getByText("Sample Room").click({ force: true }); // force click as checkbox size is zero
await page.getByRole("checkbox", { name: "Sample Room" }).click();
// Temporal implementation as multiple elements with the role "button" and name "Add" are found
await page.locator(".mx_AddExistingToSpace_footer").getByRole("button", { name: "Add" }).click();
@@ -165,6 +167,50 @@ test.describe("Spaces", () => {
).toBeVisible();
});
test(
"should allow user to add an existing room to a space after creation",
{ tag: "@screenshot" },
async ({ page, app, user }) => {
await app.client.createRoom({
name: "Sample Room",
});
await app.client.createRoom({
name: "A Room that will not be selected",
});
const menu = await openSpaceCreateMenu(page);
await menu.getByRole("button", { name: "Private" }).click();
await menu
.locator('.mx_SpaceBasicSettings_avatarContainer input[type="file"]')
.setInputFiles("playwright/sample-files/riot.png");
await expect(menu.getByRole("textbox", { name: "Address" })).not.toBeVisible();
await menu
.getByRole("textbox", { name: "Description" })
.fill("This is a personal space to mourn Riot.im...");
await menu.getByRole("textbox", { name: "Name" }).fill("This is my Riot");
await menu.getByRole("textbox", { name: "Name" }).press("Enter");
await page.getByRole("button", { name: "Just me" }).click();
await page.getByRole("button", { name: "Skip for now" }).click();
await page.getByRole("button", { name: "Add room" }).click();
await page.getByRole("menuitem", { name: "Add existing room" }).click();
await page.getByRole("checkbox", { name: "Sample Room" }).click();
await expect(page.getByRole("dialog", { name: "Avatar Add existing rooms" })).toMatchScreenshot(
"add-existing-rooms-dialog.png",
);
await page.getByRole("button", { name: "Add" }).click();
await expect(
page.locator(".mx_SpaceHierarchy_list").getByRole("treeitem", { name: "Sample Room" }),
).toBeVisible();
},
);
test("should allow user to invite another to a space", { tag: "@no-webkit" }, async ({ page, app, user, bot }) => {
await app.client.createSpace({
visibility: "public" as any,
@@ -291,4 +337,36 @@ test.describe("Spaces", () => {
// Assert we get shown the new room intro, and thus not the soft crash screen
await expect(page.locator(".mx_NewRoomIntro")).toBeVisible();
});
test("should render spaces view", { tag: "@screenshot" }, async ({ page, app, user, axe }) => {
axe.disableRules([
// Disable this check as it triggers on nested roving tab index elements which are in practice fine
"nested-interactive",
// XXX: We have some known contrast issues here
"color-contrast",
]);
const childSpaceId1 = await app.client.createSpace({
name: "Child Space 1",
initial_state: [],
});
const childSpaceId2 = await app.client.createSpace({
name: "Child Space 2",
initial_state: [],
});
const childSpaceId3 = await app.client.createSpace({
name: "Child Space 3",
initial_state: [],
});
await app.client.createSpace({
name: "Root Space",
initial_state: [
spaceChildInitialState(childSpaceId1, "a"),
spaceChildInitialState(childSpaceId2, "b"),
spaceChildInitialState(childSpaceId3, "c"),
],
});
await app.viewSpaceByName("Root Space");
await expect(page.locator(".mx_SpaceRoomView")).toMatchScreenshot("space-room-view.png");
});
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

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

View File

@@ -128,7 +128,6 @@
@import "./views/dialogs/_AddExistingToSpaceDialog.pcss";
@import "./views/dialogs/_AnalyticsLearnMoreDialog.pcss";
@import "./views/dialogs/_BugReportDialog.pcss";
@import "./views/dialogs/_BulkRedactDialog.pcss";
@import "./views/dialogs/_ChangelogDialog.pcss";
@import "./views/dialogs/_CompoundDialog.pcss";
@import "./views/dialogs/_ConfirmSpaceUserActionDialog.pcss";
@@ -153,6 +152,7 @@
@import "./views/dialogs/_ModalWidgetDialog.pcss";
@import "./views/dialogs/_PollCreateDialog.pcss";
@import "./views/dialogs/_RegistrationEmailPromptDialog.pcss";
@import "./views/dialogs/_ReportRoomDialog.pcss";
@import "./views/dialogs/_RoomSettingsDialog.pcss";
@import "./views/dialogs/_RoomSettingsDialogBridges.pcss";
@import "./views/dialogs/_RoomUpgradeDialog.pcss";
@@ -212,7 +212,6 @@
@import "./views/elements/_ServerPicker.pcss";
@import "./views/elements/_SettingsFlag.pcss";
@import "./views/elements/_Spinner.pcss";
@import "./views/elements/_StyledCheckbox.pcss";
@import "./views/elements/_StyledRadioButton.pcss";
@import "./views/elements/_SyntaxHighlight.pcss";
@import "./views/elements/_TagComposer.pcss";

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024,2025 New Vector Ltd.
Copyright 2022 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -16,9 +16,9 @@ Please see LICENSE files in the repository root for full details.
.mx_SelectableDeviceTile_checkbox {
flex: 1 0;
.mx_Checkbox_background + div {
flex: 1 0;
/* override more specific selector */
margin-left: $spacing-16 !important;
> div {
margin-top: auto;
margin-bottom: auto;
margin-right: var(--cpd-space-1x);
}
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024,2025 New Vector Ltd.
Copyright 2021 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -70,38 +70,26 @@ Please see LICENSE files in the repository root for full details.
text-transform: uppercase;
color: var(--cpd-color-text-secondary);
margin: 20px 0 12px;
}
.mx_QuickSettingsButton_pinToSidebarHeading {
padding-left: 24px;
position: relative;
}
.mx_Checkbox {
margin-bottom: 8px;
}
.mx_QuickSettingsButton_favouritesCheckbox,
.mx_QuickSettingsButton_peopleCheckbox {
.mx_Checkbox_background + div {
padding-left: 22px;
position: relative;
margin-left: 6px;
font-size: $font-15px;
line-height: $font-24px;
color: var(--cpd-color-text-primary);
}
display: flex;
}
.mx_QuickSettingsButton_moreOptionsButton {
padding-left: 22px;
margin-left: 22px;
margin-left: var(--cpd-space-7x);
font-size: $font-15px;
line-height: $font-24px;
color: var(--cpd-color-text-primary);
position: relative;
margin-bottom: 16px;
}
.mx_QuickSettingsButton_option {
margin-bottom: var(--cpd-space-3x);
label {
/* Correctly line up icons and text. */
display: flex;
}
}
}
.mx_QuickSettingsButton_ContextMenuWrapper_new_room_list {
@@ -111,15 +99,10 @@ Please see LICENSE files in the repository root for full details.
}
.mx_QuickSettingsButton_icon {
// TODO remove when all icons have fill=currentColor
* {
fill: $secondary-content;
}
margin-right: var(--cpd-space-1x);
color: $secondary-content;
width: 16px;
height: 16px;
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 18px;
height: 18px;
margin-top: auto;
margin-bottom: auto;
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024,2025 New Vector Ltd.
Copyright 2021 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -247,15 +247,6 @@ Please see LICENSE files in the repository root for full details.
.mx_AccessibleButton_kind_primary_outline {
padding: 3px 16px; /* to account for the 1px border */
}
.mx_Checkbox {
display: inline-flex;
label {
width: 16px;
height: 16px;
}
}
}
&:hover,

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024,2025 New Vector Ltd.
Copyright 2021 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -32,6 +32,11 @@ Please see LICENSE files in the repository root for full details.
.mx_AddExistingToSpace_section {
margin-right: 12px;
ul {
list-style: none;
padding-left: 0;
}
// provides space for scrollbar so that checkbox and scrollbar do not collide
&:not(:first-child) {
@@ -214,6 +219,12 @@ Please see LICENSE files in the repository root for full details.
display: flex;
margin-top: 12px;
form {
/* Align checkboxes. */
margin-top: auto;
margin-bottom: auto;
}
.mx_DecoratedRoomAvatar, /* we can't target .mx_BaseAvatar here as it'll break the decorated avatar styling */ {
margin-right: 12px;
}
@@ -227,8 +238,4 @@ Please see LICENSE files in the repository root for full details.
text-overflow: ellipsis;
margin-right: 12px;
}
.mx_Checkbox {
align-items: center;
}
}

View File

@@ -1,19 +0,0 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2021 Robin Townsend <robin@robin.town>
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
.mx_BulkRedactDialog {
.mx_Checkbox,
.mx_BulkRedactDialog_checkboxMicrocopy {
line-height: $font-20px;
}
.mx_BulkRedactDialog_checkboxMicrocopy {
margin-left: 26px;
color: $secondary-content;
}
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024,2025 New Vector Ltd.
Copyright 2021 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -43,11 +43,6 @@ Please see LICENSE files in the repository root for full details.
.mx_Field_valid.mx_Field:focus-within {
border-color: $input-border-color;
}
.mx_Checkbox input[type="checkbox"]:checked + label > .mx_Checkbox_background {
background: $info-plinth-fg-color;
border-color: $info-plinth-fg-color;
}
}
.mx_ExportDialog_progress {

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024,2025 New Vector Ltd.
Copyright 2021 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -74,10 +74,6 @@ Please see LICENSE files in the repository root for full details.
line-height: $font-15px;
color: $tertiary-content;
}
.mx_Checkbox {
align-items: center;
}
}
}

View File

@@ -0,0 +1,16 @@
/*
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
.mx_ReportRoomDialog {
textarea {
font: var(--cpd-font-body-md-regular);
border: 1px solid var(--cpd-color-border-interactive-primary);
background: var(--cpd-color-bg-canvas-default);
border-radius: 0.5rem;
padding: var(--cpd-space-3x) var(--cpd-space-4x);
}
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024,2025 New Vector Ltd.
Copyright 2020 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -19,13 +19,6 @@ Please see LICENSE files in the repository root for full details.
margin-top: 20px;
font-size: $font-15px;
line-height: $font-15px;
.mx_WidgetCapabilitiesPromptDialog_byline {
color: $muted-fg-color;
margin-left: 26px;
font-size: $font-12px;
line-height: $font-12px;
}
}
.mx_Dialog_buttons {

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024,2025 New Vector Ltd.
Copyright 2022 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -7,26 +7,5 @@ Please see LICENSE files in the repository root for full details.
*/
.mx_LabelledCheckbox {
display: flex;
gap: 8px;
flex-direction: row;
.mx_Checkbox {
margin-top: 3px; /* visually align with label text */
}
.mx_LabelledCheckbox_labels {
flex: 1;
.mx_LabelledCheckbox_label {
vertical-align: middle;
}
.mx_LabelledCheckbox_byline {
display: block;
padding-top: $spacing-4;
color: $muted-fg-color;
font-size: $font-11px;
}
}
margin-top: var(--cpd-space-2x);
}

View File

@@ -1,98 +0,0 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2020 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
.mx_Checkbox {
$size: $font-16px;
$border-radius: 0.27rem;
display: flex;
align-items: flex-start;
input[type="checkbox"] {
appearance: none;
margin: 0;
padding: 0;
& + label {
display: flex;
align-items: center;
flex-grow: 1;
}
& + label > .mx_Checkbox_background {
display: inline-flex;
position: relative;
flex-shrink: 0;
height: $size;
width: $size;
size: 0.5rem;
border: 1px solid var(--cpd-color-border-interactive-primary);
box-sizing: border-box;
border-radius: $border-radius;
.mx_Checkbox_checkmark {
display: none;
height: 100%;
width: 100%;
mask-image: url("@vector-im/compound-design-tokens/icons/check.svg");
mask-position: center;
mask-size: 100%;
mask-repeat: no-repeat;
}
}
&:checked + label > .mx_Checkbox_background .mx_Checkbox_checkmark {
display: block;
}
& + label > *:not(.mx_Checkbox_background) {
margin-left: 10px;
}
&:disabled + label {
cursor: not-allowed;
}
&:focus-visible {
& + label .mx_Checkbox_background {
@mixin unreal-focus;
}
}
}
}
.mx_Checkbox.mx_Checkbox_kind_solid input[type="checkbox"] {
& + label > .mx_Checkbox_background .mx_Checkbox_checkmark {
background: var(--cpd-color-icon-on-solid-primary);
}
&:checked + label > .mx_Checkbox_background {
background: var(--cpd-color-bg-accent-rest);
border-color: var(--cpd-color-bg-accent-rest);
}
&:checked:disabled + label > .mx_Checkbox_background {
background: var(--cpd-color-bg-action-primary-disabled);
border-color: var(--cpd-color-bg-action-primary-disabled);
}
}
.mx_Checkbox.mx_Checkbox_kind_outline input[type="checkbox"] {
& + label > .mx_Checkbox_background .mx_Checkbox_checkmark {
background: var(--cpd-color-bg-accent-rest);
}
&:checked + label > .mx_Checkbox_background {
background: transparent;
border-color: var(--cpd-color-bg-accent-rest);
}
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024, 2025 New Vector Ltd.
Copyright 2020 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -101,6 +101,6 @@ Please see LICENSE files in the repository root for full details.
margin: $spacing-12 0 $spacing-4;
}
.mx_RoomSummaryCard_leave {
.mx_RoomSummaryCard_bottomOptions {
margin: 0 0 var(--cpd-space-8x);
}

View File

@@ -119,9 +119,6 @@ Please see LICENSE files in the repository root for full details.
word-break: break-all;
text-overflow: ellipsis;
/* Don't spill over the container */
width: 90%;
/* E2E icon wrapper */
.mx_Flex > span {
display: inline-block;
@@ -130,15 +127,11 @@ Please see LICENSE files in the repository root for full details.
.mx_UserInfo_profile_name {
height: 30px;
text-wrap: nowrap;
}
.mx_UserInfo_profile_mxid {
color: var(--cpd-color-text-secondary);
height: 28px;
max-width: 100%;
/* MXIDs are one long "word" */
word-break: break-all;
}
.mx_UserInfo_profileStatus {

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024,2025 New Vector Ltd.
Copyright 2020 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -393,8 +393,7 @@ Please see LICENSE files in the repository root for full details.
margin-bottom: 4px;
}
.mx_StyledRadioButton,
.mx_Checkbox {
.mx_StyledRadioButton {
margin-top: 8px;
}
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024,2025 New Vector Ltd.
Copyright 2021 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -14,17 +14,12 @@ Please see LICENSE files in the repository root for full details.
}
}
.mx_SidebarUserSettingsTab_checkbox {
margin-bottom: $spacing-8;
/* override checkbox styles */
label {
align-items: flex-start !important;
}
svg {
height: 16px;
width: 16px;
margin-right: $spacing-8;
margin-bottom: -1px;
}
.mx_SidebarUserSettingsTab_icon {
margin-right: var(--cpd-space-2x);
margin-top: auto;
margin-bottom: auto;
}
.mx_SidebarUserSettingsTab_checkbox label {
display: flex;
}

View File

@@ -22,7 +22,6 @@ import {
type MatrixCall,
} from "matrix-js-sdk/src/webrtc/call";
import { logger } from "matrix-js-sdk/src/logger";
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
import { CallEventHandlerEvent } from "matrix-js-sdk/src/webrtc/callEventHandler";
import { MatrixClientPeg } from "./MatrixClientPeg";
@@ -596,7 +595,7 @@ export default class LegacyCallHandler extends TypedEventEmitter<LegacyCallHandl
switch (newState) {
case CallState.Ringing: {
const incomingCallPushRule = new PushProcessor(MatrixClientPeg.safeGet()).getPushRuleById(
const incomingCallPushRule = MatrixClientPeg.safeGet().pushProcessor.getPushRuleById(
RuleId.IncomingCall,
);
const pushRuleEnabled = incomingCallPushRule?.enabled;

View File

@@ -406,6 +406,39 @@ export function attemptTokenLogin(
});
}
/**
* Load the pickle key inside the credentials or create it if it does not exist for this device.
*
* @param credentials Holds the device to load/store the pickle key
*
* @returns {Promise} promise which resolves to the loaded or generated pickle key or undefined if
* none was loaded nor generated
*/
async function loadOrCreatePickleKey(credentials: IMatrixClientCreds): Promise<string | undefined> {
// Try to load the pickle key
const userId = credentials.userId;
const deviceId = credentials.deviceId;
let pickleKey = (await PlatformPeg.get()?.getPickleKey(userId, deviceId ?? "")) ?? undefined;
if (!pickleKey) {
// Create it if it did not exist
pickleKey =
userId && deviceId
? ((await PlatformPeg.get()?.createPickleKey(userId, deviceId)) ?? undefined)
: undefined;
if (pickleKey) {
logger.log(`Created pickle key for ${credentials.userId}|${credentials.deviceId}`);
} else {
logger.log("Pickle key not created");
}
} else {
logger.log(
`Pickle key already exists for ${credentials.userId}|${credentials.deviceId} do not create a new one`,
);
}
return pickleKey;
}
/**
* Called after a successful token login or OIDC authorization.
* Clear storage then save new credentials in storage
@@ -413,6 +446,8 @@ export function attemptTokenLogin(
*/
async function onSuccessfulDelegatedAuthLogin(credentials: IMatrixClientCreds): Promise<void> {
await clearStorage();
// SSO does not go through setLoggedIn so we need to load/create the pickle key here too
credentials.pickleKey = await loadOrCreatePickleKey(credentials);
await persistCredentials(credentials);
// remember that we just logged in
@@ -655,18 +690,8 @@ async function handleLoadSessionFailure(e: unknown): Promise<boolean> {
export async function setLoggedIn(credentials: IMatrixClientCreds): Promise<MatrixClient> {
credentials.freshLogin = true;
stopMatrixClient();
const pickleKey =
credentials.userId && credentials.deviceId
? await PlatformPeg.get()?.createPickleKey(credentials.userId, credentials.deviceId)
: null;
if (pickleKey) {
logger.log(`Created pickle key for ${credentials.userId}|${credentials.deviceId}`);
} else {
logger.log("Pickle key not created");
}
return doSetLoggedIn({ ...credentials, pickleKey: pickleKey ?? undefined }, true, true);
credentials.pickleKey = await loadOrCreatePickleKey(credentials);
return doSetLoggedIn(credentials, true, true);
}
/**

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024,2025 New Vector Ltd.
Copyright 2019 The Matrix.org Foundation C.I.C.
Copyright 2018 New Vector Ltd
Copyright 2015, 2016 OpenMarket Ltd
@@ -16,13 +16,12 @@ import { KeyBindingAction } from "../KeyboardShortcuts";
import { getKeyBindingsManager } from "../../KeyBindingsManager";
interface IProps extends React.ComponentProps<typeof StyledCheckbox> {
label?: string;
onChange(): void; // we handle keyup/down ourselves so lose the ChangeEvent
onClose(): void; // gets called after onChange on KeyBindingAction.ActivateSelectedButton
}
// Semantic component for representing a styled role=menuitemcheckbox
export const StyledMenuItemCheckbox: React.FC<IProps> = ({ children, label, onChange, onClose, ...props }) => {
export const StyledMenuItemCheckbox: React.FC<IProps> = ({ children, onChange, onClose, ...props }) => {
const [onFocus, isActive, ref] = useRovingTabIndex<HTMLInputElement>();
const onKeyDown = (e: React.KeyboardEvent): void => {
@@ -63,7 +62,6 @@ export const StyledMenuItemCheckbox: React.FC<IProps> = ({ children, label, onCh
<StyledCheckbox
{...props}
role="menuitemcheckbox"
aria-label={label}
onChange={onChange}
onKeyDown={onKeyDown}
onKeyUp={onKeyUp}

View File

@@ -1388,7 +1388,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
// so show the homepage.
dis.dispatch<ViewHomePagePayload>({ action: Action.ViewHomePage, justRegistered: true });
}
} else {
} else if (!(await this.shouldForceVerification())) {
this.showScreenAfterLogin();
}
@@ -2003,9 +2003,17 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
};
// complete security / e2e setup has finished
private onCompleteSecurityE2eSetupFinished = (): void => {
// This is async but we making this function async to wait for it isn't useful
this.onShowPostLoginScreen().catch((e) => {
private onCompleteSecurityE2eSetupFinished = async (): Promise<void> => {
const forceVerify = await this.shouldForceVerification();
if (forceVerify) {
const isVerified = await MatrixClientPeg.safeGet().getCrypto()?.isCrossSigningReady();
if (!isVerified) {
// We must verify but we haven't yet verified - don't continue logging in
return;
}
}
await this.onShowPostLoginScreen().catch((e) => {
logger.error("Exception showing post-login screen", e);
});
};

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024,2025 New Vector Ltd.
Copyright 2021-2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -16,6 +16,7 @@ import React, {
useCallback,
useContext,
useEffect,
useId,
useMemo,
useRef,
useState,
@@ -116,6 +117,7 @@ const Tile: React.FC<ITileProps> = ({
const [showChildren, toggleShowChildren] = useStateToggle(true);
const [onFocus, isActive, ref, nodeRef] = useRovingTabIndex();
const [busy, setBusy] = useState(false);
const checkboxLabelId = useId();
const onPreviewClick = (ev: ButtonEvent): void => {
ev.preventDefault();
@@ -172,7 +174,14 @@ const Tile: React.FC<ITileProps> = ({
let checkbox: ReactElement | undefined;
if (onToggleClick) {
if (hasPermissions) {
checkbox = <StyledCheckbox checked={!!selected} onChange={onToggleClick} tabIndex={isActive ? 0 : -1} />;
checkbox = (
<StyledCheckbox
role="presentation"
aria-labelledby={checkboxLabelId}
checked={!!selected}
tabIndex={-1}
/>
);
} else {
checkbox = (
<TextWithTooltip
@@ -181,7 +190,12 @@ const Tile: React.FC<ITileProps> = ({
ev.stopPropagation();
}}
>
<StyledCheckbox disabled={true} tabIndex={isActive ? 0 : -1} />
<StyledCheckbox
role="presentation"
aria-labelledby={checkboxLabelId}
disabled={true}
tabIndex={-1}
/>
</TextWithTooltip>
);
}
@@ -248,7 +262,7 @@ const Tile: React.FC<ITileProps> = ({
<div className="mx_SpaceHierarchy_roomTile_item">
<div className="mx_SpaceHierarchy_roomTile_avatar">{avatar}</div>
<div className="mx_SpaceHierarchy_roomTile_name">
{name}
<span id={checkboxLabelId}>{name}</span>
{joinedSection}
{suggestedSection}
</div>
@@ -330,11 +344,14 @@ const Tile: React.FC<ITileProps> = ({
};
}
const shouldToggle = hasPermissions && onToggleClick;
return (
<li
className="mx_SpaceHierarchy_roomTileWrapper"
role="treeitem"
aria-selected={selected}
aria-labelledby={checkboxLabelId}
aria-expanded={children ? showChildren : undefined}
>
<AccessibleButton
@@ -342,7 +359,7 @@ const Tile: React.FC<ITileProps> = ({
mx_SpaceHierarchy_subspace: room.room_type === RoomType.Space,
mx_SpaceHierarchy_joining: busy,
})}
onClick={hasPermissions && onToggleClick ? onToggleClick : onPreviewClick}
onClick={shouldToggle ? onToggleClick : onPreviewClick}
onKeyDown={onKeyDown}
ref={ref}
onFocus={onFocus}

View File

@@ -18,7 +18,7 @@ import SpaceStore from "../../../stores/spaces/SpaceStore";
import dispatcher from "../../../dispatcher/dispatcher";
import { Action } from "../../../dispatcher/actions";
import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
import { useIndexForActiveRoom } from "./useIndexForActiveRoom";
import { useStickyRoomList } from "./useStickyRoomList";
export interface RoomListViewState {
/**
@@ -97,8 +97,14 @@ export interface RoomListViewState {
*/
export function useRoomListViewModel(): RoomListViewState {
const matrixClient = useMatrixClientContext();
const { primaryFilters, activePrimaryFilter, rooms, activateSecondaryFilter, activeSecondaryFilter } =
useFilteredRooms();
const {
primaryFilters,
activePrimaryFilter,
rooms: filteredRooms,
activateSecondaryFilter,
activeSecondaryFilter,
} = useFilteredRooms();
const { activeIndex, rooms } = useStickyRoomList(filteredRooms);
const currentSpace = useEventEmitterState<Room | null>(
SpaceStore.instance,
@@ -107,7 +113,6 @@ export function useRoomListViewModel(): RoomListViewState {
);
const canCreateRoom = hasCreateRoomRights(matrixClient, currentSpace);
const activeIndex = useIndexForActiveRoom(rooms);
const { activeSortOption, sort } = useSorter();
const { shouldShowMessagePreview, toggleMessagePreview } = useMessagePreviewToggle();

View File

@@ -145,22 +145,14 @@ export function useFilteredRooms(): FilteredRooms {
// SecondaryFilter is an enum for the UI, let's convert it to something
// that the store will understand.
const secondary = secondaryFiltersToFilterKeyMap.get(filter);
// Active primary filter may need to be toggled off when applying this secondary filer.
let primary = primaryFilter;
if (
primaryFilter !== undefined &&
secondary !== undefined &&
!isPrimaryFilterCompatible(primaryFilter, secondary)
) {
primary = undefined;
}
setActiveSecondaryFilter(filter);
setPrimaryFilter(primary);
updateRoomsFromStore(filterUndefined([primary, secondary]));
// Reset any active primary filters.
setPrimaryFilter(undefined);
updateRoomsFromStore(filterUndefined([secondary]));
},
[activeSecondaryFilter, primaryFilter, updateRoomsFromStore],
[activeSecondaryFilter, updateRoomsFromStore],
);
/**

View File

@@ -1,44 +0,0 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
import { useCallback, useEffect, useState } from "react";
import { SdkContextClass } from "../../../contexts/SDKContext";
import { useDispatcher } from "../../../hooks/useDispatcher";
import dispatcher from "../../../dispatcher/dispatcher";
import { Action } from "../../../dispatcher/actions";
import type { Room } from "matrix-js-sdk/src/matrix";
/**
* Tracks the index of the active room in the given array of rooms.
* @param rooms list of rooms
* @returns index of the active room or undefined otherwise.
*/
export function useIndexForActiveRoom(rooms: Room[]): number | undefined {
const [index, setIndex] = useState<number | undefined>(undefined);
const calculateIndex = useCallback(
(newRoomId?: string) => {
const activeRoomId = newRoomId ?? SdkContextClass.instance.roomViewStore.getRoomId();
const index = rooms.findIndex((room) => room.roomId === activeRoomId);
setIndex(index === -1 ? undefined : index);
},
[rooms],
);
// Re-calculate the index when the active room has changed.
useDispatcher(dispatcher, (payload) => {
if (payload.action === Action.ActiveRoomChanged) calculateIndex(payload.newRoomId);
});
// Re-calculate the index when the list of rooms has changed.
useEffect(() => {
calculateIndex();
}, [calculateIndex, rooms]);
return index;
}

View File

@@ -0,0 +1,117 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
import { useCallback, useEffect, useState } from "react";
import { SdkContextClass } from "../../../contexts/SDKContext";
import { useDispatcher } from "../../../hooks/useDispatcher";
import dispatcher from "../../../dispatcher/dispatcher";
import { Action } from "../../../dispatcher/actions";
import type { Room } from "matrix-js-sdk/src/matrix";
import type { Optional } from "matrix-events-sdk";
function getIndexByRoomId(rooms: Room[], roomId: Optional<string>): number | undefined {
const index = rooms.findIndex((room) => room.roomId === roomId);
return index === -1 ? undefined : index;
}
function getRoomsWithStickyRoom(
rooms: Room[],
oldIndex: number | undefined,
newIndex: number | undefined,
isRoomChange: boolean,
): { newRooms: Room[]; newIndex: number | undefined } {
const updated = { newIndex, newRooms: rooms };
if (isRoomChange) {
/*
* When opening another room, the index should obviously change.
*/
return updated;
}
if (newIndex === undefined || oldIndex === undefined) {
/*
* If oldIndex is undefined, then there was no active room before.
* So nothing to do in regards to sticky room.
* Similarly, if newIndex is undefined, there's no active room anymore.
*/
return updated;
}
if (newIndex === oldIndex) {
/*
* If the index hasn't changed, we have nothing to do.
*/
return updated;
}
if (oldIndex > rooms.length - 1) {
/*
* If the old index falls out of the bounds of the rooms array
* (usually because rooms were removed), we can no longer place
* the active room in the same old index.
*/
return updated;
}
/*
* Making the active room sticky is as simple as removing it from
* its new index and placing it in the old index.
*/
const newRooms = [...rooms];
const [newRoom] = newRooms.splice(newIndex, 1);
newRooms.splice(oldIndex, 0, newRoom);
return { newIndex: oldIndex, newRooms };
}
interface StickyRoomListResult {
/**
* List of rooms with sticky active room.
*/
rooms: Room[];
/**
* Index of the active room in the room list.
*/
activeIndex: number | undefined;
}
/**
* - Provides a list of rooms such that the active room is sticky i.e the active room is kept
* in the same index even when the order of rooms in the list changes.
* - Provides the index of the active room.
* @param rooms list of rooms
* @see {@link StickyRoomListResult} details what this hook returns..
*/
export function useStickyRoomList(rooms: Room[]): StickyRoomListResult {
const [listState, setListState] = useState<{ index: number | undefined; roomsWithStickyRoom: Room[] }>({
index: undefined,
roomsWithStickyRoom: rooms,
});
const updateRoomsAndIndex = useCallback(
(newRoomId?: string, isRoomChange: boolean = false) => {
setListState((current) => {
const activeRoomId = newRoomId ?? SdkContextClass.instance.roomViewStore.getRoomId();
const newActiveIndex = getIndexByRoomId(rooms, activeRoomId);
const oldIndex = current.index;
const { newIndex, newRooms } = getRoomsWithStickyRoom(rooms, oldIndex, newActiveIndex, isRoomChange);
return { index: newIndex, roomsWithStickyRoom: newRooms };
});
},
[rooms],
);
// Re-calculate the index when the active room has changed.
useDispatcher(dispatcher, (payload) => {
if (payload.action === Action.ActiveRoomChanged) updateRoomsAndIndex(payload.newRoomId, true);
});
// Re-calculate the index when the list of rooms has changed.
useEffect(() => {
updateRoomsAndIndex();
}, [rooms, updateRoomsAndIndex]);
return { activeIndex: listState.index, rooms: listState.roomsWithStickyRoom };
}

View File

@@ -1,12 +1,12 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024,2025 New Vector Ltd.
Copyright 2021 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import React, { type ReactElement, type ReactNode, useContext, useMemo, useRef, useState } from "react";
import React, { type ReactElement, type ReactNode, useContext, useId, useMemo, useRef, useState } from "react";
import classNames from "classnames";
import { type Room, EventType } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
@@ -53,8 +53,9 @@ export const Entry: React.FC<{
checked: boolean;
onChange?(value: boolean): void;
}> = ({ room, checked, onChange }) => {
const id = useId();
return (
<label className="mx_AddExistingToSpace_entry">
<li id={id} className="mx_AddExistingToSpace_entry" aria-label={room.name}>
{room?.isSpaceRoom() ? (
<RoomAvatar room={room} size="32px" />
) : (
@@ -62,11 +63,12 @@ export const Entry: React.FC<{
)}
<span className="mx_AddExistingToSpace_entry_name">{room.name}</span>
<StyledCheckbox
aria-labelledby={id}
onChange={onChange ? (e) => onChange(e.currentTarget.checked) : undefined}
checked={checked}
disabled={!onChange}
/>
</label>
</li>
);
};
@@ -357,6 +359,7 @@ const defaultRendererFactory =
<div className="mx_AddExistingToSpace_section">
<h3>{_t(title)}</h3>
<LazyRenderList
element="ul"
itemHeight={ROW_HEIGHT}
items={rooms}
scrollTop={scrollTop}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024,2025 New Vector Ltd.
Copyright 2021 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -113,12 +113,13 @@ const BulkRedactDialog: React.FC<Props> = (props) => {
<div className="mx_Dialog_content" id="mx_Dialog_content">
<p>{_t("user_info|redact|confirm_description_1", { count, user })}</p>
<p>{_t("user_info|redact|confirm_description_2")}</p>
<StyledCheckbox checked={keepStateEvents} onChange={(e) => setKeepStateEvents(e.target.checked)}>
<StyledCheckbox
description={_t("user_info|redact|confirm_keep_state_explainer")}
checked={keepStateEvents}
onChange={(e) => setKeepStateEvents(e.target.checked)}
>
{_t("user_info|redact|confirm_keep_state_label")}
</StyledCheckbox>
<div className="mx_BulkRedactDialog_checkboxMicrocopy">
{_t("user_info|redact|confirm_keep_state_explainer")}
</div>
</div>
<DialogButtons
primaryButton={_t("user_info|redact|confirm_button", { count })}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024,2025 New Vector Ltd.
Copyright 2021 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -78,7 +78,6 @@ const GenericFeatureFeedbackDialog: React.FC<IProps> = ({
}}
autoFocus={true}
/>
<StyledCheckbox
checked={canContact}
onChange={(e) => setCanContact((e.target as HTMLInputElement).checked)}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024,2025 New Vector Ltd.
Copyright 2021 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -44,22 +44,23 @@ const Entry: React.FC<{
}
return (
<label className="mx_ManageRestrictedJoinRuleDialog_entry">
<div>
<div>
{localRoom ? <RoomAvatar room={room} size="20px" /> : <RoomAvatar oobData={room} size="20px" />}
<span className="mx_ManageRestrictedJoinRuleDialog_entry_name">{room.name}</span>
</div>
{description && (
<div className="mx_ManageRestrictedJoinRuleDialog_entry_description">{description}</div>
)}
</div>
<div className="mx_ManageRestrictedJoinRuleDialog_entry">
<StyledCheckbox
onChange={onChange ? (e) => onChange(e.target.checked) : undefined}
checked={checked}
disabled={!onChange}
/>
</label>
description={description}
>
<div>
{localRoom ? (
<RoomAvatar role="none" room={room} size="20px" />
) : (
<RoomAvatar oobData={room} size="20px" />
)}
<span className="mx_ManageRestrictedJoinRuleDialog_entry_name">{room.name}</span>
</div>
</StyledCheckbox>
</div>
);
};

View File

@@ -0,0 +1,95 @@
/*
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import React, { type ChangeEventHandler, useCallback, useState } from "react";
import { Root, Field, Label, InlineSpinner, ErrorMessage } from "@vector-im/compound-web";
import { _t } from "../../../languageHandler";
import SdkConfig from "../../../SdkConfig";
import Markdown from "../../../Markdown";
import BaseDialog from "./BaseDialog";
import DialogButtons from "../elements/DialogButtons";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
interface IProps {
roomId: string;
onFinished(complete: boolean): void;
}
/*
* A dialog for reporting a room.
*/
export const ReportRoomDialog: React.FC<IProps> = function ({ roomId, onFinished }) {
const [error, setErr] = useState<string>();
const [busy, setBusy] = useState(false);
const [sent, setSent] = useState(false);
const [reason, setReason] = useState("");
const client = MatrixClientPeg.safeGet();
const onReasonChange = useCallback<ChangeEventHandler<HTMLTextAreaElement>>((e) => setReason(e.target.value), []);
const onCancel = useCallback(() => onFinished(sent), [sent, onFinished]);
const onSubmit = useCallback(async () => {
setBusy(true);
try {
await client.reportRoom(roomId, reason);
setSent(true);
} catch (ex) {
if (ex instanceof Error) {
setErr(ex.message);
} else {
setErr("Unknown error");
}
} finally {
setBusy(false);
}
}, [roomId, reason, client]);
const adminMessageMD = SdkConfig.getObject("report_event")?.get("admin_message_md", "adminMessageMD");
let adminMessage: JSX.Element | undefined;
if (adminMessageMD) {
const html = new Markdown(adminMessageMD).toHTML({ externalLinks: true });
adminMessage = <p dangerouslySetInnerHTML={{ __html: html }} />;
}
return (
<BaseDialog
className="mx_ReportRoomDialog"
onFinished={() => onFinished(sent)}
title={_t("report_room|title")}
contentId="mx_ReportEventDialog"
>
{sent && <p>{_t("report_room|sent")}</p>}
{!sent && (
<Root id="mx_ReportEventDialog" onSubmit={onSubmit}>
<p>{_t("report_room|description")}</p>
{adminMessage}
<Field name="reason">
<Label htmlFor="mx_ReportRoomDialog_reason">{_t("room_settings|permissions|ban_reason")}</Label>
<textarea
id="mx_ReportRoomDialog_reason"
placeholder={_t("report_room|reason_placeholder")}
rows={5}
onChange={onReasonChange}
value={reason}
disabled={busy}
/>
{error ? <ErrorMessage>{error}</ErrorMessage> : null}
</Field>
{busy ? <InlineSpinner /> : null}
<DialogButtons
primaryButton={_t("action|send_report")}
onPrimaryButtonClick={onSubmit}
focus={true}
onCancel={onCancel}
disabled={busy}
/>
</Root>
)}
</BaseDialog>
);
};

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024,2025 New Vector Ltd.
Copyright 2021 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -45,14 +45,13 @@ const SpacePreferencesAppearanceTab: React.FC<Pick<IProps, "space">> = ({ space
!showPeople,
);
}}
description={_t("space|preferences|show_people_in_space", {
spaceName: space.name,
})}
>
{_t("common|people")}
</StyledCheckbox>
<SettingsSubsectionText>
{_t("space|preferences|show_people_in_space", {
spaceName: space.name,
})}
</SettingsSubsectionText>
<SettingsSubsectionText />
</SettingsSubsection>
</SettingsSection>
</SettingsTab>

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024,2025 New Vector Ltd.
Copyright 2020, 2021 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -100,16 +100,12 @@ export default class WidgetCapabilitiesPromptDialog extends React.PureComponent<
});
const checkboxRows = orderedCapabilities.map(([cap, isChecked], i) => {
const text = CapabilityText.for(cap, this.props.widgetKind);
const byline = text.byline ? (
<span className="mx_WidgetCapabilitiesPromptDialog_byline">{text.byline}</span>
) : null;
return (
<div className="mx_WidgetCapabilitiesPromptDialog_cap" key={cap + i}>
<StyledCheckbox checked={isChecked} onChange={() => this.onToggle(cap)}>
<StyledCheckbox checked={isChecked} onChange={() => this.onToggle(cap)} description={text.byline}>
{text.primary}
</StyledCheckbox>
{byline}
</div>
);
});

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024,2025 New Vector Ltd.
Copyright 2022 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -28,13 +28,16 @@ interface IProps {
const LabelledCheckbox: React.FC<IProps> = ({ value, label, byline, disabled, onChange, className }) => {
return (
<label className={classnames("mx_LabelledCheckbox", className)}>
<StyledCheckbox disabled={disabled} checked={value} onChange={(e) => onChange(e.target.checked)} />
<div className="mx_LabelledCheckbox_labels">
<div className={classnames("mx_LabelledCheckbox", className)}>
<StyledCheckbox
description={byline}
disabled={disabled}
checked={value}
onChange={(e) => onChange(e.target.checked)}
>
<span className="mx_LabelledCheckbox_label">{label}</span>
{byline ? <span className="mx_LabelledCheckbox_byline">{byline}</span> : null}
</div>
</label>
</StyledCheckbox>
</div>
);
};

View File

@@ -1,64 +1,51 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024,2025 New Vector Ltd.
Copyright 2020 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import React, { type Ref } from "react";
import React, { useId, type ReactNode, type Ref } from "react";
import { secureRandomString } from "matrix-js-sdk/src/randomstring";
import classnames from "classnames";
export enum CheckboxStyle {
Solid = "solid",
Outline = "outline",
}
import { CheckboxInput, Form, HelpMessage, InlineField, Label } from "@vector-im/compound-web";
interface IProps extends React.InputHTMLAttributes<HTMLInputElement> {
inputRef?: Ref<HTMLInputElement>;
kind?: CheckboxStyle;
id?: string;
description?: ReactNode;
}
export default class StyledCheckbox extends React.PureComponent<IProps> {
private id: string;
const StyledCheckbox: React.FC<IProps> = ({
id: initialId,
children: label,
className,
inputRef,
description,
...otherProps
}) => {
const id = initialId || "checkbox_" + secureRandomString(10);
const name = useId();
const descriptionId = useId();
return (
<Form.Root>
<InlineField
className={className}
name={name}
control={
<CheckboxInput
ref={inputRef}
aria-describedby={description ? descriptionId : undefined}
id={id}
{...otherProps}
/>
}
>
{label && <Label htmlFor={id}>{label}</Label>}
{description && <HelpMessage id={descriptionId}>{description}</HelpMessage>}
</InlineField>
</Form.Root>
);
};
public static readonly defaultProps = {
className: "",
};
public constructor(props: IProps) {
super(props);
// 56^10 so unlikely chance of collision.
this.id = this.props.id || "checkbox_" + secureRandomString(10);
}
public render(): React.ReactNode {
/* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */
const { children, className, kind = CheckboxStyle.Solid, inputRef, ...otherProps } = this.props;
const newClassName = classnames("mx_Checkbox", className, {
mx_Checkbox_hasKind: kind,
[`mx_Checkbox_kind_${kind}`]: kind,
});
return (
<span className={newClassName}>
<input
// Pass through the ref - used for keyboard shortcut access to some buttons
ref={inputRef}
id={this.id}
{...otherProps}
type="checkbox"
/>
<label htmlFor={this.id}>
{/* Using the div to center the image */}
<div className="mx_Checkbox_background">
<div className="mx_Checkbox_checkmark" />
</div>
{!!this.props.children && <div>{this.props.children}</div>}
</label>
</span>
);
}
}
export default StyledCheckbox;

View File

@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
import React, { createRef, type SyntheticEvent, type MouseEvent, StrictMode } from "react";
import { MsgType, PushRuleKind } from "matrix-js-sdk/src/matrix";
import { TooltipProvider } from "@vector-im/compound-web";
import { globToRegexp } from "matrix-js-sdk/src/utils";
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
import * as HtmlUtils from "../../../HtmlUtils";
import { formatDate } from "../../../DateUtils";
@@ -110,7 +110,10 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
pushDetails.rule.kind === PushRuleKind.ContentSpecific &&
pushDetails.rule.pattern
) {
this.pillifyNotificationKeywords([content], this.regExpForKeywordPattern(pushDetails.rule.pattern));
this.pillifyNotificationKeywords(
[content],
PushProcessor.getPushRuleGlobRegex(pushDetails.rule.pattern, true),
);
}
}
@@ -235,12 +238,12 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
continue;
}
const match = text.match(exp);
if (!match || match.length < 3) {
if (!match || match.length < 2) {
node = node.nextSibling;
continue;
}
const keywordText = match[2];
const idx = match.index! + match[1].length;
const keywordText = match[1];
const idx = match.index!;
const before = text.substring(0, idx);
const after = text.substring(idx + keywordText.length);
@@ -265,12 +268,6 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
}
}
private regExpForKeywordPattern(pattern: string): RegExp {
// Reflects the push notification pattern-matching implementation at
// https://github.com/matrix-org/matrix-js-sdk/blob/dbd7d26968b94700827bac525c39afff2c198e61/src/pushprocessor.ts#L570
return new RegExp("(^|\\W)(" + globToRegexp(pattern) + ")(\\W|$)", "i");
}
private findLinks(nodes: ArrayLike<Element>): string[] {
let links: string[] = [];

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024, 2025 New Vector Ltd.
Copyright 2020 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -77,6 +77,7 @@ import { isVideoRoom as calcIsVideoRoom } from "../../../utils/video-rooms";
import { usePinnedEvents } from "../../../hooks/usePinnedEvents";
import { ReleaseAnnouncement } from "../../structures/ReleaseAnnouncement.tsx";
import { useScopedRoomContext } from "../../../contexts/ScopedRoomContext.tsx";
import { ReportRoomDialog } from "../dialogs/ReportRoomDialog.tsx";
interface IProps {
room: Room;
@@ -231,6 +232,11 @@ const RoomSummaryCard: React.FC<IProps> = ({
room_id: room.roomId,
});
};
const onReportRoomClick = (): void => {
Modal.createDialog(ReportRoomDialog, {
roomId: room.roomId,
});
};
const isRoomEncrypted = useIsEncrypted(cli, room);
const roomContext = useScopedRoomContext("e2eStatus", "timelineRenderingType");
@@ -439,14 +445,21 @@ const RoomSummaryCard: React.FC<IProps> = ({
<MenuItem Icon={SettingsIcon} label={_t("common|settings")} onSelect={onRoomSettingsClick} />
<Separator />
<MenuItem
className="mx_RoomSummaryCard_leave"
Icon={LeaveIcon}
kind="critical"
label={_t("action|leave_room")}
onSelect={onLeaveRoomClick}
/>
<div className="mx_RoomSummaryCard_bottomOptions">
<MenuItem
className="mx_RoomSummaryCard_leave"
Icon={LeaveIcon}
kind="critical"
label={_t("action|leave_room")}
onSelect={onLeaveRoomClick}
/>
<MenuItem
Icon={ErrorIcon}
kind="critical"
label={_t("action|report_room")}
onSelect={onReportRoomClick}
/>
</div>
</div>
</BaseCard>
);

View File

@@ -1409,30 +1409,24 @@ export const UserInfoHeader: React.FC<{
<Flex direction="column" align="center" className="mx_UserInfo_profile">
<Heading size="sm" weight="semibold" as="h1" dir="auto">
<Flex className="mx_UserInfo_profile_name" direction="row-reverse" align="center">
<Tooltip isTriggerInteractive={true} placement="left" label={displayName}>
<span>
{displayName}
</span>
</Tooltip>
{displayName}
</Flex>
</Heading>
{presenceLabel}
{timezoneInfo && (
<Flex align="center" className="mx_UserInfo_timezone">
<Text size="sm" weight="regular">
<Tooltip label={timezoneInfo?.timezone ?? ""}>
<span>{timezoneInfo?.friendly ?? ""}</span>
</Tooltip>
</Text>
</Flex>
)}
{userIdentifier && (
<Text size="sm" weight="semibold" className="mx_UserInfo_profile_mxid">
<CopyableText getTextToCopy={() => userIdentifier} border={false}>
{userIdentifier}
</CopyableText>
</Text>
<Tooltip label={timezoneInfo?.timezone ?? ""}>
<Flex align="center" className="mx_UserInfo_timezone">
<Text size="sm" weight="regular">
{timezoneInfo?.friendly ?? ""}
</Text>
</Flex>
</Tooltip>
)}
<Text size="sm" weight="semibold" className="mx_UserInfo_profile_mxid">
<CopyableText getTextToCopy={() => userIdentifier} border={false}>
{userIdentifier}
</CopyableText>
</Text>
</Flex>
{!hideVerificationSection && <VerificationSection member={member} devices={devices} />}
</Container>

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024,2025 New Vector Ltd.
Copyright 2022 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -10,7 +10,7 @@ import React, { type HTMLProps } from "react";
import { Tooltip } from "@vector-im/compound-web";
import { _t } from "../../../../languageHandler";
import StyledCheckbox, { CheckboxStyle } from "../../elements/StyledCheckbox";
import StyledCheckbox from "../../elements/StyledCheckbox";
interface Props extends Omit<HTMLProps<HTMLDivElement>, "className"> {
selectedDeviceCount: number;
@@ -34,7 +34,6 @@ const FilteredDeviceListHeader: React.FC<Props> = ({
{!isSelectDisabled && (
<Tooltip label={checkboxLabel} placement="top" isTriggerInteractive={false}>
<StyledCheckbox
kind={CheckboxStyle.Solid}
checked={isAllSelected}
onChange={toggleSelectAll}
id="device-select-all-checkbox"

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024,2025 New Vector Ltd.
Copyright 2022 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React from "react";
import StyledCheckbox, { CheckboxStyle } from "../../elements/StyledCheckbox";
import StyledCheckbox from "../../elements/StyledCheckbox";
import DeviceTile, { type DeviceTileProps } from "./DeviceTile";
interface Props extends DeviceTileProps {
@@ -21,7 +21,6 @@ const SelectableDeviceTile: React.FC<Props> = ({ children, device, isSelected, o
return (
<div className="mx_SelectableDeviceTile">
<StyledCheckbox
kind={CheckboxStyle.Solid}
checked={isSelected}
onChange={onSelect}
className="mx_SelectableDeviceTile_checkbox"

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024,2025 New Vector Ltd.
Copyright 2021-2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -14,7 +14,6 @@ import {
FavouriteSolidIcon,
} from "@vector-im/compound-design-tokens/assets/web/icons";
import { Icon as HashCircleIcon } from "../../../../../../res/img/element-icons/roomlist/hash-circle.svg";
import { _t } from "../../../../../languageHandler";
import SettingsStore from "../../../../../settings/SettingsStore";
import { SettingLevel } from "../../../../../settings/SettingLevel";
@@ -24,7 +23,7 @@ import { MetaSpace } from "../../../../../stores/spaces";
import PosthogTrackers from "../../../../../PosthogTrackers";
import SettingsTab from "../SettingsTab";
import { SettingsSection } from "../../shared/SettingsSection";
import { SettingsSubsection, SettingsSubsectionText } from "../../shared/SettingsSubsection";
import { SettingsSubsection } from "../../shared/SettingsSubsection";
import SdkConfig from "../../../../../SdkConfig";
type InteractionName = "WebSettingsSidebarTabSpacesCheckbox" | "WebQuickSettingsPinToSidebarCheckbox";
@@ -87,14 +86,10 @@ const SidebarUserSettingsTab: React.FC = () => {
onChange={onMetaSpaceChangeFactory(MetaSpace.Home, "WebSettingsSidebarTabSpacesCheckbox")}
className="mx_SidebarUserSettingsTab_checkbox"
disabled={homeEnabled}
description={_t("settings|sidebar|metaspaces_home_description")}
>
<SettingsSubsectionText>
<HomeSolidIcon />
{_t("common|home")}
</SettingsSubsectionText>
<SettingsSubsectionText>
{_t("settings|sidebar|metaspaces_home_description")}
</SettingsSubsectionText>
<HomeSolidIcon className="mx_SidebarUserSettingsTab_icon" />
{_t("common|home")}
</StyledCheckbox>
<StyledCheckbox
@@ -103,13 +98,9 @@ const SidebarUserSettingsTab: React.FC = () => {
onChange={onAllRoomsInHomeToggle}
className="mx_SidebarUserSettingsTab_checkbox mx_SidebarUserSettingsTab_homeAllRoomsCheckbox"
data-testid="mx_SidebarUserSettingsTab_homeAllRoomsCheckbox"
description={_t("settings|sidebar|metaspaces_home_all_rooms_description")}
>
<SettingsSubsectionText>
{_t("settings|sidebar|metaspaces_home_all_rooms")}
</SettingsSubsectionText>
<SettingsSubsectionText>
{_t("settings|sidebar|metaspaces_home_all_rooms_description")}
</SettingsSubsectionText>
{_t("settings|sidebar|metaspaces_home_all_rooms")}
</StyledCheckbox>
{!newRoomListEnabled && (
@@ -121,14 +112,10 @@ const SidebarUserSettingsTab: React.FC = () => {
"WebSettingsSidebarTabSpacesCheckbox",
)}
className="mx_SidebarUserSettingsTab_checkbox"
description={_t("settings|sidebar|metaspaces_favourites_description")}
>
<SettingsSubsectionText>
<FavouriteSolidIcon />
{_t("common|favourites")}
</SettingsSubsectionText>
<SettingsSubsectionText>
{_t("settings|sidebar|metaspaces_favourites_description")}
</SettingsSubsectionText>
<FavouriteSolidIcon className="mx_SidebarUserSettingsTab_icon" />
{_t("common|favourites")}
</StyledCheckbox>
<StyledCheckbox
@@ -138,14 +125,10 @@ const SidebarUserSettingsTab: React.FC = () => {
"WebSettingsSidebarTabSpacesCheckbox",
)}
className="mx_SidebarUserSettingsTab_checkbox"
description={_t("settings|sidebar|metaspaces_people_description")}
>
<SettingsSubsectionText>
<UserProfileSolidIcon />
{_t("common|people")}
</SettingsSubsectionText>
<SettingsSubsectionText>
{_t("settings|sidebar|metaspaces_people_description")}
</SettingsSubsectionText>
<UserProfileSolidIcon className="mx_SidebarUserSettingsTab_icon" />
{_t("common|people")}
</StyledCheckbox>
</>
)}
@@ -154,14 +137,9 @@ const SidebarUserSettingsTab: React.FC = () => {
checked={!!orphansEnabled}
onChange={onMetaSpaceChangeFactory(MetaSpace.Orphans, "WebSettingsSidebarTabSpacesCheckbox")}
className="mx_SidebarUserSettingsTab_checkbox"
description={_t("settings|sidebar|metaspaces_orphans_description")}
>
<SettingsSubsectionText>
<HashCircleIcon />
{_t("settings|sidebar|metaspaces_orphans")}
</SettingsSubsectionText>
<SettingsSubsectionText>
{_t("settings|sidebar|metaspaces_orphans_description")}
</SettingsSubsectionText>
{_t("settings|sidebar|metaspaces_orphans")}
</StyledCheckbox>
{SettingsStore.getValue("feature_video_rooms") && (
<StyledCheckbox
@@ -171,12 +149,10 @@ const SidebarUserSettingsTab: React.FC = () => {
"WebSettingsSidebarTabSpacesCheckbox",
)}
className="mx_SidebarUserSettingsTab_checkbox"
description={conferenceSubsectionText}
>
<SettingsSubsectionText>
<VideoCallSolidIcon />
{_t("settings|sidebar|metaspaces_video_rooms")}
</SettingsSubsectionText>
<SettingsSubsectionText>{conferenceSubsectionText}</SettingsSubsectionText>
<VideoCallSolidIcon className="mx_SidebarUserSettingsTab_icon" />
{_t("settings|sidebar|metaspaces_video_rooms")}
</StyledCheckbox>
)}
</SettingsSubsection>

View File

@@ -1,5 +1,5 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2024,2025 New Vector Ltd.
Copyright 2021-2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -12,6 +12,7 @@ import {
OverflowHorizontalIcon,
UserProfileSolidIcon,
FavouriteSolidIcon,
PinSolidIcon,
} from "@vector-im/compound-design-tokens/assets/web/icons";
import { _t } from "../../../languageHandler";
@@ -25,7 +26,6 @@ import defaultDispatcher from "../../../dispatcher/dispatcher";
import { Action } from "../../../dispatcher/actions";
import { UserTab } from "../dialogs/UserTab";
import QuickThemeSwitcher from "./QuickThemeSwitcher";
import { Icon as PinUprightIcon } from "../../../../res/img/element-icons/room/pin-upright.svg";
import Modal from "../../../Modal";
import DevtoolsDialog from "../dialogs/DevtoolsDialog";
import { SdkContextClass } from "../../../contexts/SDKContext";
@@ -89,13 +89,12 @@ const QuickSettingsButton: React.FC<{
{!newRoomListEnabled && (
<>
<h4 className="mx_QuickSettingsButton_pinToSidebarHeading">
<PinUprightIcon className="mx_QuickSettingsButton_icon" />
<h4>
<PinSolidIcon className="mx_QuickSettingsButton_icon" />
{_t("quick_settings|metaspace_section")}
</h4>
<StyledCheckbox
className="mx_QuickSettingsButton_favouritesCheckbox"
className="mx_QuickSettingsButton_option"
checked={!!favouritesEnabled}
onChange={onMetaSpaceChangeFactory(
MetaSpace.Favourites,
@@ -106,7 +105,7 @@ const QuickSettingsButton: React.FC<{
{_t("common|favourites")}
</StyledCheckbox>
<StyledCheckbox
className="mx_QuickSettingsButton_peopleCheckbox"
className="mx_QuickSettingsButton_option"
checked={!!peopleEnabled}
onChange={onMetaSpaceChangeFactory(
MetaSpace.People,
@@ -117,7 +116,7 @@ const QuickSettingsButton: React.FC<{
{_t("common|people")}
</StyledCheckbox>
<AccessibleButton
className="mx_QuickSettingsButton_moreOptionsButton"
className="mx_QuickSettingsButton_moreOptionsButton mx_QuickSettingsButton_option"
onClick={() => {
closeMenu();
defaultDispatcher.dispatch({

View File

@@ -104,6 +104,7 @@
"reply": "Reply",
"reply_in_thread": "Reply in thread",
"report_content": "Report Content",
"report_room": "Report room",
"resend": "Resend",
"reset": "Reset",
"resume": "Resume",
@@ -1810,6 +1811,12 @@
"spam_or_propaganda": "Spam or propaganda",
"toxic_behaviour": "Toxic Behaviour"
},
"report_room": {
"description": "Report this room to your homeserver admin. This will send the room's unique ID, but if messages are encrypted, the administrator won't be able to read them or view shared files.",
"reason_placeholder": " Reason for reporting...",
"sent": "Your report was sent.",
"title": "Report Room"
},
"restore_key_backup_dialog": {
"count_of_decryption_failures": "Failed to decrypt %(failedCount)s sessions!",
"count_of_successfully_restored_keys": "Successfully restored %(sessionCount)s keys",

View File

@@ -3147,6 +3147,7 @@
"view": "Affiche le salon avec cette adresse",
"whois": "Affiche des informations à propos de lutilisateur"
},
"sliding_sync_legacy_no_longer_supported": "L'ancienne fonctionnalité Sliding Sync n'est plus prise en charge : veuillez vous déconnecter puis vous reconnecter pour activer la nouvelle fonctionnalité Sliding Sync",
"space": {
"add_existing_room_space": {
"create": "Voulez-vous plutôt ajouter un nouveau salon ?",

View File

@@ -1235,6 +1235,7 @@
"change": "Azonosítási kiszolgáló módosítása",
"change_prompt": "Bontja a kapcsolatot a(z) <current /> azonosítási kiszolgálóval, és inkább ehhez kapcsolódik: <new />?",
"change_server_prompt": "Ha nem szeretné a(z) <server /> kiszolgálót használnia kapcsolatok kereséséhez, és hogy megtalálják az ismerősei, akkor adjon meg egy másik azonosítási kiszolgálót.",
"changed": "Az azonosítási kiszolgáló megváltozott",
"checking": "Kiszolgáló ellenőrzése",
"description_connected": "Jelenleg a(z) <server></server> kiszolgálót használja a kapcsolatok kereséséhez, és hogy megtalálják az ismerősei. A használt azonosítási kiszolgálót alább tudja megváltoztatni.",
"description_disconnected": "Jelenleg nem használ azonosítási kiszolgálót. A kapcsolatok kereséséhez, és hogy megtalálják az ismerősei, adjon hozzá egy azonosítási kiszolgálót.",
@@ -4027,7 +4028,7 @@
"error_need_to_be_logged_in": "Be kell jelentkeznie.",
"error_unable_start_audio_stream_description": "A hangközvetítés indítása sikertelen.",
"error_unable_start_audio_stream_title": "Az élő adás indítása sikertelen",
"modal_data_warning": "Az ezen a képernyőn látható adatok megosztásra kerülnek ezzel: %(widgetDomain)s",
"modal_data_warning": "A lenti adatok megosztásra kerülnek ezzel: %(widgetDomain)s",
"modal_title_default": "Előugró kisalkalmazás",
"no_name": "Ismeretlen alkalmazás",
"open_id_permissions_dialog": {

View File

@@ -459,8 +459,8 @@
"all_chats": "Alle chatter",
"analytics": "Statistikk",
"and_n_others": {
"og %(count)s andre …": "other",
"og én annen …": "one"
"one": "og en annen...",
"other": "og %(count)s andre..."
},
"appearance": "Utseende",
"application": "Applikasjon",
@@ -1378,8 +1378,8 @@
"other": "Invitere %(user)s og %(count)s andre"
},
"items_and_n_others": {
"<Items/> og %(count)s andre": "other",
"<Items/> og én annen": "one"
"one": "<Items/> og en annen",
"other": "<Items/> og %(count)s andre"
},
"keyboard": {
"activate_button": "Aktiver valgt knapp",
@@ -2101,9 +2101,12 @@
"no_chats": "Ingen chatter ennå",
"no_chats_description": "Kom i gang ved å sende meldinger til noen eller ved å opprette et rom",
"no_chats_description_no_room_rights": "Kom i gang med å sende meldinger til noen",
"no_favourites": "Du har ikke favorittchat ennå",
"no_favourites_description": "Du kan legge til en chat til dine favoritter i chat-innstillingene",
"no_people": "Du har ikke direkte chatter med noen ennå",
"no_people_description": "Du kan fjerne merket for filtre for å se de andre chattene dine",
"no_rooms": "Du er ikke med i noen rom ennå",
"no_rooms_description": "Du kan fjerne merket for filtre for å se de andre chattene dine",
"no_unread": "Gratulerer! Du har ingen uleste meldinger",
"show_chats": "Vis alle chatter"
},
@@ -2531,7 +2534,9 @@
"title": "Avansert"
},
"delete_key_storage": {
"breadcrumb_page": "Slett nøkkellagring",
"confirm": "Slett nøkkellagring",
"description": "Hvis du sletter nøkkellagring, fjernes den kryptografiske identiteten og meldingsnøklene fra serveren og deaktivere følgende sikkerhetsfunksjoner:",
"list_first": "Du vil ikke ha kryptert meldingshistorikk på nye enheter",
"list_second": "Du vil miste tilgangen til de krypterte meldingene dine hvis du logger av %(brand)s overalt",
"title": "Er du sikker på at du vil slå av nøkkellagring og slette den?"
@@ -2541,7 +2546,9 @@
"device_not_verified_title": "Enhet er ikke verifisert",
"dialog_title": "<strong>Innstillinger:</strong> Kryptering",
"key_storage": {
"allow_key_storage": "Tillat lagring av nøkler"
"allow_key_storage": "Tillat lagring av nøkler",
"description": "Lagre din kryptografiske identitet og meldingsnøkler sikkert på serveren. Dette lar deg se meldingsloggen din på alle nye enheter. <a>Lær mer </a>",
"title": "Nøkkellager"
},
"recovery": {
"change_recovery_confirm_button": "Bekreft ny gjenopprettingsnøkkel",
@@ -3636,12 +3643,12 @@
"send_state_sent": "Meldingen ble sendt",
"summary": {
"banned": {
"ble bannlyst": "one",
"ble bannlyst %(count)s ganger": "other"
"one": "ble utestengt",
"other": "ble utestengt %(count)s ganger"
},
"banned_multiple": {
"ble bannlyst": "one",
"ble bannlyst %(count)s ganger": "other"
"one": "ble utestengt",
"other": "ble utestengt %(count)s ganger"
},
"changed_avatar": {
"one": "%(oneUser)sendret profilbildet deres",
@@ -3652,8 +3659,8 @@
"other": "%(severalUsers)sendret profilbildet deres %(count)s ganger"
},
"changed_name": {
"%(oneUser)s endret navnet sitt": "one",
"%(oneUser)sendret navnet sitt %(count)s ganger": "other"
"one": "%(oneUser)s endret navn",
"other": "%(oneUser)s endret navn %(count)s ganger"
},
"changed_name_multiple": {
"%(severalUsers)s endret navnene sine": "one"
@@ -3706,12 +3713,12 @@
"other": "ble fjernet %(count)s ganger"
},
"left": {
"%(oneUser)s forlot rommet %(count)s ganger": "other",
"%(oneUser)s forlot rommet": "one"
"one": "%(oneUser)s forlot",
"other": "%(oneUser)s forlot %(count)s ganger"
},
"left_multiple": {
"%(severalUsers)s forlot rommet %(count)s ganger": "other",
"%(severalUsers)s forlot rommet": "one"
"one": "%(severalUsers)s forlot",
"other": "%(severalUsers)s forlot %(count)s ganger"
},
"no_change": {
"one": "%(oneUser)sgjorde ingen endringer",
@@ -3762,12 +3769,12 @@
"other": "%(severalUsers)sendret serverens ACLer %(count)s ganger"
},
"unbanned": {
"fikk bannlysningen sin opphevet %(count)s ganger": "other",
"fikk bannlysningen sin opphevet": "one"
"one": "fikk opphevet forbudet",
"other": "fikk opphevet forbudet %(count)s ganger"
},
"unbanned_multiple": {
"fikk bannlysningene sine opphevet %(count)s ganger": "other",
"fikk bannlysningene sine opphevet": "one"
"one": "fikk opphevet forbudet",
"other": "fikk opphevet forbudet %(count)s ganger"
}
},
"thread_info_basic": "Fra en tråd",

View File

@@ -64,6 +64,7 @@
"go": "Уперед",
"go_back": "Назад",
"got_it": "Зрозуміло",
"hide": "Сховати",
"hide_advanced": "Сховати розширені",
"hold": "Утримувати",
"ignore": "Ігнорувати",
@@ -2100,6 +2101,19 @@
"add_space_label": "Додати простір",
"breadcrumbs_empty": "Немає недавно відвіданих кімнат",
"breadcrumbs_label": "Недавно відвідані кімнати",
"empty": {
"no_chats": "Ще немає бесід",
"no_chats_description": "Почніть користування, надіславши комусь повідомлення або створивши кімнату",
"no_chats_description_no_room_rights": "Розпочніть користування, написавши комусь повідомлення",
"no_favourites": "У вас ще немає обраних бесід",
"no_favourites_description": "Ви можете додати бесіду до обраних у її налаштуваннях",
"no_people": "У вас ще немає особистих бесід",
"no_people_description": "Ви можете очистити фільтри, щоб побачити інші ваші бесіди",
"no_rooms": "Ви ще не входили до кімнат",
"no_rooms_description": "Ви можете очистити фільтри, щоб побачити інші ваші бесіди",
"no_unread": "Вітаємо! У вас немає непрочитаних повідомлень",
"show_chats": "Показати всі бесіди"
},
"failed_add_tag": "Не вдалось додати до кімнати мітку %(tagName)s",
"failed_remove_tag": "Не вдалося прибрати з кімнати мітку %(tagName)s",
"failed_set_dm_tag": "Не вдалося встановити мітку особистого повідомлення",
@@ -3138,6 +3152,7 @@
"view": "Перегляд кімнати з вказаною адресою",
"whois": "Показує відомості про користувача"
},
"sliding_sync_legacy_no_longer_supported": "Застаріла ковзна синхронізація більше не підтримується: вийдіть і ввійдіть знову, щоб увімкнути новий прапорець ковзної синхронізації",
"space": {
"add_existing_room_space": {
"create": "Хочете додати нову кімнату натомість?",

View File

@@ -8,8 +8,6 @@ Please see LICENSE files in the repository root for full details.
*/
import { logger } from "matrix-js-sdk/src/logger";
// XXX: This feels wrong.
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
import { PushRuleActionName } from "matrix-js-sdk/src/matrix";
import SettingController from "./SettingController";
@@ -20,8 +18,7 @@ import { type SettingLevel } from "../SettingLevel";
// default action on this rule is dont_notify, but it could be something else
export function isPushNotifyDisabled(): boolean {
// Return the value of the master push rule as a default
const processor = new PushProcessor(MatrixClientPeg.safeGet());
const masterRule = processor.getPushRuleById(".m.rule.master");
const masterRule = MatrixClientPeg.safeGet().pushProcessor.getPushRuleById(".m.rule.master");
if (!masterRule) {
logger.warn("No master push rule! Notifications are disabled for this user.");

View File

@@ -8,50 +8,50 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { type ReactNode } from "react";
import * as utils from "matrix-js-sdk/src/utils";
import { MatrixError, JoinRule, type Room, type MatrixEvent } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { logger } from "matrix-js-sdk/src/logger";
import { type ViewRoom as ViewRoomEvent } from "@matrix-org/analytics-events/types/typescript/ViewRoom";
import { type JoinedRoom as JoinedRoomEvent } from "@matrix-org/analytics-events/types/typescript/JoinedRoom";
import { type Optional } from "matrix-events-sdk";
import EventEmitter from "events";
import { type ViewRoom as ViewRoomEvent } from "@matrix-org/analytics-events/types/typescript/ViewRoom";
import {
RoomViewLifecycle,
type ViewRoomOpts,
} from "@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle";
import EventEmitter from "events";
import { type Optional } from "matrix-events-sdk";
import { logger } from "matrix-js-sdk/src/logger";
import { JoinRule, MatrixError, type MatrixEvent, type Room } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import * as utils from "matrix-js-sdk/src/utils";
import React, { type ReactNode } from "react";
import { type MatrixDispatcher } from "../dispatcher/dispatcher";
import { MatrixClientPeg } from "../MatrixClientPeg";
import Modal from "../Modal";
import { _t } from "../languageHandler";
import { getCachedRoomIDForAlias, storeRoomAliasInCache } from "../RoomAliasCache";
import { Action } from "../dispatcher/actions";
import { retry } from "../utils/promise";
import ErrorDialog from "../components/views/dialogs/ErrorDialog";
import { TimelineRenderingType } from "../contexts/RoomContext";
import { type ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload";
import DMRoomMap from "../utils/DMRoomMap";
import { isMetaSpace, MetaSpace } from "./spaces";
import { type SdkContextClass } from "../contexts/SDKContext";
import { Action } from "../dispatcher/actions";
import { type MatrixDispatcher } from "../dispatcher/dispatcher";
import { type ActionPayload } from "../dispatcher/payloads";
import { type ActiveRoomChangedPayload } from "../dispatcher/payloads/ActiveRoomChangedPayload";
import { type CancelAskToJoinPayload } from "../dispatcher/payloads/CancelAskToJoinPayload";
import { type JoinRoomErrorPayload } from "../dispatcher/payloads/JoinRoomErrorPayload";
import { type JoinRoomPayload } from "../dispatcher/payloads/JoinRoomPayload";
import { type JoinRoomReadyPayload } from "../dispatcher/payloads/JoinRoomReadyPayload";
import { type JoinRoomErrorPayload } from "../dispatcher/payloads/JoinRoomErrorPayload";
import { type ViewRoomErrorPayload } from "../dispatcher/payloads/ViewRoomErrorPayload";
import ErrorDialog from "../components/views/dialogs/ErrorDialog";
import { type ActiveRoomChangedPayload } from "../dispatcher/payloads/ActiveRoomChangedPayload";
import SettingsStore from "../settings/SettingsStore";
import { awaitRoomDownSync } from "../utils/RoomUpgrade";
import { UPDATE_EVENT } from "./AsyncStore";
import { type SdkContextClass } from "../contexts/SDKContext";
import { CallStore } from "./CallStore";
import { type ThreadPayload } from "../dispatcher/payloads/ThreadPayload";
import { type ActionPayload } from "../dispatcher/payloads";
import { type CancelAskToJoinPayload } from "../dispatcher/payloads/CancelAskToJoinPayload";
import { type SubmitAskToJoinPayload } from "../dispatcher/payloads/SubmitAskToJoinPayload";
import { ModuleRunner } from "../modules/ModuleRunner";
import { setMarkedUnreadState } from "../utils/notifications";
import { type ThreadPayload } from "../dispatcher/payloads/ThreadPayload";
import { type ViewRoomErrorPayload } from "../dispatcher/payloads/ViewRoomErrorPayload";
import { type ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload";
import { _t } from "../languageHandler";
import { MatrixClientPeg } from "../MatrixClientPeg";
import Modal from "../Modal";
import { ConnectionState, ElementCall } from "../models/Call";
import { ModuleRunner } from "../modules/ModuleRunner";
import { getCachedRoomIDForAlias, storeRoomAliasInCache } from "../RoomAliasCache";
import SettingsStore from "../settings/SettingsStore";
import DMRoomMap from "../utils/DMRoomMap";
import { setMarkedUnreadState } from "../utils/notifications";
import { retry } from "../utils/promise";
import { awaitRoomDownSync } from "../utils/RoomUpgrade";
import { isVideoRoom } from "../utils/video-rooms";
import { UPDATE_EVENT } from "./AsyncStore";
import { CallStore } from "./CallStore";
import { isMetaSpace, MetaSpace } from "./spaces";
const NUM_JOIN_RETRY = 5;
@@ -265,14 +265,14 @@ export class RoomViewStore extends EventEmitter {
numMembers > 1000
? "MoreThanAThousand"
: numMembers > 100
? "OneHundredAndOneToAThousand"
: numMembers > 10
? "ElevenToOneHundred"
: numMembers > 2
? "ThreeToTen"
: numMembers > 1
? "Two"
: "One";
? "OneHundredAndOneToAThousand"
: numMembers > 10
? "ElevenToOneHundred"
: numMembers > 2
? "ThreeToTen"
: numMembers > 1
? "Two"
: "One";
this.stores.posthogAnalytics.trackEvent<JoinedRoomEvent>({
eventName: "JoinedRoom",
@@ -537,6 +537,33 @@ export class RoomViewStore extends EventEmitter {
metricsTrigger: payload.metricsTrigger,
});
} catch (err) {
if (err instanceof MatrixError) {
if (err.errcode === "RE_JKI_JOIN_POLICY_URL") {
const redirect_url = [
window.location.origin,
window.location.pathname.replace(/\/$/, ""), // Remove trailing slash if present
"/static/join_room_redirect.html"
].join("")
const url = err.data["re.jki.join_policy_url"] + "?redirect_url=" + redirect_url;
window.addEventListener("message", (event) => {
if (event.data instanceof Object) {
if ("action" in event.data && event.data.action == "join") {
payload.opts = payload.opts || {};
payload.opts.token = event.data.token;
this.joinRoom(payload);
}
}
})
window.open(url, "join_window");
return;
}
}
this.dis?.dispatch({
action: Action.JoinRoomError,
roomId,

View File

@@ -11,7 +11,7 @@ import { RoomNotificationStateStore } from "../../../notifications/RoomNotificat
export class UnreadFilter implements Filter {
public matches(room: Room): boolean {
return RoomNotificationStateStore.instance.getRoomState(room).isUnread;
return RoomNotificationStateStore.instance.getRoomState(room).hasUnreadCount;
}
public get key(): FilterKey.UnreadFilter {

View File

@@ -7,7 +7,6 @@ Please see LICENSE files in the repository root for full details.
*/
import React, { StrictMode } from "react";
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
import { type MatrixClient, type MatrixEvent, RuleId } from "matrix-js-sdk/src/matrix";
import { TooltipProvider } from "@vector-im/compound-web";
@@ -119,11 +118,10 @@ export function pillifyLinks(
}
if (roomNotifTextNodes.length > 0) {
const pushProcessor = new PushProcessor(matrixClient);
const atRoomRule = pushProcessor.getPushRuleById(
const atRoomRule = matrixClient.pushProcessor.getPushRuleById(
mxEvent.getContent()["m.mentions"] !== undefined ? RuleId.IsRoomMention : RuleId.AtRoomNotification,
);
if (atRoomRule && pushProcessor.ruleMatchesEvent(atRoomRule, mxEvent)) {
if (atRoomRule && matrixClient.pushProcessor.ruleMatchesEvent(atRoomRule, mxEvent)) {
// Now replace all those nodes with Pills
for (const roomNotifTextNode of roomNotifTextNodes) {
// Set the next node to be processed to the one after the node

View File

@@ -12,15 +12,16 @@ import {
EventType,
type RuleId,
type IAnnotatedPushRule,
type IPushRule,
type PushRuleKind,
} from "matrix-js-sdk/src/matrix";
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
import { logger } from "matrix-js-sdk/src/logger";
import { VectorPushRulesDefinitions, type VectorPushRuleDefinition } from "../../notifications";
import { updateExistingPushRulesWithActions } from "./updatePushRuleActions";
const pushRuleAndKindToAnnotated = (
ruleAndKind: ReturnType<PushProcessor["getPushRuleAndKindById"]>,
ruleAndKind: { rule: IPushRule; kind: PushRuleKind } | null,
): IAnnotatedPushRule | undefined =>
ruleAndKind
? {
@@ -34,23 +35,21 @@ const pushRuleAndKindToAnnotated = (
* And updates any that are out of sync
* Ignores ruleIds that do not exist for the user
* @param matrixClient - cli
* @param pushProcessor - processor used to retrieve current state of rules
* @param ruleId - primary rule
* @param definition - VectorPushRuleDefinition of the primary rule
*/
const monitorSyncedRule = async (
matrixClient: MatrixClient,
pushProcessor: PushProcessor,
ruleId: RuleId | string,
definition: VectorPushRuleDefinition,
): Promise<void> => {
const primaryRule = pushRuleAndKindToAnnotated(pushProcessor.getPushRuleAndKindById(ruleId));
const primaryRule = pushRuleAndKindToAnnotated(matrixClient.pushProcessor.getPushRuleAndKindById(ruleId));
if (!primaryRule) {
return;
}
const syncedRules: IAnnotatedPushRule[] | undefined = definition.syncedRuleIds
?.map((ruleId) => pushRuleAndKindToAnnotated(pushProcessor.getPushRuleAndKindById(ruleId)))
?.map((ruleId) => pushRuleAndKindToAnnotated(matrixClient.pushProcessor.getPushRuleAndKindById(ruleId)))
.filter((n?: IAnnotatedPushRule): n is IAnnotatedPushRule => Boolean(n));
// no synced rules to manage
@@ -94,11 +93,10 @@ export const monitorSyncedPushRules = async (
if (accountDataEvent?.getType() !== EventType.PushRules) {
return;
}
const pushProcessor = new PushProcessor(matrixClient);
Object.entries(VectorPushRulesDefinitions).forEach(async ([ruleId, definition]) => {
try {
await monitorSyncedRule(matrixClient, pushProcessor, ruleId, definition);
await monitorSyncedRule(matrixClient, ruleId, definition);
} catch (error) {
logger.error(`Failed to fully synchronise push rules for ${ruleId}`, error);
}

View File

@@ -7,7 +7,6 @@ Please see LICENSE files in the repository root for full details.
*/
import { type MatrixClient, type IPushRule, type PushRuleAction, type PushRuleKind } from "matrix-js-sdk/src/matrix";
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
/**
* Sets the actions for a given push rule id and kind
@@ -51,10 +50,8 @@ export const updateExistingPushRulesWithActions = async (
ruleIds?: IPushRule["rule_id"][],
actions?: PushRuleAction[],
): Promise<void> => {
const pushProcessor = new PushProcessor(matrixClient);
const rules: PushRuleAndKind[] | undefined = ruleIds
?.map((ruleId) => pushProcessor.getPushRuleAndKindById(ruleId))
?.map((ruleId) => matrixClient.pushProcessor.getPushRuleAndKindById(ruleId))
.filter((n: PushRuleAndKind | null): n is PushRuleAndKind => Boolean(n));
if (!rules?.length) {

View File

@@ -22,6 +22,7 @@ import { mocked } from "jest-mock";
import { CallEventHandlerEvent } from "matrix-js-sdk/src/webrtc/callEventHandler";
import fetchMock from "fetch-mock-jest";
import { waitFor } from "jest-matrix-react";
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
import LegacyCallHandler, {
AudioID,
@@ -473,6 +474,9 @@ describe("LegacyCallHandler without third party protocols", () => {
throw new Error("Endpoint unsupported.");
};
// @ts-expect-error
MatrixClientPeg.safeGet().pushProcessor = new PushProcessor(MatrixClientPeg.safeGet());
audioElement = document.createElement("audio");
audioElement.id = "remoteAudio";
document.body.appendChild(audioElement);

View File

@@ -19,6 +19,7 @@ import {
import { MediaHandler } from "matrix-js-sdk/src/webrtc/mediaHandler";
import { logger } from "matrix-js-sdk/src/logger";
import userEvent from "@testing-library/user-event";
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
import LoggedInView from "../../../../src/components/structures/LoggedInView";
import { SDKContext } from "../../../../src/contexts/SDKContext";
@@ -75,6 +76,8 @@ describe("<LoggedInView />", () => {
jest.clearAllMocks();
mockClient.getMediaHandler.mockReturnValue(mediaHandler);
mockClient.setPushRuleActions.mockReset().mockResolvedValue({});
// @ts-expect-error
mockClient.pushProcessor = new PushProcessor(mockClient);
});
describe("synced push rules", () => {

View File

@@ -22,7 +22,12 @@ import { logger } from "matrix-js-sdk/src/logger";
import { OidcError } from "matrix-js-sdk/src/oidc/error";
import { type BearerTokenResponse } from "matrix-js-sdk/src/oidc/validate";
import { defer, type IDeferred, sleep } from "matrix-js-sdk/src/utils";
import { CryptoEvent, UserVerificationStatus } from "matrix-js-sdk/src/crypto-api";
import {
CryptoEvent,
type DeviceVerificationStatus,
UserVerificationStatus,
type CryptoApi,
} from "matrix-js-sdk/src/crypto-api";
import MatrixChat from "../../../../src/components/structures/MatrixChat";
import * as StorageAccess from "../../../../src/utils/StorageAccess";
@@ -62,6 +67,7 @@ import { UIFeature } from "../../../../src/settings/UIFeature";
import AutoDiscoveryUtils from "../../../../src/utils/AutoDiscoveryUtils";
import { type ValidatedServerConfig } from "../../../../src/utils/ValidatedServerConfig";
import Modal from "../../../../src/Modal.tsx";
import { SetupEncryptionStore } from "../../../../src/stores/SetupEncryptionStore.ts";
jest.mock("matrix-js-sdk/src/oidc/authorize", () => ({
completeAuthorizationCodeGrant: jest.fn(),
@@ -894,13 +900,92 @@ describe("<MatrixChat />", () => {
});
describe("unskippable verification", () => {
it("should show the complete security screen if unskippable verification is enabled", async () => {
beforeEach(() => {
// Force verification is turned on in settings
defaultProps.config.force_verification = true;
// And this device is being force-verified (because it logged in after
// enforcement was turned on).
localStorage.setItem("must_verify_device", "true");
// lostKeys returns false, meaning there are other devices to verify against
const realStore = SetupEncryptionStore.sharedInstance();
jest.spyOn(realStore, "lostKeys").mockReturnValue(false);
});
afterEach(() => {
jest.restoreAllMocks();
// Reset things back to how they were before we started
defaultProps.config.force_verification = false;
localStorage.removeItem("must_verify_device");
});
it("should show the complete security screen if unskippable verification is enabled", async () => {
// Given we have force verification on, and an existing logged-in session
// that is not verified (see beforeEach())
// When we render MatrixChat
getComponent();
await screen.findByRole("heading", { name: "Unable to verify this device", level: 1 });
// Then we are asked to verify our device
await screen.findByRole("heading", { name: "Verify this device", level: 1 });
// Sanity: we are not racing with another screen update, so this heading stays visible
await screen.findByRole("heading", { name: "Verify this device", level: 1 });
});
it("should not open app after cancelling device verify if unskippable verification is on", async () => {
// See https://github.com/element-hq/element-web/issues/29230
// We used to allow bypassing force verification by choosing "Verify with
// another device" and not completing the verification.
// Given we have force verification on, and an existing logged-in session
// that is not verified (see beforeEach())
// And our crypto is set up
mockClient.getCrypto.mockReturnValue(createMockCrypto());
// And MatrixChat is rendered
getComponent();
// When we click "Verify with another device"
await screen.findByRole("heading", { name: "Verify this device", level: 1 });
const verify = screen.getByRole("button", { name: "Verify with another device" });
act(() => verify.click());
// And close the device verification dialog
const closeButton = await screen.findByRole("button", { name: "Close dialog" });
act(() => closeButton.click());
// Then we are not allowed in - we are still being asked to verify
await screen.findByRole("heading", { name: "Verify this device", level: 1 });
});
function createMockCrypto(): CryptoApi {
return {
getVersion: jest.fn().mockReturnValue("Version 0"),
getVerificationRequestsToDeviceInProgress: jest.fn().mockReturnValue([]),
getUserDeviceInfo: jest.fn().mockReturnValue({
get: jest
.fn()
.mockReturnValue(
new Map([
["devid", { dehydrated: false, getIdentityKey: jest.fn().mockReturnValue("k") }],
]),
),
}),
getUserVerificationStatus: jest
.fn()
.mockResolvedValue(new UserVerificationStatus(true, true, false)),
setDeviceIsolationMode: jest.fn(),
isDehydrationSupported: jest.fn().mockReturnValue(false),
getDeviceVerificationStatus: jest
.fn()
.mockResolvedValue({ signedByOwner: true } as DeviceVerificationStatus),
isCrossSigningReady: jest.fn().mockReturnValue(false),
requestOwnUserVerification: jest.fn().mockResolvedValue({ cancel: jest.fn() }),
} as any;
}
});
});

View File

@@ -58,6 +58,7 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
role="tree"
>
<li
aria-labelledby=":r8:"
class="mx_SpaceHierarchy_roomTileWrapper"
role="treeitem"
>
@@ -86,7 +87,11 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
<div
class="mx_SpaceHierarchy_roomTile_name"
>
Unnamed Room
<span
id=":r8:"
>
Unnamed Room
</span>
</div>
<div
class="mx_SpaceHierarchy_roomTile_info"
@@ -104,30 +109,54 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
>
Join
</div>
<span
class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
<form
class="_root_19upo_16"
>
<input
id="checkbox_vY7Q4uEh9K"
tabindex="0"
type="checkbox"
/>
<label
for="checkbox_vY7Q4uEh9K"
<div
class="_inline-field_19upo_32"
>
<div
class="mx_Checkbox_background"
class="_inline-field-control_19upo_44"
>
<div
class="mx_Checkbox_checkmark"
/>
class="_container_1hel1_10"
>
<input
aria-labelledby=":r8:"
class="_input_1hel1_18"
id="checkbox_MwbPDmfGtm"
role="presentation"
tabindex="-1"
type="checkbox"
/>
<div
class="_ui_1hel1_19"
>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
/>
</svg>
</div>
</div>
</div>
</label>
</span>
<div
class="_inline-field-body_19upo_38"
/>
</div>
</form>
</div>
</div>
</li>
<li
aria-labelledby=":rc:"
class="mx_SpaceHierarchy_roomTileWrapper"
role="treeitem"
>
@@ -156,7 +185,11 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
<div
class="mx_SpaceHierarchy_roomTile_name"
>
Unnamed Room
<span
id=":rc:"
>
Unnamed Room
</span>
</div>
<div
class="mx_SpaceHierarchy_roomTile_info"
@@ -174,30 +207,54 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
>
Join
</div>
<span
class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
<form
class="_root_19upo_16"
>
<input
id="checkbox_38QgU2Pomx"
tabindex="-1"
type="checkbox"
/>
<label
for="checkbox_38QgU2Pomx"
<div
class="_inline-field_19upo_32"
>
<div
class="mx_Checkbox_background"
class="_inline-field-control_19upo_44"
>
<div
class="mx_Checkbox_checkmark"
/>
class="_container_1hel1_10"
>
<input
aria-labelledby=":rc:"
class="_input_1hel1_18"
id="checkbox_GQvdMWe954"
role="presentation"
tabindex="-1"
type="checkbox"
/>
<div
class="_ui_1hel1_19"
>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
/>
</svg>
</div>
</div>
</div>
</label>
</span>
<div
class="_inline-field-body_19upo_38"
/>
</div>
</form>
</div>
</div>
</li>
<li
aria-labelledby=":rg:"
class="mx_SpaceHierarchy_roomTileWrapper"
role="treeitem"
>
@@ -226,7 +283,11 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
<div
class="mx_SpaceHierarchy_roomTile_name"
>
Knock room
<span
id=":rg:"
>
Knock room
</span>
</div>
<div
class="mx_SpaceHierarchy_roomTile_info"
@@ -244,31 +305,55 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
>
View
</div>
<span
class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
<form
class="_root_19upo_16"
>
<input
id="checkbox_wKpa6hpi3Y"
tabindex="-1"
type="checkbox"
/>
<label
for="checkbox_wKpa6hpi3Y"
<div
class="_inline-field_19upo_32"
>
<div
class="mx_Checkbox_background"
class="_inline-field-control_19upo_44"
>
<div
class="mx_Checkbox_checkmark"
/>
class="_container_1hel1_10"
>
<input
aria-labelledby=":rg:"
class="_input_1hel1_18"
id="checkbox_DVIAu5CsiH"
role="presentation"
tabindex="-1"
type="checkbox"
/>
<div
class="_ui_1hel1_19"
>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
/>
</svg>
</div>
</div>
</div>
</label>
</span>
<div
class="_inline-field-body_19upo_38"
/>
</div>
</form>
</div>
</div>
</li>
<li
aria-expanded="true"
aria-labelledby=":rk:"
class="mx_SpaceHierarchy_roomTileWrapper"
role="treeitem"
>
@@ -297,7 +382,11 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
<div
class="mx_SpaceHierarchy_roomTile_name"
>
Nested space
<span
id=":rk:"
>
Nested space
</span>
</div>
<div
class="mx_SpaceHierarchy_roomTile_info"
@@ -315,26 +404,49 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
>
Join
</div>
<span
class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
<form
class="_root_19upo_16"
>
<input
id="checkbox_EetmBG4yVC"
tabindex="-1"
type="checkbox"
/>
<label
for="checkbox_EetmBG4yVC"
<div
class="_inline-field_19upo_32"
>
<div
class="mx_Checkbox_background"
class="_inline-field-control_19upo_44"
>
<div
class="mx_Checkbox_checkmark"
/>
class="_container_1hel1_10"
>
<input
aria-labelledby=":rk:"
class="_input_1hel1_18"
id="checkbox_RD7nyrA2oh"
role="presentation"
tabindex="-1"
type="checkbox"
/>
<div
class="_ui_1hel1_19"
>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
/>
</svg>
</div>
</div>
</div>
</label>
</span>
<div
class="_inline-field-body_19upo_38"
/>
</div>
</form>
</div>
<div
class="mx_SpaceHierarchy_subspace_toggle mx_SpaceHierarchy_subspace_toggle_shown"
@@ -346,6 +458,7 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
/>
</li>
<li
aria-labelledby=":ro:"
class="mx_SpaceHierarchy_roomTileWrapper"
role="treeitem"
>
@@ -374,7 +487,11 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
<div
class="mx_SpaceHierarchy_roomTile_name"
>
Nested room
<span
id=":ro:"
>
Nested room
</span>
</div>
<div
class="mx_SpaceHierarchy_roomTile_info"
@@ -393,30 +510,53 @@ exports[`SpaceHierarchy <SpaceHierarchy /> renders 1`] = `
Join
</div>
<span
aria-labelledby=":r8:"
aria-labelledby=":rp:"
tabindex="0"
>
<span
class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
<form
class="_root_19upo_16"
>
<input
disabled=""
id="checkbox_eEefiPqpMR"
tabindex="-1"
type="checkbox"
/>
<label
for="checkbox_eEefiPqpMR"
<div
class="_inline-field_19upo_32"
>
<div
class="mx_Checkbox_background"
class="_inline-field-control_19upo_44"
>
<div
class="mx_Checkbox_checkmark"
/>
class="_container_1hel1_10"
>
<input
aria-labelledby=":ro:"
class="_input_1hel1_18"
disabled=""
id="checkbox_jWVJIPauy1"
role="presentation"
tabindex="-1"
type="checkbox"
/>
<div
class="_ui_1hel1_19"
>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
/>
</svg>
</div>
</div>
</div>
</label>
</span>
<div
class="_inline-field-body_19upo_38"
/>
</div>
</form>
</span>
</div>
</div>

View File

@@ -162,6 +162,29 @@ describe("RoomListViewModel", () => {
expect(vm.current.activePrimaryFilter).toEqual(vm.current.primaryFilters[i]);
});
it("should remove any active primary filters when secondary filter is changed", async () => {
const { fn } = mockAndCreateRooms();
const { result: vm } = renderHook(() => useRoomListViewModel());
// Let's first toggle the People filter
const i = vm.current.primaryFilters.findIndex((f) => f.name === "People");
act(() => {
vm.current.primaryFilters[i].toggle();
});
expect(vm.current.primaryFilters[i].active).toEqual(true);
// Let's say we toggle the mentions secondary filter
act(() => {
vm.current.activateSecondaryFilter(SecondaryFilters.MentionsOnly);
});
// Primary filer should have been unapplied
expect(vm.current.primaryFilters[i].active).toEqual(false);
// RLS call must include only the secondary filter
expect(fn).toHaveBeenLastCalledWith(expect.arrayContaining([FilterKey.MentionsFilter]));
});
const testcases: Array<[string, { secondary: SecondaryFilters; filterKey: FilterKey }, string]> = [
[
"Mentions only",
@@ -291,34 +314,51 @@ describe("RoomListViewModel", () => {
});
});
describe("active index", () => {
it("should recalculate active index when list of rooms change", () => {
describe("Sticky room and active index", () => {
function expectActiveRoom(vm: ReturnType<typeof useRoomListViewModel>, i: number, roomId: string) {
expect(vm.activeIndex).toEqual(i);
expect(vm.rooms[i].roomId).toEqual(roomId);
}
it("active room and active index are retained on order change", () => {
const { rooms } = mockAndCreateRooms();
// Let's say that the first room is the active room initially
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockImplementation(() => rooms[0].roomId);
// Let's say that the room at index 5 is active
const roomId = rooms[5].roomId;
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockImplementation(() => roomId);
const { result: vm } = renderHook(() => useRoomListViewModel());
expect(vm.current.activeIndex).toEqual(0);
expect(vm.current.activeIndex).toEqual(5);
// Let's say that a new room is added and that becomes active
const newRoom = mkStubRoom("bar:matrix.org", "Bar", undefined);
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockImplementation(() => newRoom.roomId);
rooms.push(newRoom);
// Let's say that room at index 9 moves to index 5
const room9 = rooms[9];
rooms.splice(9, 1);
rooms.splice(5, 0, room9);
act(() => RoomListStoreV3.instance.emit(LISTS_UPDATE_EVENT));
// Now the active room should be the last room which we just added
expect(vm.current.activeIndex).toEqual(rooms.length - 1);
// Active room index should still be 5
expectActiveRoom(vm.current, 5, roomId);
// Let's add 2 new rooms from index 0
const newRoom1 = mkStubRoom("bar1:matrix.org", "Bar 1", undefined);
const newRoom2 = mkStubRoom("bar2:matrix.org", "Bar 2", undefined);
rooms.unshift(newRoom1, newRoom2);
act(() => RoomListStoreV3.instance.emit(LISTS_UPDATE_EVENT));
// Active room index should still be 5
expectActiveRoom(vm.current, 5, roomId);
});
it("should recalculate active index when active room changes", () => {
it("active room and active index are updated when another room is opened", () => {
const { rooms } = mockAndCreateRooms();
const roomId = rooms[5].roomId;
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockImplementation(() => roomId);
const { result: vm } = renderHook(() => useRoomListViewModel());
expectActiveRoom(vm.current, 5, roomId);
// No active room yet
expect(vm.current.activeIndex).toBeUndefined();
// Let's say that room at index 5 becomes active
const room = rooms[5];
// Let's say that room at index 9 becomes active
const room = rooms[9];
act(() => {
dispatcher.dispatch(
{
@@ -330,8 +370,76 @@ describe("RoomListViewModel", () => {
);
});
// We expect index 5 to be active now
expect(vm.current.activeIndex).toEqual(5);
// Active room index should change to reflect new room
expectActiveRoom(vm.current, 9, room.roomId);
});
it("active room and active index are updated when active index spills out of rooms array bounds", () => {
const { rooms } = mockAndCreateRooms();
// Let's say that the room at index 5 is active
const roomId = rooms[5].roomId;
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockImplementation(() => roomId);
const { result: vm } = renderHook(() => useRoomListViewModel());
expectActiveRoom(vm.current, 5, roomId);
// Let's say that we remove rooms from the start of the array
for (let i = 0; i < 4; ++i) {
// We should be able to do 4 deletions before we run out of rooms
rooms.splice(0, 1);
act(() => RoomListStoreV3.instance.emit(LISTS_UPDATE_EVENT));
expectActiveRoom(vm.current, 5, roomId);
}
// If we remove one more room from the start, there's not going to be enough rooms
// to maintain the active index.
rooms.splice(0, 1);
act(() => RoomListStoreV3.instance.emit(LISTS_UPDATE_EVENT));
expectActiveRoom(vm.current, 0, roomId);
});
it("active room and active index are retained when rooms that appear after the active room are deleted", () => {
const { rooms } = mockAndCreateRooms();
// Let's say that the room at index 5 is active
const roomId = rooms[5].roomId;
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockImplementation(() => roomId);
const { result: vm } = renderHook(() => useRoomListViewModel());
expectActiveRoom(vm.current, 5, roomId);
// Let's say that we remove rooms from the start of the array
for (let i = 0; i < 4; ++i) {
// Deleting rooms after index 5 (active) should not update the active index
rooms.splice(6, 1);
act(() => RoomListStoreV3.instance.emit(LISTS_UPDATE_EVENT));
expectActiveRoom(vm.current, 5, roomId);
}
});
it("active room index becomes undefined when active room is deleted", () => {
const { rooms } = mockAndCreateRooms();
// Let's say that the room at index 5 is active
let roomId: string | undefined = rooms[5].roomId;
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockImplementation(() => roomId);
const { result: vm } = renderHook(() => useRoomListViewModel());
expectActiveRoom(vm.current, 5, roomId);
// Let's remove the active room (i.e room at index 5)
rooms.splice(5, 1);
roomId = undefined;
act(() => RoomListStoreV3.instance.emit(LISTS_UPDATE_EVENT));
expect(vm.current.activeIndex).toBeUndefined();
});
it("active room index is initially undefined", () => {
mockAndCreateRooms();
// Let's say that there's no active room currently
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId").mockImplementation(() => undefined);
const { result: vm } = renderHook(() => useRoomListViewModel());
expect(vm.current.activeIndex).toEqual(undefined);
});
});
});

View File

@@ -0,0 +1,66 @@
/*
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { render } from "jest-matrix-react";
import userEvent from "@testing-library/user-event";
import React from "react";
import { ReportRoomDialog } from "../../../../../src/components/views/dialogs/ReportRoomDialog";
import SdkConfig from "../../../../../src/SdkConfig";
import { stubClient } from "../../../../test-utils";
const ROOM_ID = "!foo:bar";
describe("ReportRoomDialog", () => {
const onFinished: jest.Mock<any, any> = jest.fn();
const reportRoom: jest.Mock<any, any> = jest.fn();
beforeEach(() => {
jest.resetAllMocks();
const client = stubClient();
client.reportRoom = reportRoom;
SdkConfig.put({
report_event: {
admin_message_md: `
# You should know
This doesn't actually go **anywhere**.`,
},
});
});
afterEach(() => {
SdkConfig.reset();
});
it("can close the dialog", async () => {
const { getByTestId } = render(<ReportRoomDialog roomId={ROOM_ID} onFinished={onFinished} />);
await userEvent.click(getByTestId("dialog-cancel-button"));
expect(onFinished).toHaveBeenCalledWith(false);
});
it("displays admin message", async () => {
const { container } = render(<ReportRoomDialog roomId={ROOM_ID} onFinished={onFinished} />);
expect(container).toMatchSnapshot();
});
it("can submit a report", async () => {
const REASON = "This room is bad!";
const { getByLabelText, getByText, getByRole } = render(
<ReportRoomDialog roomId={ROOM_ID} onFinished={onFinished} />,
);
await userEvent.type(getByLabelText("Reason"), REASON);
await userEvent.click(getByRole("button", { name: "Send report" }));
expect(reportRoom).toHaveBeenCalledWith(ROOM_ID, REASON);
expect(getByText("Your report was sent.")).toBeInTheDocument();
await userEvent.click(getByRole("button", { name: "Close dialog" }));
expect(onFinished).toHaveBeenCalledWith(true);
});
});

View File

@@ -150,28 +150,53 @@ exports[`<ExportDialog /> renders export dialog 1`] = `
</span>
</span>
</div>
<span
class="mx_Checkbox mx_ExportDialog_attachments-checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
<form
class="_root_19upo_16"
>
<input
id="include-attachments"
type="checkbox"
/>
<label
for="include-attachments"
<div
class="_inline-field_19upo_32 mx_ExportDialog_attachments-checkbox"
>
<div
class="mx_Checkbox_background"
class="_inline-field-control_19upo_44"
>
<div
class="mx_Checkbox_checkmark"
/>
class="_container_1hel1_10"
>
<input
class="_input_1hel1_18"
id="include-attachments"
type="checkbox"
/>
<div
class="_ui_1hel1_19"
>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
/>
</svg>
</div>
</div>
</div>
<div>
Include Attachments
<div
class="_inline-field-body_19upo_38"
>
<label
class="_label_19upo_59"
for="include-attachments"
>
Include Attachments
</label>
</div>
</label>
</span>
</div>
</form>
</div>
<div
class="mx_Dialog_buttons"

View File

@@ -59,53 +59,80 @@ exports[`<ManageRestrictedJoinRuleDialog /> should list spaces which are not par
<h3>
Other spaces you know
</h3>
<label
<div
class="mx_ManageRestrictedJoinRuleDialog_entry"
>
<div>
<div>
<span
class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
data-color="1"
data-testid="avatar-img"
data-type="round"
role="presentation"
style="--cpd-avatar-size: 20px;"
>
O
</span>
<span
class="mx_ManageRestrictedJoinRuleDialog_entry_name"
>
Other Space
</span>
</div>
<div
class="mx_ManageRestrictedJoinRuleDialog_entry_description"
>
0 members
</div>
</div>
<span
class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
<form
class="_root_19upo_16"
>
<input
id="checkbox_vY7Q4uEh9K"
type="checkbox"
/>
<label
for="checkbox_vY7Q4uEh9K"
<div
class="_inline-field_19upo_32"
>
<div
class="mx_Checkbox_background"
class="_inline-field-control_19upo_44"
>
<div
class="mx_Checkbox_checkmark"
/>
class="_container_1hel1_10"
>
<input
aria-describedby=":r5:"
class="_input_1hel1_18"
id="checkbox_vY7Q4uEh9K"
type="checkbox"
/>
<div
class="_ui_1hel1_19"
>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
/>
</svg>
</div>
</div>
</div>
</label>
</span>
</label>
<div
class="_inline-field-body_19upo_38"
>
<label
class="_label_19upo_59"
for="checkbox_vY7Q4uEh9K"
>
<div>
<span
class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
data-color="1"
data-testid="avatar-img"
data-type="round"
role="none"
style="--cpd-avatar-size: 20px;"
>
O
</span>
<span
class="mx_ManageRestrictedJoinRuleDialog_entry_name"
>
Other Space
</span>
</div>
</label>
<span
class="_message_19upo_85 _help-message_19upo_91"
id=":r5:"
>
0 members
</span>
</div>
</div>
</form>
</div>
</div>
</div>
<div

View File

@@ -0,0 +1,100 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ReportRoomDialog displays admin message 1`] = `
<div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
aria-describedby="mx_ReportEventDialog"
aria-labelledby="mx_BaseDialog_title"
class="mx_ReportRoomDialog mx_Dialog_fixedWidth"
data-focus-lock-disabled="false"
role="dialog"
>
<div
class="mx_Dialog_header"
>
<h1
class="mx_Heading_h3 mx_Dialog_title"
id="mx_BaseDialog_title"
>
Report Room
</h1>
</div>
<form
class="_root_19upo_16"
id="mx_ReportEventDialog"
>
<p>
Report this room to your homeserver admin. This will send the room's unique ID, but if messages are encrypted, the administrator won't be able to read them or view shared files.
</p>
<p>
<h1>
You should know
</h1>
<p>
This doesn't actually go
<strong>
anywhere
</strong>
.
</p>
</p>
<div
class="_field_19upo_26"
>
<label
class="_label_19upo_59"
for="mx_ReportRoomDialog_reason"
>
Reason
</label>
<textarea
id="mx_ReportRoomDialog_reason"
placeholder=" Reason for reporting..."
rows="5"
/>
</div>
<div
class="mx_Dialog_buttons"
>
<span
class="mx_Dialog_buttons_row"
>
<button
data-testid="dialog-cancel-button"
type="button"
>
Cancel
</button>
<button
class="mx_Dialog_primary"
data-testid="dialog-primary-button"
type="button"
>
Send report
</button>
</span>
</div>
</form>
<div
aria-label="Close dialog"
class="mx_AccessibleButton mx_Dialog_cancelButton"
role="button"
tabindex="0"
/>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</div>
`;

View File

@@ -2,81 +2,129 @@
exports[`<LabelledCheckbox /> should render with byline of "this is a byline" 1`] = `
<DocumentFragment>
<label
<div
class="mx_LabelledCheckbox"
>
<span
class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
<form
class="_root_19upo_16"
>
<input
checked=""
id="checkbox_vY7Q4uEh9K"
type="checkbox"
/>
<label
for="checkbox_vY7Q4uEh9K"
<div
class="_inline-field_19upo_32"
>
<div
class="mx_Checkbox_background"
class="_inline-field-control_19upo_44"
>
<div
class="mx_Checkbox_checkmark"
/>
class="_container_1hel1_10"
>
<input
aria-describedby=":r4:"
checked=""
class="_input_1hel1_18"
id="checkbox_vY7Q4uEh9K"
type="checkbox"
/>
<div
class="_ui_1hel1_19"
>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
/>
</svg>
</div>
</div>
</div>
</label>
</span>
<div
class="mx_LabelledCheckbox_labels"
>
<span
class="mx_LabelledCheckbox_label"
>
Hello world
</span>
<span
class="mx_LabelledCheckbox_byline"
>
this is a byline
</span>
</div>
</label>
<div
class="_inline-field-body_19upo_38"
>
<label
class="_label_19upo_59"
for="checkbox_vY7Q4uEh9K"
>
<span
class="mx_LabelledCheckbox_label"
>
Hello world
</span>
</label>
<span
class="_message_19upo_85 _help-message_19upo_91"
id=":r4:"
>
this is a byline
</span>
</div>
</div>
</form>
</div>
</DocumentFragment>
`;
exports[`<LabelledCheckbox /> should render with byline of undefined 1`] = `
<DocumentFragment>
<label
<div
class="mx_LabelledCheckbox"
>
<span
class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
<form
class="_root_19upo_16"
>
<input
checked=""
id="checkbox_vY7Q4uEh9K"
type="checkbox"
/>
<label
for="checkbox_vY7Q4uEh9K"
<div
class="_inline-field_19upo_32"
>
<div
class="mx_Checkbox_background"
class="_inline-field-control_19upo_44"
>
<div
class="mx_Checkbox_checkmark"
/>
class="_container_1hel1_10"
>
<input
checked=""
class="_input_1hel1_18"
id="checkbox_vY7Q4uEh9K"
type="checkbox"
/>
<div
class="_ui_1hel1_19"
>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
/>
</svg>
</div>
</div>
</div>
</label>
</span>
<div
class="mx_LabelledCheckbox_labels"
>
<span
class="mx_LabelledCheckbox_label"
>
Hello world
</span>
</div>
</label>
<div
class="_inline-field-body_19upo_38"
>
<label
class="_label_19upo_59"
for="checkbox_vY7Q4uEh9K"
>
<span
class="mx_LabelledCheckbox_label"
>
Hello world
</span>
</label>
</div>
</div>
</form>
</div>
</DocumentFragment>
`;

View File

@@ -10,6 +10,7 @@ import React from "react";
import { type MatrixClient, type MatrixEvent, PushRuleKind } from "matrix-js-sdk/src/matrix";
import { mocked, type MockedObject } from "jest-mock";
import { render, waitFor } from "jest-matrix-react";
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
import { getMockClientWithEventEmitter, mkEvent, mkMessage, mkStubRoom } from "../../../../test-utils";
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
@@ -85,6 +86,8 @@ describe("<TextualBody />", () => {
throw new Error("MockClient event not found");
},
});
// @ts-expect-error
defaultMatrixClient.pushProcessor = new PushProcessor(defaultMatrixClient);
mocked(defaultRoom).findEventById.mockImplementation((eventId: string) => {
if (eventId === defaultEvent.getId()) return defaultEvent;

View File

@@ -609,46 +609,87 @@ exports[`<RoomSummaryCard /> has button to edit topic 1`] = `
data-orientation="horizontal"
role="separator"
/>
<button
class="mx_RoomSummaryCard_leave _item_1x5l4_8 _interactive_1x5l4_26"
data-kind="critical"
role="menuitem"
<div
class="mx_RoomSummaryCard_bottomOptions"
>
<svg
aria-hidden="true"
class="_icon_1x5l4_34"
fill="currentColor"
height="24"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
<button
class="mx_RoomSummaryCard_leave _item_1x5l4_8 _interactive_1x5l4_26"
data-kind="critical"
role="menuitem"
>
<path
d="M14 13q.424 0 .713-.287A.97.97 0 0 0 15 12a.97.97 0 0 0-.287-.713A.97.97 0 0 0 14 11a.97.97 0 0 0-.713.287A.97.97 0 0 0 13 12q0 .424.287.713.288.287.713.287"
/>
<path
d="M10.385 21.788A1 1 0 0 1 10 21V3a1.003 1.003 0 0 1 1.242-.97l8 2A1 1 0 0 1 20 5v14a1 1 0 0 1-.758.97l-8 2a1 1 0 0 1-.857-.182M18 5.781l-6-1.5v15.438l6-1.5zM9 6H7v12h2v2H7a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2z"
/>
</svg>
<span
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_1x5l4_43"
<svg
aria-hidden="true"
class="_icon_1x5l4_34"
fill="currentColor"
height="24"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14 13q.424 0 .713-.287A.97.97 0 0 0 15 12a.97.97 0 0 0-.287-.713A.97.97 0 0 0 14 11a.97.97 0 0 0-.713.287A.97.97 0 0 0 13 12q0 .424.287.713.288.287.713.287"
/>
<path
d="M10.385 21.788A1 1 0 0 1 10 21V3a1.003 1.003 0 0 1 1.242-.97l8 2A1 1 0 0 1 20 5v14a1 1 0 0 1-.758.97l-8 2a1 1 0 0 1-.857-.182M18 5.781l-6-1.5v15.438l6-1.5zM9 6H7v12h2v2H7a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2z"
/>
</svg>
<span
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_1x5l4_43"
>
Leave room
</span>
<svg
aria-hidden="true"
class="_nav-hint_1x5l4_50"
fill="currentColor"
height="24"
viewBox="8 0 8 24"
width="8"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
/>
</svg>
</button>
<button
class="_item_1x5l4_8 _interactive_1x5l4_26"
data-kind="critical"
role="menuitem"
>
Leave room
</span>
<svg
aria-hidden="true"
class="_nav-hint_1x5l4_50"
fill="currentColor"
height="24"
viewBox="8 0 8 24"
width="8"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
/>
</svg>
</button>
<svg
aria-hidden="true"
class="_icon_1x5l4_34"
fill="currentColor"
height="24"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
/>
</svg>
<span
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_1x5l4_43"
>
Report room
</span>
<svg
aria-hidden="true"
class="_nav-hint_1x5l4_50"
fill="currentColor"
height="24"
viewBox="8 0 8 24"
width="8"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
/>
</svg>
</button>
</div>
</div>
</div>
</div>
@@ -1227,46 +1268,87 @@ exports[`<RoomSummaryCard /> renders the room summary 1`] = `
data-orientation="horizontal"
role="separator"
/>
<button
class="mx_RoomSummaryCard_leave _item_1x5l4_8 _interactive_1x5l4_26"
data-kind="critical"
role="menuitem"
<div
class="mx_RoomSummaryCard_bottomOptions"
>
<svg
aria-hidden="true"
class="_icon_1x5l4_34"
fill="currentColor"
height="24"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
<button
class="mx_RoomSummaryCard_leave _item_1x5l4_8 _interactive_1x5l4_26"
data-kind="critical"
role="menuitem"
>
<path
d="M14 13q.424 0 .713-.287A.97.97 0 0 0 15 12a.97.97 0 0 0-.287-.713A.97.97 0 0 0 14 11a.97.97 0 0 0-.713.287A.97.97 0 0 0 13 12q0 .424.287.713.288.287.713.287"
/>
<path
d="M10.385 21.788A1 1 0 0 1 10 21V3a1.003 1.003 0 0 1 1.242-.97l8 2A1 1 0 0 1 20 5v14a1 1 0 0 1-.758.97l-8 2a1 1 0 0 1-.857-.182M18 5.781l-6-1.5v15.438l6-1.5zM9 6H7v12h2v2H7a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2z"
/>
</svg>
<span
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_1x5l4_43"
<svg
aria-hidden="true"
class="_icon_1x5l4_34"
fill="currentColor"
height="24"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14 13q.424 0 .713-.287A.97.97 0 0 0 15 12a.97.97 0 0 0-.287-.713A.97.97 0 0 0 14 11a.97.97 0 0 0-.713.287A.97.97 0 0 0 13 12q0 .424.287.713.288.287.713.287"
/>
<path
d="M10.385 21.788A1 1 0 0 1 10 21V3a1.003 1.003 0 0 1 1.242-.97l8 2A1 1 0 0 1 20 5v14a1 1 0 0 1-.758.97l-8 2a1 1 0 0 1-.857-.182M18 5.781l-6-1.5v15.438l6-1.5zM9 6H7v12h2v2H7a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2z"
/>
</svg>
<span
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_1x5l4_43"
>
Leave room
</span>
<svg
aria-hidden="true"
class="_nav-hint_1x5l4_50"
fill="currentColor"
height="24"
viewBox="8 0 8 24"
width="8"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
/>
</svg>
</button>
<button
class="_item_1x5l4_8 _interactive_1x5l4_26"
data-kind="critical"
role="menuitem"
>
Leave room
</span>
<svg
aria-hidden="true"
class="_nav-hint_1x5l4_50"
fill="currentColor"
height="24"
viewBox="8 0 8 24"
width="8"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
/>
</svg>
</button>
<svg
aria-hidden="true"
class="_icon_1x5l4_34"
fill="currentColor"
height="24"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
/>
</svg>
<span
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_1x5l4_43"
>
Report room
</span>
<svg
aria-hidden="true"
class="_nav-hint_1x5l4_50"
fill="currentColor"
height="24"
viewBox="8 0 8 24"
width="8"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
/>
</svg>
</button>
</div>
</div>
</div>
</div>
@@ -1882,46 +1964,87 @@ exports[`<RoomSummaryCard /> renders the room topic in the summary 1`] = `
data-orientation="horizontal"
role="separator"
/>
<button
class="mx_RoomSummaryCard_leave _item_1x5l4_8 _interactive_1x5l4_26"
data-kind="critical"
role="menuitem"
<div
class="mx_RoomSummaryCard_bottomOptions"
>
<svg
aria-hidden="true"
class="_icon_1x5l4_34"
fill="currentColor"
height="24"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
<button
class="mx_RoomSummaryCard_leave _item_1x5l4_8 _interactive_1x5l4_26"
data-kind="critical"
role="menuitem"
>
<path
d="M14 13q.424 0 .713-.287A.97.97 0 0 0 15 12a.97.97 0 0 0-.287-.713A.97.97 0 0 0 14 11a.97.97 0 0 0-.713.287A.97.97 0 0 0 13 12q0 .424.287.713.288.287.713.287"
/>
<path
d="M10.385 21.788A1 1 0 0 1 10 21V3a1.003 1.003 0 0 1 1.242-.97l8 2A1 1 0 0 1 20 5v14a1 1 0 0 1-.758.97l-8 2a1 1 0 0 1-.857-.182M18 5.781l-6-1.5v15.438l6-1.5zM9 6H7v12h2v2H7a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2z"
/>
</svg>
<span
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_1x5l4_43"
<svg
aria-hidden="true"
class="_icon_1x5l4_34"
fill="currentColor"
height="24"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14 13q.424 0 .713-.287A.97.97 0 0 0 15 12a.97.97 0 0 0-.287-.713A.97.97 0 0 0 14 11a.97.97 0 0 0-.713.287A.97.97 0 0 0 13 12q0 .424.287.713.288.287.713.287"
/>
<path
d="M10.385 21.788A1 1 0 0 1 10 21V3a1.003 1.003 0 0 1 1.242-.97l8 2A1 1 0 0 1 20 5v14a1 1 0 0 1-.758.97l-8 2a1 1 0 0 1-.857-.182M18 5.781l-6-1.5v15.438l6-1.5zM9 6H7v12h2v2H7a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2z"
/>
</svg>
<span
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_1x5l4_43"
>
Leave room
</span>
<svg
aria-hidden="true"
class="_nav-hint_1x5l4_50"
fill="currentColor"
height="24"
viewBox="8 0 8 24"
width="8"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
/>
</svg>
</button>
<button
class="_item_1x5l4_8 _interactive_1x5l4_26"
data-kind="critical"
role="menuitem"
>
Leave room
</span>
<svg
aria-hidden="true"
class="_nav-hint_1x5l4_50"
fill="currentColor"
height="24"
viewBox="8 0 8 24"
width="8"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
/>
</svg>
</button>
<svg
aria-hidden="true"
class="_icon_1x5l4_34"
fill="currentColor"
height="24"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
/>
</svg>
<span
class="_typography_6v6n8_153 _font-body-md-medium_6v6n8_60 _label_1x5l4_43"
>
Report room
</span>
<svg
aria-hidden="true"
class="_nav-hint_1x5l4_50"
fill="currentColor"
height="24"
viewBox="8 0 8 24"
width="8"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.7 17.3a.95.95 0 0 1-.275-.7q0-.425.275-.7l3.9-3.9-3.9-3.9a.95.95 0 0 1-.275-.7q0-.425.275-.7a.95.95 0 0 1 .7-.275q.425 0 .7.275l4.6 4.6q.15.15.213.325.062.175.062.375t-.062.375a.9.9 0 0 1-.213.325l-4.6 4.6a.95.95 0 0 1-.7.275.95.95 0 0 1-.7-.275"
/>
</svg>
</button>
</div>
</div>
</div>
</div>

View File

@@ -36,6 +36,7 @@ import {
} from "jest-matrix-react";
import { mocked } from "jest-mock";
import userEvent from "@testing-library/user-event";
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
import Notifications from "../../../../../src/components/views/settings/Notifications";
import SettingsStore from "../../../../../src/settings/SettingsStore";
@@ -301,6 +302,8 @@ describe("<Notifications />", () => {
mockClient.getPushRules.mockClear().mockResolvedValue(pushRules);
mockClient.addPushRule.mockClear();
mockClient.deletePushRule.mockClear();
// @ts-expect-error
mockClient.pushProcessor = new PushProcessor(mockClient);
userEvent.setup();

View File

@@ -9,29 +9,50 @@ exports[`<FilteredDeviceListHeader /> renders correctly when all devices are sel
<span
tabindex="0"
>
<span
class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
<form
class="_root_19upo_16"
>
<input
aria-label="Deselect all"
aria-labelledby=":r6:"
checked=""
data-testid="device-select-all-checkbox"
id="device-select-all-checkbox"
type="checkbox"
/>
<label
for="device-select-all-checkbox"
<div
class="_inline-field_19upo_32"
>
<div
class="mx_Checkbox_background"
class="_inline-field-control_19upo_44"
>
<div
class="mx_Checkbox_checkmark"
/>
class="_container_1hel1_10"
>
<input
aria-label="Deselect all"
aria-labelledby=":r9:"
checked=""
class="_input_1hel1_18"
data-testid="device-select-all-checkbox"
id="device-select-all-checkbox"
type="checkbox"
/>
<div
class="_ui_1hel1_19"
>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
/>
</svg>
</div>
</div>
</div>
</label>
</span>
<div
class="_inline-field-body_19upo_38"
/>
</div>
</form>
</span>
<span
class="mx_FilteredDeviceListHeader_label"
@@ -54,28 +75,49 @@ exports[`<FilteredDeviceListHeader /> renders correctly when no devices are sele
<span
tabindex="0"
>
<span
class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
<form
class="_root_19upo_16"
>
<input
aria-label="Select all"
aria-labelledby=":r0:"
data-testid="device-select-all-checkbox"
id="device-select-all-checkbox"
type="checkbox"
/>
<label
for="device-select-all-checkbox"
<div
class="_inline-field_19upo_32"
>
<div
class="mx_Checkbox_background"
class="_inline-field-control_19upo_44"
>
<div
class="mx_Checkbox_checkmark"
/>
class="_container_1hel1_10"
>
<input
aria-label="Select all"
aria-labelledby=":r0:"
class="_input_1hel1_18"
data-testid="device-select-all-checkbox"
id="device-select-all-checkbox"
type="checkbox"
/>
<div
class="_ui_1hel1_19"
>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
/>
</svg>
</div>
</div>
</div>
</label>
</span>
<div
class="_inline-field-body_19upo_38"
/>
</div>
</form>
</span>
<span
class="mx_FilteredDeviceListHeader_label"

View File

@@ -3,6 +3,7 @@
exports[`<SelectableDeviceTile /> renders selected tile 1`] = `
<input
checked=""
class="_input_1hel1_18"
data-testid="device-tile-checkbox-my-device"
id="device-tile-checkbox-my-device"
type="checkbox"
@@ -14,88 +15,113 @@ exports[`<SelectableDeviceTile /> renders unselected device tile with checkbox 1
<div
class="mx_SelectableDeviceTile"
>
<span
class="mx_Checkbox mx_SelectableDeviceTile_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
<form
class="_root_19upo_16"
>
<input
data-testid="device-tile-checkbox-my-device"
id="device-tile-checkbox-my-device"
type="checkbox"
/>
<label
for="device-tile-checkbox-my-device"
<div
class="_inline-field_19upo_32 mx_SelectableDeviceTile_checkbox"
>
<div
class="mx_Checkbox_background"
class="_inline-field-control_19upo_44"
>
<div
class="mx_Checkbox_checkmark"
/>
class="_container_1hel1_10"
>
<input
class="_input_1hel1_18"
data-testid="device-tile-checkbox-my-device"
id="device-tile-checkbox-my-device"
type="checkbox"
/>
<div
class="_ui_1hel1_19"
>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
/>
</svg>
</div>
</div>
</div>
<div>
<div
class="mx_DeviceTile mx_DeviceTile_interactive"
data-testid="device-tile-my-device"
<div
class="_inline-field-body_19upo_38"
>
<label
class="_label_19upo_59"
for="device-tile-checkbox-my-device"
>
<div
class="mx_DeviceTypeIcon"
class="mx_DeviceTile mx_DeviceTile_interactive"
data-testid="device-tile-my-device"
>
<div
class="mx_DeviceTypeIcon_deviceIconWrapper"
class="mx_DeviceTypeIcon"
>
<div
aria-label="Unknown session type"
class="mx_DeviceTypeIcon_deviceIcon"
class="mx_DeviceTypeIcon_deviceIconWrapper"
>
<div
aria-label="Unknown session type"
class="mx_DeviceTypeIcon_deviceIcon"
role="img"
/>
</div>
<div
aria-label="Unverified"
class="mx_DeviceTypeIcon_verificationIcon unverified"
role="img"
/>
</div>
<div
aria-label="Unverified"
class="mx_DeviceTypeIcon_verificationIcon unverified"
role="img"
/>
</div>
<div
class="mx_DeviceTile_info"
>
<h4
class="mx_Heading_h4"
class="mx_DeviceTile_info"
>
My Device
</h4>
<h4
class="mx_Heading_h4"
>
My Device
</h4>
<div
class="mx_DeviceTile_metadata"
>
<span
data-testid="device-metadata-isVerified"
>
Unverified
</span>
·
<span
data-testid="device-metadata-lastSeenIp"
>
123.456.789
</span>
·
<span
data-testid="device-metadata-deviceId"
>
my-device
</span>
</div>
</div>
<div
class="mx_DeviceTile_metadata"
class="mx_DeviceTile_actions"
>
<span
data-testid="device-metadata-isVerified"
>
Unverified
</span>
·
<span
data-testid="device-metadata-lastSeenIp"
>
123.456.789
</span>
·
<span
data-testid="device-metadata-deviceId"
>
my-device
</span>
<div>
test
</div>
</div>
</div>
<div
class="mx_DeviceTile_actions"
>
<div>
test
</div>
</div>
</div>
</label>
</div>
</label>
</span>
</div>
</form>
</div>
</div>
`;

View File

@@ -39,8 +39,7 @@ const labelActivityStatus = "New room activity, upgrades and status messages occ
const labelActivityBots = "Messages sent by bots";
const labelMentionUser = "Notify when someone mentions using @displayname or @mxid";
const labelMentionRoom = "Notify when someone mentions using @room";
const labelMentionKeyword =
"Notify when someone uses a keyword" + "Enter keywords here, or use for spelling variations or nicknames";
const labelMentionKeyword = "Notify when someone uses a keyword";
const labelResetDefault = "Reset to default settings";
const keywords = ["justjann3", "justj4nn3", "justj4nne", "Janne", "J4nne", "Jann3", "jann3", "j4nne", "janne"];

View File

@@ -383,28 +383,49 @@ exports[`<SessionManagerTab /> goes to filtered list from security recommendatio
<span
tabindex="0"
>
<span
class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
<form
class="_root_19upo_16"
>
<input
aria-label="Select all"
aria-labelledby=":r3s:"
data-testid="device-select-all-checkbox"
id="device-select-all-checkbox"
type="checkbox"
/>
<label
for="device-select-all-checkbox"
<div
class="_inline-field_19upo_32"
>
<div
class="mx_Checkbox_background"
class="_inline-field-control_19upo_44"
>
<div
class="mx_Checkbox_checkmark"
/>
class="_container_1hel1_10"
>
<input
aria-label="Select all"
aria-labelledby=":r4q:"
class="_input_1hel1_18"
data-testid="device-select-all-checkbox"
id="device-select-all-checkbox"
type="checkbox"
/>
<div
class="_ui_1hel1_19"
>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
/>
</svg>
</div>
</div>
</div>
</label>
</span>
<div
class="_inline-field-body_19upo_38"
/>
</div>
</form>
</span>
<span
class="mx_FilteredDeviceListHeader_label"

View File

@@ -38,30 +38,53 @@ exports[`<SidebarUserSettingsTab /> renders sidebar settings with guest spa url
<div
class="mx_SettingsSubsection_content"
>
<span
class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
<form
class="_root_19upo_16"
>
<input
checked=""
disabled=""
id="checkbox_vY7Q4uEh9K"
type="checkbox"
/>
<label
for="checkbox_vY7Q4uEh9K"
<div
class="_inline-field_19upo_32 mx_SidebarUserSettingsTab_checkbox"
>
<div
class="mx_Checkbox_background"
class="_inline-field-control_19upo_44"
>
<div
class="mx_Checkbox_checkmark"
/>
class="_container_1hel1_10"
>
<input
aria-describedby=":r1:"
checked=""
class="_input_1hel1_18"
disabled=""
id="checkbox_vY7Q4uEh9K"
type="checkbox"
/>
<div
class="_ui_1hel1_19"
>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
/>
</svg>
</div>
</div>
</div>
<div>
<div
class="mx_SettingsSubsection_text"
<div
class="_inline-field-body_19upo_38"
>
<label
class="_label_19upo_59"
for="checkbox_vY7Q4uEh9K"
>
<svg
class="mx_SidebarUserSettingsTab_icon"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
@@ -73,69 +96,116 @@ exports[`<SidebarUserSettingsTab /> renders sidebar settings with guest spa url
/>
</svg>
Home
</div>
<div
class="mx_SettingsSubsection_text"
</label>
<span
class="_message_19upo_85 _help-message_19upo_91"
id=":r1:"
>
Home is useful for getting an overview of everything.
</div>
</span>
</div>
</label>
</span>
<span
class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_SidebarUserSettingsTab_homeAllRoomsCheckbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
</div>
</form>
<form
class="_root_19upo_16"
>
<input
data-testid="mx_SidebarUserSettingsTab_homeAllRoomsCheckbox"
id="checkbox_38QgU2Pomx"
type="checkbox"
/>
<label
for="checkbox_38QgU2Pomx"
<div
class="_inline-field_19upo_32 mx_SidebarUserSettingsTab_checkbox mx_SidebarUserSettingsTab_homeAllRoomsCheckbox"
>
<div
class="mx_Checkbox_background"
class="_inline-field-control_19upo_44"
>
<div
class="mx_Checkbox_checkmark"
/>
class="_container_1hel1_10"
>
<input
aria-describedby=":r5:"
class="_input_1hel1_18"
data-testid="mx_SidebarUserSettingsTab_homeAllRoomsCheckbox"
id="checkbox_38QgU2Pomx"
type="checkbox"
/>
<div
class="_ui_1hel1_19"
>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
/>
</svg>
</div>
</div>
</div>
<div>
<div
class="mx_SettingsSubsection_text"
<div
class="_inline-field-body_19upo_38"
>
<label
class="_label_19upo_59"
for="checkbox_38QgU2Pomx"
>
Show all rooms
</div>
<div
class="mx_SettingsSubsection_text"
</label>
<span
class="_message_19upo_85 _help-message_19upo_91"
id=":r5:"
>
Show all your rooms in Home, even if they're in a space.
</div>
</span>
</div>
</label>
</span>
<span
class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
</div>
</form>
<form
class="_root_19upo_16"
>
<input
id="checkbox_wKpa6hpi3Y"
type="checkbox"
/>
<label
for="checkbox_wKpa6hpi3Y"
<div
class="_inline-field_19upo_32 mx_SidebarUserSettingsTab_checkbox"
>
<div
class="mx_Checkbox_background"
class="_inline-field-control_19upo_44"
>
<div
class="mx_Checkbox_checkmark"
/>
class="_container_1hel1_10"
>
<input
aria-describedby=":r9:"
class="_input_1hel1_18"
id="checkbox_wKpa6hpi3Y"
type="checkbox"
/>
<div
class="_ui_1hel1_19"
>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
/>
</svg>
</div>
</div>
</div>
<div>
<div
class="mx_SettingsSubsection_text"
<div
class="_inline-field-body_19upo_38"
>
<label
class="_label_19upo_59"
for="checkbox_wKpa6hpi3Y"
>
<svg
class="mx_SidebarUserSettingsTab_icon"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
@@ -147,37 +217,61 @@ exports[`<SidebarUserSettingsTab /> renders sidebar settings with guest spa url
/>
</svg>
Favourites
</div>
<div
class="mx_SettingsSubsection_text"
</label>
<span
class="_message_19upo_85 _help-message_19upo_91"
id=":r9:"
>
Group all your favourite rooms and people in one place.
</div>
</span>
</div>
</label>
</span>
<span
class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
</div>
</form>
<form
class="_root_19upo_16"
>
<input
id="checkbox_EetmBG4yVC"
type="checkbox"
/>
<label
for="checkbox_EetmBG4yVC"
<div
class="_inline-field_19upo_32 mx_SidebarUserSettingsTab_checkbox"
>
<div
class="mx_Checkbox_background"
class="_inline-field-control_19upo_44"
>
<div
class="mx_Checkbox_checkmark"
/>
class="_container_1hel1_10"
>
<input
aria-describedby=":rd:"
class="_input_1hel1_18"
id="checkbox_EetmBG4yVC"
type="checkbox"
/>
<div
class="_ui_1hel1_19"
>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
/>
</svg>
</div>
</div>
</div>
<div>
<div
class="mx_SettingsSubsection_text"
<div
class="_inline-field-body_19upo_38"
>
<label
class="_label_19upo_59"
for="checkbox_EetmBG4yVC"
>
<svg
class="mx_SidebarUserSettingsTab_icon"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
@@ -192,69 +286,115 @@ exports[`<SidebarUserSettingsTab /> renders sidebar settings with guest spa url
/>
</svg>
People
</div>
<div
class="mx_SettingsSubsection_text"
</label>
<span
class="_message_19upo_85 _help-message_19upo_91"
id=":rd:"
>
Group all your people in one place.
</div>
</span>
</div>
</label>
</span>
<span
class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
</div>
</form>
<form
class="_root_19upo_16"
>
<input
id="checkbox_eEefiPqpMR"
type="checkbox"
/>
<label
for="checkbox_eEefiPqpMR"
<div
class="_inline-field_19upo_32 mx_SidebarUserSettingsTab_checkbox"
>
<div
class="mx_Checkbox_background"
class="_inline-field-control_19upo_44"
>
<div
class="mx_Checkbox_checkmark"
/>
</div>
<div>
<div
class="mx_SettingsSubsection_text"
class="_container_1hel1_10"
>
<div />
Rooms outside of a space
<input
aria-describedby=":rh:"
class="_input_1hel1_18"
id="checkbox_eEefiPqpMR"
type="checkbox"
/>
<div
class="_ui_1hel1_19"
>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
/>
</svg>
</div>
</div>
<div
class="mx_SettingsSubsection_text"
</div>
<div
class="_inline-field-body_19upo_38"
>
<label
class="_label_19upo_59"
for="checkbox_eEefiPqpMR"
>
Rooms outside of a space
</label>
<span
class="_message_19upo_85 _help-message_19upo_91"
id=":rh:"
>
Group all your rooms that aren't part of a space in one place.
</div>
</span>
</div>
</label>
</span>
<span
class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
</div>
</form>
<form
class="_root_19upo_16"
>
<input
id="checkbox_MwbPDmfGtm"
type="checkbox"
/>
<label
for="checkbox_MwbPDmfGtm"
<div
class="_inline-field_19upo_32 mx_SidebarUserSettingsTab_checkbox"
>
<div
class="mx_Checkbox_background"
class="_inline-field-control_19upo_44"
>
<div
class="mx_Checkbox_checkmark"
/>
class="_container_1hel1_10"
>
<input
aria-describedby=":rl:"
class="_input_1hel1_18"
id="checkbox_MwbPDmfGtm"
type="checkbox"
/>
<div
class="_ui_1hel1_19"
>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
/>
</svg>
</div>
</div>
</div>
<div>
<div
class="mx_SettingsSubsection_text"
<div
class="_inline-field-body_19upo_38"
>
<label
class="_label_19upo_59"
for="checkbox_MwbPDmfGtm"
>
<svg
class="mx_SidebarUserSettingsTab_icon"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
@@ -266,15 +406,16 @@ exports[`<SidebarUserSettingsTab /> renders sidebar settings with guest spa url
/>
</svg>
Video rooms and conferences
</div>
<div
class="mx_SettingsSubsection_text"
</label>
<span
class="_message_19upo_85 _help-message_19upo_91"
id=":rl:"
>
Group all private video rooms and conferences. In conferences you can invite people outside of matrix.
</div>
</span>
</div>
</label>
</span>
</div>
</form>
</div>
</div>
</div>
@@ -322,30 +463,53 @@ exports[`<SidebarUserSettingsTab /> renders sidebar settings without guest spa u
<div
class="mx_SettingsSubsection_content"
>
<span
class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
<form
class="_root_19upo_16"
>
<input
checked=""
disabled=""
id="checkbox_vY7Q4uEh9K"
type="checkbox"
/>
<label
for="checkbox_vY7Q4uEh9K"
<div
class="_inline-field_19upo_32 mx_SidebarUserSettingsTab_checkbox"
>
<div
class="mx_Checkbox_background"
class="_inline-field-control_19upo_44"
>
<div
class="mx_Checkbox_checkmark"
/>
class="_container_1hel1_10"
>
<input
aria-describedby=":rp:"
checked=""
class="_input_1hel1_18"
disabled=""
id="checkbox_vY7Q4uEh9K"
type="checkbox"
/>
<div
class="_ui_1hel1_19"
>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
/>
</svg>
</div>
</div>
</div>
<div>
<div
class="mx_SettingsSubsection_text"
<div
class="_inline-field-body_19upo_38"
>
<label
class="_label_19upo_59"
for="checkbox_vY7Q4uEh9K"
>
<svg
class="mx_SidebarUserSettingsTab_icon"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
@@ -357,69 +521,116 @@ exports[`<SidebarUserSettingsTab /> renders sidebar settings without guest spa u
/>
</svg>
Home
</div>
<div
class="mx_SettingsSubsection_text"
</label>
<span
class="_message_19upo_85 _help-message_19upo_91"
id=":rp:"
>
Home is useful for getting an overview of everything.
</div>
</span>
</div>
</label>
</span>
<span
class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_SidebarUserSettingsTab_homeAllRoomsCheckbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
</div>
</form>
<form
class="_root_19upo_16"
>
<input
data-testid="mx_SidebarUserSettingsTab_homeAllRoomsCheckbox"
id="checkbox_38QgU2Pomx"
type="checkbox"
/>
<label
for="checkbox_38QgU2Pomx"
<div
class="_inline-field_19upo_32 mx_SidebarUserSettingsTab_checkbox mx_SidebarUserSettingsTab_homeAllRoomsCheckbox"
>
<div
class="mx_Checkbox_background"
class="_inline-field-control_19upo_44"
>
<div
class="mx_Checkbox_checkmark"
/>
class="_container_1hel1_10"
>
<input
aria-describedby=":rt:"
class="_input_1hel1_18"
data-testid="mx_SidebarUserSettingsTab_homeAllRoomsCheckbox"
id="checkbox_38QgU2Pomx"
type="checkbox"
/>
<div
class="_ui_1hel1_19"
>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
/>
</svg>
</div>
</div>
</div>
<div>
<div
class="mx_SettingsSubsection_text"
<div
class="_inline-field-body_19upo_38"
>
<label
class="_label_19upo_59"
for="checkbox_38QgU2Pomx"
>
Show all rooms
</div>
<div
class="mx_SettingsSubsection_text"
</label>
<span
class="_message_19upo_85 _help-message_19upo_91"
id=":rt:"
>
Show all your rooms in Home, even if they're in a space.
</div>
</span>
</div>
</label>
</span>
<span
class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
</div>
</form>
<form
class="_root_19upo_16"
>
<input
id="checkbox_wKpa6hpi3Y"
type="checkbox"
/>
<label
for="checkbox_wKpa6hpi3Y"
<div
class="_inline-field_19upo_32 mx_SidebarUserSettingsTab_checkbox"
>
<div
class="mx_Checkbox_background"
class="_inline-field-control_19upo_44"
>
<div
class="mx_Checkbox_checkmark"
/>
class="_container_1hel1_10"
>
<input
aria-describedby=":r11:"
class="_input_1hel1_18"
id="checkbox_wKpa6hpi3Y"
type="checkbox"
/>
<div
class="_ui_1hel1_19"
>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
/>
</svg>
</div>
</div>
</div>
<div>
<div
class="mx_SettingsSubsection_text"
<div
class="_inline-field-body_19upo_38"
>
<label
class="_label_19upo_59"
for="checkbox_wKpa6hpi3Y"
>
<svg
class="mx_SidebarUserSettingsTab_icon"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
@@ -431,37 +642,61 @@ exports[`<SidebarUserSettingsTab /> renders sidebar settings without guest spa u
/>
</svg>
Favourites
</div>
<div
class="mx_SettingsSubsection_text"
</label>
<span
class="_message_19upo_85 _help-message_19upo_91"
id=":r11:"
>
Group all your favourite rooms and people in one place.
</div>
</span>
</div>
</label>
</span>
<span
class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
</div>
</form>
<form
class="_root_19upo_16"
>
<input
id="checkbox_EetmBG4yVC"
type="checkbox"
/>
<label
for="checkbox_EetmBG4yVC"
<div
class="_inline-field_19upo_32 mx_SidebarUserSettingsTab_checkbox"
>
<div
class="mx_Checkbox_background"
class="_inline-field-control_19upo_44"
>
<div
class="mx_Checkbox_checkmark"
/>
class="_container_1hel1_10"
>
<input
aria-describedby=":r15:"
class="_input_1hel1_18"
id="checkbox_EetmBG4yVC"
type="checkbox"
/>
<div
class="_ui_1hel1_19"
>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
/>
</svg>
</div>
</div>
</div>
<div>
<div
class="mx_SettingsSubsection_text"
<div
class="_inline-field-body_19upo_38"
>
<label
class="_label_19upo_59"
for="checkbox_EetmBG4yVC"
>
<svg
class="mx_SidebarUserSettingsTab_icon"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
@@ -476,69 +711,115 @@ exports[`<SidebarUserSettingsTab /> renders sidebar settings without guest spa u
/>
</svg>
People
</div>
<div
class="mx_SettingsSubsection_text"
</label>
<span
class="_message_19upo_85 _help-message_19upo_91"
id=":r15:"
>
Group all your people in one place.
</div>
</span>
</div>
</label>
</span>
<span
class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
</div>
</form>
<form
class="_root_19upo_16"
>
<input
id="checkbox_eEefiPqpMR"
type="checkbox"
/>
<label
for="checkbox_eEefiPqpMR"
<div
class="_inline-field_19upo_32 mx_SidebarUserSettingsTab_checkbox"
>
<div
class="mx_Checkbox_background"
class="_inline-field-control_19upo_44"
>
<div
class="mx_Checkbox_checkmark"
/>
</div>
<div>
<div
class="mx_SettingsSubsection_text"
class="_container_1hel1_10"
>
<div />
Rooms outside of a space
<input
aria-describedby=":r19:"
class="_input_1hel1_18"
id="checkbox_eEefiPqpMR"
type="checkbox"
/>
<div
class="_ui_1hel1_19"
>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
/>
</svg>
</div>
</div>
<div
class="mx_SettingsSubsection_text"
</div>
<div
class="_inline-field-body_19upo_38"
>
<label
class="_label_19upo_59"
for="checkbox_eEefiPqpMR"
>
Rooms outside of a space
</label>
<span
class="_message_19upo_85 _help-message_19upo_91"
id=":r19:"
>
Group all your rooms that aren't part of a space in one place.
</div>
</span>
</div>
</label>
</span>
<span
class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
</div>
</form>
<form
class="_root_19upo_16"
>
<input
id="checkbox_MwbPDmfGtm"
type="checkbox"
/>
<label
for="checkbox_MwbPDmfGtm"
<div
class="_inline-field_19upo_32 mx_SidebarUserSettingsTab_checkbox"
>
<div
class="mx_Checkbox_background"
class="_inline-field-control_19upo_44"
>
<div
class="mx_Checkbox_checkmark"
/>
class="_container_1hel1_10"
>
<input
aria-describedby=":r1d:"
class="_input_1hel1_18"
id="checkbox_MwbPDmfGtm"
type="checkbox"
/>
<div
class="_ui_1hel1_19"
>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
/>
</svg>
</div>
</div>
</div>
<div>
<div
class="mx_SettingsSubsection_text"
<div
class="_inline-field-body_19upo_38"
>
<label
class="_label_19upo_59"
for="checkbox_MwbPDmfGtm"
>
<svg
class="mx_SidebarUserSettingsTab_icon"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
@@ -550,15 +831,16 @@ exports[`<SidebarUserSettingsTab /> renders sidebar settings without guest spa u
/>
</svg>
Video rooms and conferences
</div>
<div
class="mx_SettingsSubsection_text"
</label>
<span
class="_message_19upo_85 _help-message_19upo_91"
id=":r1d:"
>
Group all private video rooms and conferences.
</div>
</span>
</div>
</label>
</span>
</div>
</form>
</div>
</div>
</div>

View File

@@ -457,7 +457,7 @@ describe("RoomListStoreV3", () => {
// Let's say 8, 27 are unread
jest.spyOn(RoomNotificationStateStore.instance, "getRoomState").mockImplementation((room) => {
const state = {
isUnread: [rooms[8], rooms[27]].includes(room),
hasUnreadCount: [rooms[8], rooms[27]].includes(room),
} as unknown as RoomNotificationState;
return state;
});
@@ -588,7 +588,7 @@ describe("RoomListStoreV3", () => {
// Let's say 8, 27 are unread
jest.spyOn(RoomNotificationStateStore.instance, "getRoomState").mockImplementation((room) => {
const state = {
isUnread: [rooms[8], rooms[27]].includes(room),
hasUnreadCount: [rooms[8], rooms[27]].includes(room),
} as unknown as RoomNotificationState;
return state;
});

View File

@@ -10,6 +10,7 @@ import React from "react";
import { act, render } from "jest-matrix-react";
import { MatrixEvent, ConditionKind, EventType, PushRuleActionName, Room, TweakName } from "matrix-js-sdk/src/matrix";
import { mocked } from "jest-mock";
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
import { pillifyLinks } from "../../../src/utils/pillify";
import { stubClient } from "../../test-utils";
@@ -78,6 +79,8 @@ describe("pillify", () => {
},
],
};
// @ts-expect-error
cli.pushProcessor = new PushProcessor(cli);
DMRoomMap.makeShared(cli);
});

View File

@@ -176,14 +176,14 @@ module.exports = (env, argv) => {
minimize: enableMinification,
minimizer: enableMinification
? [
new TerserPlugin({
// Already minified and includes an auto-generated license comment
// that the plugin would otherwise pointlessly extract into a separate
// file. We add the actual license using CopyWebpackPlugin below.
exclude: "jitsi_external_api.min.js",
}),
new CssMinimizerPlugin(),
]
new TerserPlugin({
// Already minified and includes an auto-generated license comment
// that the plugin would otherwise pointlessly extract into a separate
// file. We add the actual license using CopyWebpackPlugin below.
exclude: "jitsi_external_api.min.js",
}),
new CssMinimizerPlugin(),
]
: [],
// Set the value of `process.env.NODE_ENV` for libraries like React
@@ -625,6 +625,12 @@ module.exports = (env, argv) => {
minify: false,
chunks: [],
}),
new HtmlWebpackPlugin({
template: "./src/vector/static/join_room_redirect.html",
filename: "static/join_room_redirect.html",
minify: false,
chunks: [],
}),
new HtmlWebpackPlugin({
template: "./src/vector/static/incompatible-browser.html",
filename: "static/incompatible-browser.html",
@@ -648,16 +654,16 @@ module.exports = (env, argv) => {
// This plugin throws an error on import on some platforms like ppc64le & s390x even if the plugin isn't called,
// so we require it conditionally.
process.env.SENTRY_DSN &&
require("@sentry/webpack-plugin").sentryWebpackPlugin({
release: process.env.VERSION,
sourcemaps: {
paths: "./webapp/bundles/**",
},
errorHandler: (err) => {
console.warn("Sentry CLI Plugin: " + err.message);
console.log(`::warning title=Sentry error::${err.message}`);
},
}),
require("@sentry/webpack-plugin").sentryWebpackPlugin({
release: process.env.VERSION,
sourcemaps: {
paths: "./webapp/bundles/**",
},
errorHandler: (err) => {
console.warn("Sentry CLI Plugin: " + err.message);
console.log(`::warning title=Sentry error::${err.message}`);
},
}),
new CopyWebpackPlugin({
patterns: [