diff --git a/CHANGELOG.md b/CHANGELOG.md index 48a721c013..b3a18c5396 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,34 @@ +Changes in [1.11.103](https://github.com/element-hq/element-web/releases/tag/v1.11.103) (2025-06-10) +==================================================================================================== +## 🐛 Bug Fixes + ++ Check the sender of an event matches owner of session, preventing sender spoofing by homeserver owners. +[13c1d20](https://github.com/matrix-org/matrix-rust-sdk/commit/13c1d2048286bbabf5e7bc6b015aafee98f04d55) (High, [GHSA-x958-rvg6-956w](https://github.com/matrix-org/matrix-rust-sdk/security/advisories/GHSA-x958-rvg6-956w)). + +Changes in [1.11.102](https://github.com/element-hq/element-web/releases/tag/v1.11.102) (2025-06-03) +==================================================================================================== +## ✨ Features + +* EW: Modernize the recovery key input modal ([#29819](https://github.com/element-hq/element-web/pull/29819)). Contributed by @uhoreg. +* New room list: move secondary filters into primary filters ([#29972](https://github.com/element-hq/element-web/pull/29972)). Contributed by @florianduros. +* Prompt the user when key storage is unexpectedly off ([#29912](https://github.com/element-hq/element-web/pull/29912)). Contributed by @andybalaam. +* New room list: move sort menu in room list header ([#29983](https://github.com/element-hq/element-web/pull/29983)). Contributed by @florianduros. +* New room list: rework spacing of room list item ([#29965](https://github.com/element-hq/element-web/pull/29965)). Contributed by @florianduros. +* RLS: Remove forgotten room from skiplist ([#29933](https://github.com/element-hq/element-web/pull/29933)). Contributed by @MidhunSureshR. +* Add room list sorting ([#29951](https://github.com/element-hq/element-web/pull/29951)). Contributed by @dbkr. +* Don't use the minimised width(68px) on the new room list ([#29778](https://github.com/element-hq/element-web/pull/29778)). Contributed by @langleyd. + +## 🐛 Bug Fixes + +* [Backport staging] Close call options popup menu when option has been selected ([#30054](https://github.com/element-hq/element-web/pull/30054)). Contributed by @RiotRobot. +* RoomListStoreV3: Only add new rooms that pass `VisibilityProvider` check ([#29974](https://github.com/element-hq/element-web/pull/29974)). Contributed by @MidhunSureshR. +* Re-order primary filters ([#29957](https://github.com/element-hq/element-web/pull/29957)). Contributed by @dbkr. +* Fix leaky CSS adding `!` to all H1 elements ([#29964](https://github.com/element-hq/element-web/pull/29964)). Contributed by @t3chguy. +* Fix extensions panel style ([#29273](https://github.com/element-hq/element-web/pull/29273)). Contributed by @langleyd. +* Fix state events being hidden from widgets in read\_events actions ([#29954](https://github.com/element-hq/element-web/pull/29954)). Contributed by @robintown. +* Remove old filter test ([#29963](https://github.com/element-hq/element-web/pull/29963)). Contributed by @dbkr. + + Changes in [1.11.101](https://github.com/element-hq/element-web/releases/tag/v1.11.101) (2025-05-20) ==================================================================================================== ## ✨ Features diff --git a/Dockerfile b/Dockerfile index a1813cf66d..ceb5fd34b9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,7 +19,7 @@ RUN /src/scripts/docker-package.sh RUN cp /src/config.sample.json /src/webapp/config.json # App -FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:2acffd86b1bdefb8fa6b48b6e9aadf75430e8ab9c43c54c515ea7df77897f987 +FROM nginxinc/nginx-unprivileged:alpine-slim@sha256:66e34aa81c2faf290ea4e4c28a490f2b35a07478265a2d5994c8637506045eee # Need root user to install packages & manipulate the usr directory USER root diff --git a/package.json b/package.json index 058351ac0c..4c15e5c7e7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "element-web", - "version": "1.11.101", + "version": "1.11.103", "description": "Element: the future of secure communication", "author": "New Vector Ltd.", "repository": { @@ -71,10 +71,10 @@ "**/pretty-format/react-is": "19.1.0", "@playwright/test": "1.52.0", "@types/react": "19.1.6", - "@types/react-dom": "19.1.5", + "@types/react-dom": "19.1.6", "oidc-client-ts": "3.2.1", "jwt-decode": "4.0.0", - "caniuse-lite": "1.0.30001720", + "caniuse-lite": "1.0.30001721", "testcontainers": "^11.0.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0", "wrap-ansi": "npm:wrap-ansi@^7.0.0" @@ -138,7 +138,7 @@ "opus-recorder": "^8.0.3", "pako": "^2.0.3", "png-chunks-extract": "^1.0.0", - "posthog-js": "1.248.1", + "posthog-js": "1.249.4", "qrcode": "1.5.4", "re-resizable": "6.11.2", "react": "^19.0.0", @@ -180,7 +180,7 @@ "@babel/preset-typescript": "^7.12.7", "@babel/runtime": "^7.12.5", "@casualbot/jest-sonar-reporter": "2.2.7", - "@element-hq/element-call-embedded": "0.12.0", + "@element-hq/element-call-embedded": "0.12.2", "@element-hq/element-web-playwright-common": "^1.1.5", "@peculiar/webcrypto": "^1.4.3", "@playwright/test": "^1.50.1", @@ -214,7 +214,7 @@ "@types/qrcode": "^1.3.5", "@types/react": "19.1.6", "@types/react-beautiful-dnd": "^13.0.0", - "@types/react-dom": "19.1.5", + "@types/react-dom": "19.1.6", "@types/react-transition-group": "^4.4.0", "@types/sanitize-html": "2.16.0", "@types/semver": "^7.5.8", diff --git a/playwright/e2e/crypto/backups-mas.spec.ts b/playwright/e2e/crypto/backups-mas.spec.ts index 6519a484e9..6a7fff6672 100644 --- a/playwright/e2e/crypto/backups-mas.spec.ts +++ b/playwright/e2e/crypto/backups-mas.spec.ts @@ -23,7 +23,13 @@ test.describe("Encryption state after registration", () => { test("Key backup is enabled by default", async ({ page, mailpitClient, app }, testInfo) => { await page.goto("/#/login"); await page.getByRole("button", { name: "Continue" }).click(); - await registerAccountMas(page, mailpitClient, `alice_${testInfo.testId}`, "alice@email.com", "Pa$sW0rD!"); + await registerAccountMas( + page, + mailpitClient, + `alice_${testInfo.testId}`, + `alice_${testInfo.testId}@email.com`, + "Pa$sW0rD!", + ); // Wait for the ui to load await expect(page.locator(".mx_MatrixChat")).toBeVisible(); @@ -35,7 +41,13 @@ test.describe("Encryption state after registration", () => { test("user is prompted to set up recovery", async ({ page, mailpitClient, app }, testInfo) => { await page.goto("/#/login"); await page.getByRole("button", { name: "Continue" }).click(); - await registerAccountMas(page, mailpitClient, `alice_${testInfo.testId}`, "alice@email.com", "Pa$sW0rD!"); + await registerAccountMas( + page, + mailpitClient, + `alice_${testInfo.testId}`, + `alice_${testInfo.testId}@email.com`, + "Pa$sW0rD!", + ); await page.getByRole("button", { name: "Add room" }).click(); await page.getByRole("menuitem", { name: "New room" }).click(); @@ -64,7 +76,7 @@ test.describe("Key backup reset from elsewhere", () => { await page.goto("/#/login"); await page.getByRole("button", { name: "Continue" }).click(); - await registerAccountMas(page, mailpitClient, testUsername, "alice@email.com", testPassword); + await registerAccountMas(page, mailpitClient, testUsername, `${testUsername}@email.com`, testPassword); await page.getByRole("button", { name: "Add room" }).click(); await page.getByRole("menuitem", { name: "New room" }).click(); diff --git a/playwright/e2e/crypto/device-verification.spec.ts b/playwright/e2e/crypto/device-verification.spec.ts index 64846ac86d..5ae53f649e 100644 --- a/playwright/e2e/crypto/device-verification.spec.ts +++ b/playwright/e2e/crypto/device-verification.spec.ts @@ -22,6 +22,7 @@ import { } from "./utils"; import { type Bot } from "../../pages/bot"; import { Toasts } from "../../pages/toasts.ts"; +import type { ElementAppPage } from "../../pages/ElementAppPage.ts"; test.describe("Device verification", { tag: "@no-webkit" }, () => { let aliceBotClient: Bot; @@ -163,38 +164,44 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => { test("Verify device with Security Phrase during login", async ({ page, app, credentials, homeserver }) => { await logIntoElement(page, credentials); - - // Select the security phrase - await page.locator(".mx_AuthPage").getByRole("button", { name: "Verify with Recovery Key or Phrase" }).click(); - - // Fill the passphrase - const dialog = page.locator(".mx_Dialog"); - await dialog.locator("textarea").fill("new passphrase"); - await dialog.getByRole("button", { name: "Continue", disabled: false }).click(); - - await page.locator(".mx_AuthPage").getByRole("button", { name: "Done" }).click(); - - // Check that our device is now cross-signed - await checkDeviceIsCrossSigned(app); - - // Check that the current device is connected to key backup - // The backup decryption key should be in cache also, as we got it directly from the 4S - await checkDeviceIsConnectedKeyBackup(app, expectedBackupVersion, true); + await enterRecoveryKeyAndCheckVerified(page, app, "new passphrase"); }); test("Verify device with Recovery Key during login", async ({ page, app, credentials, homeserver }) => { + const recoveryKey = (await aliceBotClient.getRecoveryKey()).encodedPrivateKey; + + await logIntoElement(page, credentials); + await enterRecoveryKeyAndCheckVerified(page, app, recoveryKey); + }); + + test("Verify device with Recovery Key from settings", async ({ page, app, credentials }) => { + const recoveryKey = (await aliceBotClient.getRecoveryKey()).encodedPrivateKey; + await logIntoElement(page, credentials); - // Select the security phrase - await page.locator(".mx_AuthPage").getByRole("button", { name: "Verify with Recovery Key or Phrase" }).click(); + /* Dismiss "Verify this device" */ + const authPage = page.locator(".mx_AuthPage"); + await authPage.getByRole("button", { name: "Skip verification for now" }).click(); + await authPage.getByRole("button", { name: "I'll verify later" }).click(); + await page.waitForSelector(".mx_MatrixChat"); - // Fill the recovery key + const settings = await app.settings.openUserSettings("Encryption"); + await settings.getByRole("button", { name: "Verify this device" }).click(); + await enterRecoveryKeyAndCheckVerified(page, app, recoveryKey); + }); + + /** Helper for the three tests above which verify by recovery key */ + async function enterRecoveryKeyAndCheckVerified(page: Page, app: ElementAppPage, recoveryKey: string) { + await page.getByRole("button", { name: "Verify with Recovery Key or Phrase" }).click(); + + // Enter the recovery key const dialog = page.locator(".mx_Dialog"); - const aliceRecoveryKey = await aliceBotClient.getRecoveryKey(); - await dialog.locator("textarea").fill(aliceRecoveryKey.encodedPrivateKey); + // We use `pressSequentially` here to make sure that the FocusLock isn't causing us any problems + // (cf https://github.com/element-hq/element-web/issues/30089) + await dialog.locator("textarea").pressSequentially(recoveryKey); await dialog.getByRole("button", { name: "Continue", disabled: false }).click(); - await page.locator(".mx_AuthPage").getByRole("button", { name: "Done" }).click(); + await page.getByRole("button", { name: "Done" }).click(); // Check that our device is now cross-signed await checkDeviceIsCrossSigned(app); @@ -202,7 +209,7 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => { // Check that the current device is connected to key backup // The backup decryption key should be in cache also, as we got it directly from the 4S await checkDeviceIsConnectedKeyBackup(app, expectedBackupVersion, true); - }); + } test("Handle incoming verification request with SAS", async ({ page, credentials, homeserver, toasts }) => { await logIntoElement(page, credentials); diff --git a/playwright/e2e/left-panel/room-list-panel/room-list-filter-sort.spec.ts b/playwright/e2e/left-panel/room-list-panel/room-list-filter-sort.spec.ts index d3aa060a27..7f87f3f43c 100644 --- a/playwright/e2e/left-panel/room-list-panel/room-list-filter-sort.spec.ts +++ b/playwright/e2e/left-panel/room-list-panel/room-list-filter-sort.spec.ts @@ -22,13 +22,21 @@ test.describe("Room list filters and sort", () => { }); function getPrimaryFilters(page: Page): Locator { - return page.getByRole("listbox", { name: "Room list filters" }); + return page.getByTestId("primary-filters"); } function getRoomOptionsMenu(page: Page): Locator { return page.getByRole("button", { name: "Room Options" }); } + function getFilterExpandButton(page: Page): Locator { + return getPrimaryFilters(page).getByRole("button", { name: "Expand filter list" }); + } + + function getFilterCollapseButton(page: Page): Locator { + return getPrimaryFilters(page).getByRole("button", { name: "Collapse filter list" }); + } + /** * Get the room list * @param page @@ -136,6 +144,7 @@ test.describe("Room list filters and sort", () => { await tile.click(); // Enable Favourite filter + await getFilterExpandButton(page).click(); const primaryFilters = getPrimaryFilters(page); await primaryFilters.getByRole("option", { name: "Favourite" }).click(); await expect(tile).not.toBeVisible(); @@ -223,10 +232,6 @@ test.describe("Room list filters and sort", () => { expect(await roomList.locator("role=gridcell").count()).toBe(4); await expect(primaryFilters).toMatchScreenshot("unread-primary-filters.png"); - await primaryFilters.getByRole("option", { name: "Favourite" }).click(); - await expect(roomList.getByRole("gridcell", { name: "favourite room" })).toBeVisible(); - expect(await roomList.locator("role=gridcell").count()).toBe(1); - await primaryFilters.getByRole("option", { name: "People" }).click(); await expect(roomList.getByRole("gridcell", { name: "unread dm" })).toBeVisible(); await expect(roomList.getByRole("gridcell", { name: "invited room" })).toBeVisible(); @@ -240,6 +245,12 @@ test.describe("Room list filters and sort", () => { await expect(roomList.getByRole("gridcell", { name: "Low prio room" })).toBeVisible(); expect(await roomList.locator("role=gridcell").count()).toBe(5); + await getFilterExpandButton(page).click(); + + await primaryFilters.getByRole("option", { name: "Favourite" }).click(); + await expect(roomList.getByRole("gridcell", { name: "favourite room" })).toBeVisible(); + expect(await roomList.locator("role=gridcell").count()).toBe(1); + await primaryFilters.getByRole("option", { name: "Mentions" }).click(); await expect(roomList.getByRole("gridcell", { name: "room with mention" })).toBeVisible(); expect(await roomList.locator("role=gridcell").count()).toBe(1); @@ -247,6 +258,9 @@ test.describe("Room list filters and sort", () => { await primaryFilters.getByRole("option", { name: "Invites" }).click(); await expect(roomList.getByRole("gridcell", { name: "invited room" })).toBeVisible(); expect(await roomList.locator("role=gridcell").count()).toBe(1); + + await getFilterCollapseButton(page).click(); + await expect(primaryFilters.locator("role=option").first()).toHaveText("Invites"); }); test( @@ -326,6 +340,8 @@ test.describe("Room list filters and sort", () => { { tag: "@screenshot" }, async ({ page, app, user }) => { const primaryFilters = getPrimaryFilters(page); + await getFilterExpandButton(page).click(); + await primaryFilters.getByRole("option", { name: filter }).click(); const emptyRoomList = getEmptyRoomList(page); @@ -343,6 +359,8 @@ test.describe("Room list filters and sort", () => { { tag: "@screenshot" }, async ({ page, app, user }) => { const primaryFilters = getPrimaryFilters(page); + await getFilterExpandButton(page).click(); + await primaryFilters.getByRole("option", { name: filter }).click(); const emptyRoomList = getEmptyRoomList(page); diff --git a/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts b/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts index d9e1922934..73eb98512b 100644 --- a/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts +++ b/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts @@ -255,6 +255,28 @@ test.describe("Room list", () => { await expect(publicRoom).toMatchScreenshot("room-list-item-public.png"); }); + test("should be a low priority room", { tag: "@screenshot" }, async ({ page, app, user }) => { + // @ts-ignore Visibility enum is not accessible + await app.client.createRoom({ name: "low priority room", visibility: "public" }); + const roomListView = getRoomList(page); + const publicRoom = roomListView.getByRole("gridcell", { name: "low priority room" }); + + // Make room low priority + await publicRoom.hover(); + const roomItemMenu = publicRoom.getByRole("button", { name: "More Options" }); + await roomItemMenu.click(); + await page.getByRole("menuitemcheckbox", { name: "Low priority" }).click(); + + // Should have low priority decoration + await expect(publicRoom.locator(".mx_RoomAvatarView_icon")).toHaveAccessibleName( + "This is a low priority room", + ); + + // focus the user menu to avoid to have hover decoration + await page.getByRole("button", { name: "User menu" }).focus(); + await expect(publicRoom).toMatchScreenshot("room-list-item-low-priority.png"); + }); + test("should be a video room", { tag: "@screenshot" }, async ({ page, app, user }) => { await page.getByTestId("room-list-panel").getByRole("button", { name: "Add" }).click(); await page.getByRole("menuitem", { name: "New video room" }).click(); @@ -333,10 +355,11 @@ test.describe("Room list", () => { }); test("should render a message preview", { tag: "@screenshot" }, async ({ page, app, user, bot }) => { - const roomListView = getRoomList(page); + await app.settings.openUserSettings("Preferences"); + await page.getByRole("switch", { name: "Show message previews" }).click(); + await app.closeDialog(); - await page.getByRole("button", { name: "Room Options" }).click(); - await page.getByRole("menuitemcheckbox", { name: "Show message previews" }).click(); + const roomListView = getRoomList(page); const roomId = await app.client.createRoom({ name: "activity" }); diff --git a/playwright/e2e/oidc/oidc-native.spec.ts b/playwright/e2e/oidc/oidc-native.spec.ts index e985e58d09..a6fbf231ce 100644 --- a/playwright/e2e/oidc/oidc-native.spec.ts +++ b/playwright/e2e/oidc/oidc-native.spec.ts @@ -33,7 +33,7 @@ test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => { await page.getByRole("button", { name: "Continue" }).click(); const userId = `alice_${testInfo.testId}`; - await registerAccountMas(page, mailpitClient, userId, "alice@email.com", "Pa$sW0rD!"); + await registerAccountMas(page, mailpitClient, userId, `${userId}@email.com`, "Pa$sW0rD!"); // Eventually, we should end up at the home screen. await expect(page).toHaveURL(/\/#\/home$/, { timeout: 10000 }); @@ -55,7 +55,7 @@ test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => { const newPage = await newPagePromise; await newPage.getByText("Devices").click(); await newPage.getByText(deviceId).click(); - await expect(newPage.getByText("Element")).toBeVisible(); + await expect(newPage.getByText("Element", { exact: true })).toBeVisible(); await expect(newPage.getByText("http://localhost:8080/")).toBeVisible(); await expect(newPage).toHaveURL(/\/oauth2_session/); await newPage.close(); @@ -83,7 +83,7 @@ test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => { await page.getByRole("button", { name: "Continue" }).click(); const userId = `alice_${testInfo.testId}`; - await registerAccountMas(page, mailpitClient, userId, "alice@email.com", "Pa$sW0rD!"); + await registerAccountMas(page, mailpitClient, userId, `${userId}@email.com`, "Pa$sW0rD!"); await expect(page.getByText("Welcome")).toBeVisible(); await page.goto("about:blank"); diff --git a/playwright/e2e/settings/preferences-user-settings-tab.spec.ts b/playwright/e2e/settings/preferences-user-settings-tab.spec.ts index 33392d8848..e295eceb44 100644 --- a/playwright/e2e/settings/preferences-user-settings-tab.spec.ts +++ b/playwright/e2e/settings/preferences-user-settings-tab.spec.ts @@ -21,10 +21,12 @@ test.describe("Preferences user settings tab", () => { const locator = await app.settings.openUserSettings("Preferences"); await use(locator); }, + // display message preview settings + labsFlags: ["feature_new_room_list"], }); test("should be rendered properly", { tag: "@screenshot" }, async ({ app, page, user }) => { - await page.setViewportSize({ width: 1024, height: 3300 }); + await page.setViewportSize({ width: 1024, height: 4000 }); const tab = await app.settings.openUserSettings("Preferences"); // Assert that the top heading is rendered await expect(tab.getByRole("heading", { name: "Preferences" })).toBeVisible(); diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Favourite-empty-room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Favourite-empty-room-list-linux.png index 6c71c11b1d..8cf4a3c97b 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Favourite-empty-room-list-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Favourite-empty-room-list-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Invites-empty-room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Invites-empty-room-list-linux.png index c7396da41d..c770da3377 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Invites-empty-room-list-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Invites-empty-room-list-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Mentions-empty-room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Mentions-empty-room-list-linux.png index 15620d3612..44e27d9618 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Mentions-empty-room-list-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Mentions-empty-room-list-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/People-empty-room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/People-empty-room-list-linux.png index 7b73e0b819..3f700037cb 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/People-empty-room-list-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/People-empty-room-list-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Rooms-empty-room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Rooms-empty-room-list-linux.png index eb3add5733..e01564eeb4 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Rooms-empty-room-list-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Rooms-empty-room-list-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Unreads-empty-room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Unreads-empty-room-list-linux.png index 70ed4bb782..94b09ac14f 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Unreads-empty-room-list-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Unreads-empty-room-list-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/default-empty-room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/default-empty-room-list-linux.png index 6781c1d364..45d2a775ea 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/default-empty-room-list-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/default-empty-room-list-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/room-panel-empty-room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/room-panel-empty-room-list-linux.png index 34924cf69f..fc391d56cf 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/room-panel-empty-room-list-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/room-panel-empty-room-list-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unread-dm-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unread-dm-linux.png index 2f12ee4e41..13577e0a1b 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unread-dm-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unread-dm-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unread-primary-filters-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unread-primary-filters-linux.png index f0cda0b577..ac8abd60ad 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unread-primary-filters-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unread-primary-filters-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unselected-primary-filters-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unselected-primary-filters-linux.png index 7d63f923b0..0b879c18fe 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unselected-primary-filters-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unselected-primary-filters-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-header.spec.ts/room-list-header-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-header.spec.ts/room-list-header-linux.png index 46ff1a53be..5e6ddff442 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-header.spec.ts/room-list-header-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-header.spec.ts/room-list-header-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-header.spec.ts/room-list-header-space-menu-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-header.spec.ts/room-list-header-space-menu-linux.png index c706e71b0f..43d8781239 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-header.spec.ts/room-list-header-space-menu-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-header.spec.ts/room-list-header-space-menu-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-header.spec.ts/room-list-space-header-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-header.spec.ts/room-list-space-header-linux.png index 3a4aea566e..c42c449281 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-header.spec.ts/room-list-space-header-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-header.spec.ts/room-list-space-header-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-linux.png index 9b130b73c4..75cea916c9 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-smallscreen-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-smallscreen-linux.png index 92b81245a2..e023a0d34d 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-smallscreen-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list-panel.spec.ts/room-list-panel-smallscreen-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-activity-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-activity-linux.png index aa73d79988..bc1aa9f4f1 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-activity-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-activity-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-linux.png index fba408c922..1315363e7e 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-silent-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-silent-linux.png index 36b7304a01..b400beac7c 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-silent-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-silent-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-invited-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-invited-linux.png index 1f2b691b4a..44d90bac34 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-invited-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-invited-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-low-priority-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-low-priority-linux.png new file mode 100644 index 0000000000..fe5ef29ecf Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-low-priority-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mark-as-unread-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mark-as-unread-linux.png index 310912e50d..86032973a3 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mark-as-unread-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mark-as-unread-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mention-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mention-linux.png index 9fa531f5b1..b134c90d3a 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mention-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-mention-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-message-preview-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-message-preview-linux.png index dac349eb2d..8970c20cb5 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-message-preview-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-message-preview-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-notification-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-notification-linux.png index 144604ffeb..0c99720d01 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-notification-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-notification-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png index 99bb312695..e9f1db7361 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png index c91ebf3b3a..b638b2880e 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-selection-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-selection-linux.png index 4b3ac052ca..4307750787 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-selection-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-selection-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-public-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-public-linux.png index e951f77ef2..c75c1d08cc 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-public-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-public-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-silent-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-silent-linux.png index 57c5cb1eb7..cf8bb5a058 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-silent-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-silent-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-video-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-video-linux.png index 16ea458274..e6d3d50e93 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-video-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-video-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-linux.png index f2b625f498..8fab88fc7f 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-scrolled-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-scrolled-linux.png index a1752e30d3..85a30b02f2 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-scrolled-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-scrolled-linux.png differ diff --git a/playwright/snapshots/settings/preferences-user-settings-tab.spec.ts/Preferences-user-settings-tab-should-be-rendered-properly-1-linux.png b/playwright/snapshots/settings/preferences-user-settings-tab.spec.ts/Preferences-user-settings-tab-should-be-rendered-properly-1-linux.png index 5f92c00fc3..c484a47fc9 100644 Binary files a/playwright/snapshots/settings/preferences-user-settings-tab.spec.ts/Preferences-user-settings-tab-should-be-rendered-properly-1-linux.png and b/playwright/snapshots/settings/preferences-user-settings-tab.spec.ts/Preferences-user-settings-tab-should-be-rendered-properly-1-linux.png differ diff --git a/res/css/views/dialogs/security/_AccessSecretStorageDialog.pcss b/res/css/views/dialogs/security/_AccessSecretStorageDialog.pcss index 943ec3a41f..9d81097d60 100644 --- a/res/css/views/dialogs/security/_AccessSecretStorageDialog.pcss +++ b/res/css/views/dialogs/security/_AccessSecretStorageDialog.pcss @@ -32,7 +32,7 @@ Please see LICENSE files in the repository root for full details. color: $alert; &::before { - mask-image: url("@vector-im/compound-design-tokens/icons/close.svg"); + mask-image: url("@vector-im/compound-design-tokens/icons/error-solid.svg"); background-color: $alert; } } diff --git a/res/css/views/rooms/RoomListPanel/_RoomListPrimaryFilters.pcss b/res/css/views/rooms/RoomListPanel/_RoomListPrimaryFilters.pcss index ac85782bbd..f8fc31ae12 100644 --- a/res/css/views/rooms/RoomListPanel/_RoomListPrimaryFilters.pcss +++ b/res/css/views/rooms/RoomListPanel/_RoomListPrimaryFilters.pcss @@ -6,7 +6,32 @@ */ .mx_RoomListPrimaryFilters { - margin: unset; - list-style-type: none; - padding: var(--cpd-space-2x) var(--cpd-space-3x); + padding: var(--cpd-space-2x) var(--cpd-space-4x) var(--cpd-space-2x) var(--cpd-space-3x); + + .mx_RoomListPrimaryFilters_wrapping { + display: none; + } + + ul { + margin: unset; + padding: unset; + list-style-type: none; + /** + * The InteractionObserver needs the height to be set to work properly. + */ + height: 100%; + flex: 1; + } + + .mx_RoomListPrimaryFilters_IconButton { + svg { + transition: transform 0.1s linear; + } + } + + .mx_RoomListPrimaryFilters_IconButton[aria-expanded="true"] { + svg { + transform: rotate(180deg); + } + } } diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index f19a8591cb..d4ae6c897b 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -128,13 +128,19 @@ declare global { } interface Electron { + // Legacy on(channel: ElectronChannel, listener: (event: Event, ...args: any[]) => void): void; send(channel: ElectronChannel, ...args: any[]): void; + // Initialisation initialise(): Promise<{ protocol: string; sessionId: string; config: IConfigOptions; + supportedSettings: Record; }>; + // Settings + setSettingValue(settingName: string, value: any): Promise; + getSettingValue(settingName: string): Promise; } interface DesktopCapturerSource { diff --git a/src/@types/invite-rules.ts b/src/@types/invite-rules.ts new file mode 100644 index 0000000000..bc72a5e922 --- /dev/null +++ b/src/@types/invite-rules.ts @@ -0,0 +1,29 @@ +/* +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. +*/ + +export const INVITE_RULES_ACCOUNT_DATA_TYPE = "org.matrix.msc4155.invite_permission_config"; + +export interface InviteConfigAccountData { + allowed_users?: string[]; + blocked_users?: string[]; + ignored_users?: string[]; + allowed_servers?: string[]; + blocked_servers?: string[]; + ignored_servers?: string[]; +} + +/** + * Computed values based on MSC4155. Currently Element Web only supports + * blocking all invites. + */ +export interface ComputedInviteConfig extends Record { + /** + * Are all invites blocked. This is only about blocking all invites, + * but this being false may still block invites through other rules. + */ + allBlocked: boolean; +} diff --git a/src/@types/matrix-js-sdk.d.ts b/src/@types/matrix-js-sdk.d.ts index c81c5377bf..e1f8c4562e 100644 --- a/src/@types/matrix-js-sdk.d.ts +++ b/src/@types/matrix-js-sdk.d.ts @@ -15,6 +15,7 @@ import type { EmptyObject } from "matrix-js-sdk/src/matrix"; import type { DeviceClientInformation } from "../utils/device/types.ts"; import type { UserWidget } from "../utils/WidgetUtils-types.ts"; import { type MediaPreviewConfig } from "./media_preview.ts"; +import { type INVITE_RULES_ACCOUNT_DATA_TYPE, type InviteConfigAccountData } from "./invite-rules.ts"; // Extend Matrix JS SDK types via Typescript declaration merging to support unspecced event fields and types declare module "matrix-js-sdk/src/types" { @@ -60,7 +61,6 @@ declare module "matrix-js-sdk/src/types" { }; }; } - export interface AccountDataEvents { // Analytics account data event "im.vector.analytics": { @@ -89,6 +89,8 @@ declare module "matrix-js-sdk/src/types" { accepted: string[]; }; + // MSC4155: Invite filtering + [INVITE_RULES_ACCOUNT_DATA_TYPE]: InviteConfigAccountData; "io.element.msc4278.media_preview_config": MediaPreviewConfig; } diff --git a/src/ContentMessages.ts b/src/ContentMessages.ts index c5e34d7130..54aaea3ae1 100644 --- a/src/ContentMessages.ts +++ b/src/ContentMessages.ts @@ -63,6 +63,7 @@ import { blobIsAnimated } from "./utils/Image.ts"; const PHYS_HIDPI = [0x00, 0x00, 0x16, 0x25, 0x00, 0x00, 0x16, 0x25, 0x01]; export class UploadCanceledError extends Error {} +export class UploadFailedError extends Error {} interface IMediaConfig { "m.upload.size"?: number; @@ -355,12 +356,19 @@ export async function uploadFile( // Pass the encrypted data as a Blob to the uploader. const blob = new Blob([encryptResult.data]); - const { content_uri: url } = await matrixClient.uploadContent(blob, { - progressHandler, - abortController, - includeFilename: false, - type: "application/octet-stream", - }); + let url: string; + try { + ({ content_uri: url } = await matrixClient.uploadContent(blob, { + progressHandler, + abortController, + includeFilename: false, + type: "application/octet-stream", + })); + } catch (e) { + if (abortController.signal.aborted) throw new UploadCanceledError(); + console.error("Failed to upload file", e); + throw new UploadFailedError(); + } if (abortController.signal.aborted) throw new UploadCanceledError(); // If the attachment is encrypted then bundle the URL along with the information @@ -372,7 +380,14 @@ export async function uploadFile( } as EncryptedFile, }; } else { - const { content_uri: url } = await matrixClient.uploadContent(file, { progressHandler, abortController }); + let url: string; + try { + ({ content_uri: url } = await matrixClient.uploadContent(file, { progressHandler, abortController })); + } catch (e) { + if (abortController.signal.aborted) throw new UploadCanceledError(); + console.error("Failed to upload file", e); + throw new UploadFailedError(); + } if (abortController.signal.aborted) throw new UploadCanceledError(); // If the attachment isn't encrypted then include the URL directly. return { url }; @@ -570,7 +585,7 @@ export default class ContentMessages { const imageInfo = await infoForImageFile(matrixClient, roomId, file); Object.assign(content.info, imageInfo); } catch (e) { - if (e instanceof HTTPError) { + if (e instanceof UploadFailedError) { // re-throw to main upload error handler throw e; } diff --git a/src/CreateCrossSigning.ts b/src/CreateCrossSigning.ts index c8e7aa3e73..5f6f3e48aa 100644 --- a/src/CreateCrossSigning.ts +++ b/src/CreateCrossSigning.ts @@ -38,10 +38,10 @@ export async function createCrossSigning(cli: MatrixClient): Promise { export async function uiAuthCallback( matrixClient: MatrixClient, - makeRequest: (authData: AuthDict) => Promise, + makeRequest: (authData: AuthDict | null) => Promise, ): Promise { try { - await makeRequest({}); + await makeRequest(null); } catch (error) { if (!(error instanceof MatrixError) || !error.data || !error.data.flows) { // Not a UIA response diff --git a/src/SecurityManager.ts b/src/SecurityManager.ts index b497c57a10..bfd0eb237d 100644 --- a/src/SecurityManager.ts +++ b/src/SecurityManager.ts @@ -19,7 +19,6 @@ import AccessSecretStorageDialog, { type KeyParams, } from "./components/views/dialogs/security/AccessSecretStorageDialog"; import { ModuleRunner } from "./modules/ModuleRunner"; -import QuestionDialog from "./components/views/dialogs/QuestionDialog"; import InteractiveAuthDialog from "./components/views/dialogs/InteractiveAuthDialog"; // This stores the secret storage private keys in memory for the JS SDK. This is @@ -50,17 +49,6 @@ export class AccessCancelledError extends Error { } } -async function confirmToDismiss(): Promise { - const [sure] = await Modal.createDialog(QuestionDialog, { - title: _t("encryption|cancel_entering_passphrase_title"), - description: _t("encryption|cancel_entering_passphrase_description"), - danger: false, - button: _t("action|go_back"), - cancelButton: _t("action|cancel"), - }).finished; - return !sure; -} - function makeInputToKey( keyInfo: SecretStorage.SecretStorageKeyDescription, ): (keyParams: KeyParams) => Promise { @@ -134,17 +122,6 @@ async function getSecretStorageKey( return MatrixClientPeg.safeGet().secretStorage.checkKey(key, keyInfo); }, }, - /* className= */ undefined, - /* isPriorityModal= */ false, - /* isStaticModal= */ false, - /* options= */ { - onBeforeClose: async (reason): Promise => { - if (reason === "backgroundClick") { - return confirmToDismiss(); - } - return true; - }, - }, ); const [keyParams] = await finished; if (!keyParams) { diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index afbfeeca03..ed2382c3ff 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -60,6 +60,7 @@ import { deop, op } from "./slash-commands/op"; import { CommandCategories } from "./slash-commands/interface"; import { Command } from "./slash-commands/command"; import { goto, join } from "./slash-commands/join"; +import { manuallyVerifyDevice } from "./components/views/dialogs/ManualDeviceKeyVerificationDialog"; export { CommandCategories, Command }; @@ -663,6 +664,36 @@ export const Commands = [ category: CommandCategories.admin, renderingTypes: [TimelineRenderingType.Room], }), + new Command({ + command: "verify", + args: " ", + description: _td("slash_command|verify"), + runFn: function (cli, _roomId, _threadId, args) { + if (args) { + const matches = args.match(/^(\S+) +(\S+)$/); + if (matches) { + const deviceId = matches[1]; + const fingerprint = matches[2]; + + const { finished } = Modal.createDialog(QuestionDialog, { + title: _t("slash_command|manual_device_verification_confirm_title"), + description: _t("slash_command|manual_device_verification_confirm_description"), + button: _t("action|verify"), + danger: true, + }); + + return success( + finished.then(([confirmed]) => { + if (confirmed) manuallyVerifyDevice(cli, deviceId, fingerprint); + }), + ); + } + } + return reject(this.getUsage()); + }, + category: CommandCategories.advanced, + renderingTypes: [TimelineRenderingType.Room], + }), new Command({ command: "discardsession", description: _td("slash_command|discardsession"), diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index fbff50bd01..d64f4b7a36 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -370,6 +370,10 @@ export class RoomView extends React.Component { private unmounted = false; private permalinkCreators: Record = {}; + // The userId from which we received this invite. + // Only populated if the membership of our user is invite. + private inviter?: string; + private roomView = createRef(); private searchResultsPanel = createRef(); private messagePanel: TimelinePanel | null = null; @@ -1350,6 +1354,11 @@ export class RoomView extends React.Component { // after a successful peek, or after we join the room). private onRoomLoaded = (room: Room): void => { if (this.unmounted) return; + + // Store the inviter so that we can know who invited us to this room even if + // the membership event changes. + this.inviter = this.getInviterFromRoom(room); + // Attach a widget store listener only when we get a room this.context.widgetLayoutStore.on(WidgetLayoutStore.emissionForRoom(room), this.onWidgetLayoutChange); @@ -1729,8 +1738,20 @@ export class RoomView extends React.Component { }); }; + private getInviterFromRoom(room: Room): string | undefined { + const ownUserId = this.context.client?.getSafeUserId(); + if (!ownUserId) return; + + const myMember = room.getMember(ownUserId); + const memberEvent = myMember?.events.member; + const senderId = memberEvent?.getSender(); + + if (memberEvent?.getContent().membership === KnownMembership.Invite) return senderId; + } + private onDeclineAndBlockButtonClicked = async (): Promise => { if (!this.state.room || !this.context.client) return; + const [shouldReject, ignoreUser, reportRoom] = await Modal.createDialog(DeclineAndBlockInviteDialog, { roomName: this.state.room.name, }).finished; @@ -1745,11 +1766,20 @@ export class RoomView extends React.Component { const actions: Promise[] = []; if (ignoreUser) { - const myMember = this.state.room.getMember(this.context.client!.getSafeUserId()); - const inviteEvent = myMember!.events.member; - const ignoredUsers = this.context.client.getIgnoredUsers(); - ignoredUsers.push(inviteEvent!.getSender()!); // de-duped internally in the js-sdk - actions.push(this.context.client.setIgnoredUsers(ignoredUsers)); + const doIgnore = async (): Promise => { + const ownUserId = this.context.client!.getSafeUserId(); + if (!this.inviter || this.inviter === ownUserId) { + // This is unlikely to happen since we cache the inviter as early as possible. + // However, we still do this check here to be double sure. + throw new CannotDetermineUserError( + "Cannot determine which user to ignore since the member event has changed.", + ); + } + const ignoredUsers = this.context.client!.getIgnoredUsers(); + ignoredUsers.push(this.inviter); // de-duped internally in the js-sdk + await this.context.client!.setIgnoredUsers(ignoredUsers); + }; + actions.push(doIgnore()); } if (reportRoom !== false) { @@ -1766,7 +1796,14 @@ export class RoomView extends React.Component { } catch (error) { logger.error(`Failed to reject invite: ${error}`); - const msg = error instanceof Error ? error.message : JSON.stringify(error); + let msg: string = ""; + if (error instanceof CannotDetermineUserError) { + msg = _t("room|failed_determine_user"); + } else if (error instanceof Error) { + msg = error.message; + } else { + msg = JSON.stringify(error); + } Modal.createDialog(ErrorDialog, { title: _t("room|failed_reject_invite"), description: msg, @@ -1783,6 +1820,9 @@ export class RoomView extends React.Component { return; } try { + this.setState({ + rejecting: true, + }); await this.context.client.leave(this.state.room.roomId); defaultDispatcher.dispatch({ action: Action.ViewHomePage }); this.setState({ @@ -2612,3 +2652,7 @@ export class RoomView extends React.Component { ); } } + +class CannotDetermineUserError extends Error { + public name = "CannotDetermineUserError"; +} diff --git a/src/components/viewmodels/avatars/RoomAvatarViewModel.tsx b/src/components/viewmodels/avatars/RoomAvatarViewModel.tsx index 8879f5ae69..3832616a9c 100644 --- a/src/components/viewmodels/avatars/RoomAvatarViewModel.tsx +++ b/src/components/viewmodels/avatars/RoomAvatarViewModel.tsx @@ -10,26 +10,26 @@ import { useEffect, useState } from "react"; import { useTypedEventEmitter } from "../../../hooks/useEventEmitter"; import { useDmMember, usePresence, type Presence } from "../../views/avatars/WithPresenceIndicator"; +import { DefaultTagID } from "../../../stores/room-list/models"; + +export enum AvatarBadgeDecoration { + LowPriority = "LowPriority", + VideoRoom = "VideoRoom", + PublicRoom = "PublicRoom", + Presence = "Presence", +} export interface RoomAvatarViewState { - /** - * Whether the room avatar has a decoration. - * A decoration can be a public or a video call icon or an indicator of presence. - */ - hasDecoration: boolean; - /** - * Whether the room is public. - */ - isPublic: boolean; - /** - * Whether the room is a video room. - */ - isVideoRoom: boolean; /** * The presence of the user in the DM room. * If null, the user is not in a DM room or presence is not enabled. */ presence: Presence | null; + + /** + * The decoration that should be rendered. + */ + badgeDecoration?: AvatarBadgeDecoration; } /** @@ -41,10 +41,20 @@ export function useRoomAvatarViewModel(room: Room): RoomAvatarViewState { const roomMember = useDmMember(room); const presence = usePresence(room, roomMember); const isPublic = useIsPublic(room); + const isLowPriority = !!room.tags[DefaultTagID.LowPriority]; - const hasDecoration = isPublic || isVideoRoom || presence !== null; + let badgeDecoration: AvatarBadgeDecoration | undefined; + if (isLowPriority) { + badgeDecoration = AvatarBadgeDecoration.LowPriority; + } else if (isVideoRoom) { + badgeDecoration = AvatarBadgeDecoration.VideoRoom; + } else if (isPublic) { + badgeDecoration = AvatarBadgeDecoration.PublicRoom; + } else if (presence) { + badgeDecoration = AvatarBadgeDecoration.Presence; + } - return { hasDecoration, isPublic, isVideoRoom, presence }; + return { badgeDecoration, presence }; } /** diff --git a/src/components/viewmodels/roomlist/RoomListHeaderViewModel.tsx b/src/components/viewmodels/roomlist/RoomListHeaderViewModel.tsx index d6f54fcae7..451a4898b7 100644 --- a/src/components/viewmodels/roomlist/RoomListHeaderViewModel.tsx +++ b/src/components/viewmodels/roomlist/RoomListHeaderViewModel.tsx @@ -32,7 +32,6 @@ import { useMatrixClientContext } from "../../../contexts/MatrixClientContext"; import type { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; import { createRoom, hasCreateRoomRights } from "./utils"; import { type SortOption, useSorter } from "./useSorter"; -import { useMessagePreviewToggle } from "./useMessagePreviewToggle"; /** * Hook to get the active space and its title. @@ -127,14 +126,6 @@ export interface RoomListHeaderViewState { * The currently active sort option. */ activeSortOption: SortOption; - /** - * Whether message previews must be shown or not. - */ - shouldShowMessagePreview: boolean; - /** - * A function to turn on/off message previews. - */ - toggleMessagePreview: () => void; } /** @@ -157,7 +148,6 @@ export function useRoomListHeaderViewModel(): RoomListHeaderViewState { /* Actions */ const { activeSortOption, sort } = useSorter(); - const { shouldShowMessagePreview, toggleMessagePreview } = useMessagePreviewToggle(); const createChatRoom = useCallback((e: Event) => { defaultDispatcher.fire(Action.CreateChat); @@ -230,7 +220,5 @@ export function useRoomListHeaderViewModel(): RoomListHeaderViewState { openSpaceSettings, activeSortOption, sort, - shouldShowMessagePreview, - toggleMessagePreview, }; } diff --git a/src/components/viewmodels/roomlist/RoomListItemMenuViewModel.tsx b/src/components/viewmodels/roomlist/RoomListItemMenuViewModel.tsx index 997b515f27..738a05b8c3 100644 --- a/src/components/viewmodels/roomlist/RoomListItemMenuViewModel.tsx +++ b/src/components/viewmodels/roomlist/RoomListItemMenuViewModel.tsx @@ -37,6 +37,10 @@ export interface RoomListItemMenuViewState { * Whether the room is a favourite room. */ isFavourite: boolean; + /** + * Whether the room is a low priority room. + */ + isLowPriority: boolean; /** * Can invite other user's in the room. */ @@ -117,6 +121,7 @@ export function useRoomListItemMenuViewModel(room: Room): RoomListItemMenuViewSt const isDm = Boolean(DMRoomMap.shared().getUserIdForRoomId(room.roomId)); const isFavourite = Boolean(roomTags[DefaultTagID.Favourite]); + const isLowPriority = Boolean(roomTags[DefaultTagID.LowPriority]); const isArchived = Boolean(roomTags[DefaultTagID.Archived]); const showMoreOptionsMenu = hasAccessToOptionsMenu(room); @@ -200,6 +205,7 @@ export function useRoomListItemMenuViewModel(room: Room): RoomListItemMenuViewSt showMoreOptionsMenu, showNotificationMenu, isFavourite, + isLowPriority, canInvite, canCopyRoomLink, canMarkAsRead, diff --git a/src/components/viewmodels/roomlist/useFilteredRooms.tsx b/src/components/viewmodels/roomlist/useFilteredRooms.tsx index c3ed4df2ae..736f75aa57 100644 --- a/src/components/viewmodels/roomlist/useFilteredRooms.tsx +++ b/src/components/viewmodels/roomlist/useFilteredRooms.tsx @@ -50,6 +50,7 @@ const filterKeyToNameMap: Map = new Map([ [FilterKey.MentionsFilter, _td("room_list|filters|mentions")], [FilterKey.InvitesFilter, _td("room_list|filters|invites")], [FilterKey.FavouriteFilter, _td("room_list|filters|favourite")], + [FilterKey.LowPriorityFilter, _td("room_list|filters|low_priority")], ]); /** diff --git a/src/components/views/avatars/RoomAvatarView.tsx b/src/components/views/avatars/RoomAvatarView.tsx index 8810d073c5..5ddf355d6f 100644 --- a/src/components/views/avatars/RoomAvatarView.tsx +++ b/src/components/views/avatars/RoomAvatarView.tsx @@ -9,13 +9,14 @@ import React, { type JSX } from "react"; import { type Room } from "matrix-js-sdk/src/matrix"; import PublicIcon from "@vector-im/compound-design-tokens/assets/web/icons/public"; import VideoIcon from "@vector-im/compound-design-tokens/assets/web/icons/video-call-solid"; +import ArrowDownIcon from "@vector-im/compound-design-tokens/assets/web/icons/arrow-down"; import OnlineOrUnavailableIcon from "@vector-im/compound-design-tokens/assets/web/icons/presence-solid-8x8"; import OfflineIcon from "@vector-im/compound-design-tokens/assets/web/icons/presence-outline-8x8"; import BusyIcon from "@vector-im/compound-design-tokens/assets/web/icons/presence-strikethrough-8x8"; import classNames from "classnames"; import RoomAvatar from "./RoomAvatar"; -import { useRoomAvatarViewModel } from "../../viewmodels/avatars/RoomAvatarViewModel"; +import { AvatarBadgeDecoration, useRoomAvatarViewModel } from "../../viewmodels/avatars/RoomAvatarViewModel"; import { _t } from "../../../languageHandler"; import { Presence } from "./WithPresenceIndicator"; @@ -33,41 +34,21 @@ interface RoomAvatarViewProps { export function RoomAvatarView({ room }: RoomAvatarViewProps): JSX.Element { const vm = useRoomAvatarViewModel(room); // No decoration, we just show the avatar - if (!vm.hasDecoration) return ; + if (!vm.badgeDecoration) return ; + + const icon = getAvatarDecoration(vm.badgeDecoration, vm.presence); + + // Presence indicator and video/public icons don't have the same size + // We use different masks + const maskClass = + vm.badgeDecoration === AvatarBadgeDecoration.Presence + ? "mx_RoomAvatarView_RoomAvatar_presence" + : "mx_RoomAvatarView_RoomAvatar_icon"; return (
- - - {/* If the room is a public video room, we prefer to display only the video icon */} - {vm.isPublic && !vm.isVideoRoom && ( - - )} - {vm.isVideoRoom && ( - - )} - {vm.presence && } + + {icon}
); } @@ -126,3 +107,39 @@ function PresenceDecoration({ presence }: PresenceDecorationProps): JSX.Element ); } } + +function getAvatarDecoration(decoration: AvatarBadgeDecoration, presence: Presence | null): React.ReactNode { + if (decoration === AvatarBadgeDecoration.LowPriority) { + return ( + + ); + } else if (decoration === AvatarBadgeDecoration.VideoRoom) { + return ( + + ); + } else if (decoration === AvatarBadgeDecoration.PublicRoom) { + return ( + + ); + } else if (decoration === AvatarBadgeDecoration.Presence) { + return ; + } +} diff --git a/src/components/views/dialogs/BaseDialog.tsx b/src/components/views/dialogs/BaseDialog.tsx index bf23919771..56f6d0c528 100644 --- a/src/components/views/dialogs/BaseDialog.tsx +++ b/src/components/views/dialogs/BaseDialog.tsx @@ -23,13 +23,25 @@ import { getKeyBindingsManager } from "../../../KeyBindingsManager"; import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts"; interface IProps { - // Whether the dialog should have a 'close' button that will - // cause the dialog to be cancelled. This should only be set - // to false if there is nothing the app can sensibly do if the - // dialog is cancelled, eg. "We can't restore your session and - // the app cannot work". Default: true. + /** + * Whether the dialog should have a 'close' button and a keyDown handler which + * will intercept 'Escape'. + * + * This should only be set to `false` if there is nothing the app can sensibly do if the + * dialog is cancelled, eg. "We can't restore your session and + * the app cannot work". + * + * Default: `true`. + */ "hasCancel"?: boolean; + /** + * Callback that will be called when the 'close' button is clicked or 'Escape' is pressed. + * + * Not used if `hasCancel` is false. + */ + "onFinished"?: () => void; + // called when a key is pressed "onKeyDown"?: (e: KeyboardEvent | React.KeyboardEvent) => void; @@ -66,7 +78,6 @@ interface IProps { // optional Posthog ScreenName to supply during the lifetime of this dialog "screenName"?: ScreenName; - onFinished(): void; } /* @@ -103,13 +114,13 @@ export default class BaseDialog extends React.Component { e.stopPropagation(); e.preventDefault(); - this.props.onFinished(); + this.props.onFinished?.(); break; } }; private onCancelClick = (): void => { - this.props.onFinished(); + this.props.onFinished?.(); }; public render(): React.ReactNode { diff --git a/src/components/views/dialogs/ManualDeviceKeyVerificationDialog.tsx b/src/components/views/dialogs/ManualDeviceKeyVerificationDialog.tsx new file mode 100644 index 0000000000..566061f73b --- /dev/null +++ b/src/components/views/dialogs/ManualDeviceKeyVerificationDialog.tsx @@ -0,0 +1,171 @@ +/* +Copyright 2024-2025 New Vector Ltd. +Copyright 2020 The Matrix.org Foundation C.I.C. +Copyright 2019 New Vector Ltd +Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> +Copyright 2017 Vector Creations Ltd +Copyright 2016 OpenMarket Ltd + +SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only +Please see LICENSE files in the repository root for full details. +*/ + +import React, { type ChangeEvent, type JSX, useCallback, useState } from "react"; +import { type MatrixClient } from "matrix-js-sdk/src/matrix"; + +import { _t, UserFriendlyError } from "../../../languageHandler"; +import { getDeviceCryptoInfo } from "../../../utils/crypto/deviceInfo"; +import QuestionDialog from "./QuestionDialog"; +import Modal from "../../../Modal"; +import InfoDialog from "./InfoDialog"; +import Field from "../elements/Field"; +import ErrorDialog from "./ErrorDialog"; +import { MatrixClientPeg } from "../../../MatrixClientPeg"; + +interface Props { + onFinished(confirm?: boolean): void; +} + +/** + * A dialog to allow us to verify devices logged in with clients that can't do + * the verification themselves. Intended for use as a dev tool. + * + * Requires entering the fingerprint ("session key") of the device in an attempt + * to prevent users being tricked into verifying a malicious device. + */ +export function ManualDeviceKeyVerificationDialog({ onFinished }: Readonly): JSX.Element { + const [deviceId, setDeviceId] = useState(""); + const [fingerprint, setFingerprint] = useState(""); + + const client = MatrixClientPeg.safeGet(); + + const onDialogFinished = useCallback( + async (confirm: boolean) => { + if (confirm) { + await manuallyVerifyDevice(client, deviceId, fingerprint); + } + onFinished(confirm); + }, + [client, deviceId, fingerprint, onFinished], + ); + + const onDeviceIdChange = useCallback((e: ChangeEvent) => { + setDeviceId(e.target.value); + }, []); + + const onFingerprintChange = useCallback((e: ChangeEvent) => { + setFingerprint(e.target.value); + }, []); + + const body = ( +
+

{_t("encryption|verification|manual|text")}

+
+ + +
+
+ ); + + return ( + + ); +} + +/** + * Check the supplied fingerprint matches the fingerprint ("session key") of the + * device with the supplied device ID, and if so, mark the device as verified. + */ +export async function manuallyVerifyDevice(client: MatrixClient, deviceId: string, fingerprint: string): Promise { + try { + await doManuallyVerifyDevice(client, deviceId, fingerprint); + + // Tell the user we verified everything + Modal.createDialog(InfoDialog, { + title: _t("encryption|verification|manual|success_title"), + description: ( +
+

{_t("encryption|verification|manual|success_description", { deviceId })}

+
+ ), + }); + } catch (e: any) { + // Display an error + const error = e instanceof UserFriendlyError ? e.translatedMessage : e.toString(); + Modal.createDialog(ErrorDialog, { + title: _t("encryption|verification|manual|failure_title"), + description: ( +
+

{_t("encryption|verification|manual|failure_description", { deviceId, error })}

+
+ ), + }); + } +} + +async function doManuallyVerifyDevice(client: MatrixClient, deviceId: string, fingerprint: string): Promise { + const userId = client.getUserId(); + if (!userId) { + throw new UserFriendlyError("encryption|verification|manual|no_userid", { + cause: undefined, + }); + } + + const crypto = client.getCrypto(); + if (!crypto) { + throw new UserFriendlyError("encryption|verification|manual|no_crypto"); + } + + const device = await getDeviceCryptoInfo(client, userId, deviceId); + if (!device) { + throw new UserFriendlyError("encryption|verification|manual|no_device", { + deviceId, + cause: undefined, + }); + } + const deviceTrust = await crypto.getDeviceVerificationStatus(userId, deviceId); + + if (deviceTrust?.isVerified()) { + if (device.getFingerprint() === fingerprint) { + throw new UserFriendlyError("encryption|verification|manual|already_verified", { + deviceId, + cause: undefined, + }); + } else { + throw new UserFriendlyError("encryption|verification|manual|already_verified_and_wrong_fingerprint", { + deviceId, + cause: undefined, + }); + } + } + + if (device.getFingerprint() !== fingerprint) { + const fprint = device.getFingerprint(); + throw new UserFriendlyError("encryption|verification|manual|wrong_fingerprint", { + fprint, + deviceId, + fingerprint, + cause: undefined, + }); + } + + // We've passed all the checks - do the device verification + await crypto.crossSignDevice(deviceId); +} diff --git a/src/components/views/dialogs/devtools/Crypto.tsx b/src/components/views/dialogs/devtools/Crypto.tsx index a05c415f00..e491c00a73 100644 --- a/src/components/views/dialogs/devtools/Crypto.tsx +++ b/src/components/views/dialogs/devtools/Crypto.tsx @@ -12,6 +12,8 @@ import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext import BaseTool from "./BaseTool"; import { useAsyncMemo } from "../../../../hooks/useAsyncMemo"; import { _t } from "../../../../languageHandler"; +import Modal from "../../../../Modal"; +import { ManualDeviceKeyVerificationDialog } from "../ManualDeviceKeyVerificationDialog"; interface KeyBackupProps { /** @@ -31,6 +33,16 @@ export function Crypto({ onBack }: KeyBackupProps): JSX.Element { <> + + + ) : ( {_t("devtools|crypto|crypto_not_available")} @@ -254,3 +266,39 @@ function getCrossSigningStatus(crossSigningReady: boolean, crossSigningPrivateKe return _t("devtools|crypto|cross_signing_not_ready"); } + +/** + * A component that displays information about the current session. + */ +function Session(): JSX.Element { + const matrixClient = useMatrixClientContext(); + const sessionData = useAsyncMemo(async () => { + const crypto = matrixClient.getCrypto()!; + const keys = await crypto.getOwnDeviceKeys(); + return { + fingerprint: keys.ed25519, + deviceId: matrixClient.deviceId, + }; + }, [matrixClient]); + + // Show a spinner while loading + if (sessionData === undefined) { + return ; + } + + return ( + + {_t("devtools|crypto|session")} + + + + + + + + + + +
{_t("devtools|crypto|device_id")}{sessionData.deviceId}
{_t("devtools|crypto|session_fingerprint")}{sessionData.fingerprint}
+ ); +} diff --git a/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx b/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx index 5b1fb3e142..10f4788580 100644 --- a/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx +++ b/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx @@ -14,9 +14,11 @@ import React, { type ChangeEvent, type FormEvent } from "react"; import { type SecretStorage } from "matrix-js-sdk/src/matrix"; import Field from "../../elements/Field"; +import { Flex } from "../../../utils/Flex"; import { _t } from "../../../../languageHandler"; import { EncryptionCard } from "../../settings/encryption/EncryptionCard"; import { EncryptionCardButtons } from "../../settings/encryption/EncryptionCardButtons"; +import BaseDialog from "../BaseDialog"; // Don't shout at the user that their key is invalid every time they type a key: wait a short time const VALIDATION_THROTTLE_MS = 200; @@ -83,6 +85,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent{validationText}; + // The entered key is not (yet) correct. Tell them so. + validationText = _t("encryption|access_secret_storage_dialog|key_validation_text|wrong_security_key"); + classes = classNames({ + "mx_AccessSecretStorageDialog_recoveryKeyFeedback": true, + "mx_AccessSecretStorageDialog_recoveryKeyFeedback--invalid": true, + }); } + + return ( + + {validationText} + + ); } public render(): React.ReactNode { @@ -205,15 +208,19 @@ export default class AccessSecretStorageDialog extends React.PureComponent ); + // We wrap the content in `BaseDialog` mostly so that we get a `FocusLock` container; otherwise, if the + // SettingsDialog is open, then the `FocusLock` in *that* stops us getting the focus. return ( - - {content} - + + + {content} + + ); } } diff --git a/src/components/views/rooms/RoomListPanel/EmptyRoomList.tsx b/src/components/views/rooms/RoomListPanel/EmptyRoomList.tsx index 1f04e4c86e..0e3412f588 100644 --- a/src/components/views/rooms/RoomListPanel/EmptyRoomList.tsx +++ b/src/components/views/rooms/RoomListPanel/EmptyRoomList.tsx @@ -76,6 +76,14 @@ export function EmptyRoomList({ vm }: EmptyRoomListProps): JSX.Element | undefin filter={vm.activePrimaryFilter} /> ); + case FilterKey.LowPriorityFilter: + return ( + + ); default: return undefined; } diff --git a/src/components/views/rooms/RoomListPanel/RoomListItemMenuView.tsx b/src/components/views/rooms/RoomListPanel/RoomListItemMenuView.tsx index 07b7be4b43..fa7a85b54f 100644 --- a/src/components/views/rooms/RoomListPanel/RoomListItemMenuView.tsx +++ b/src/components/views/rooms/RoomListPanel/RoomListItemMenuView.tsx @@ -109,12 +109,12 @@ function MoreOptionsMenu({ vm, setMenuOpen }: MoreOptionsMenuProps): JSX.Element onSelect={vm.toggleFavorite} onClick={(evt) => evt.stopPropagation()} /> - evt.stopPropagation()} - hideChevron={true} /> {vm.canInvite && ( { const MenuTrigger = ({ ref, ...props }: MenuTriggerProps): JSX.Element => ( - + ); @@ -63,12 +63,6 @@ export function RoomListOptionsMenu({ vm }: Props): JSX.Element { checked={vm.activeSortOption === SortOption.AToZ} onSelect={onAtoZSelected} /> - - ); } diff --git a/src/components/views/rooms/RoomListPanel/RoomListPrimaryFilters.tsx b/src/components/views/rooms/RoomListPanel/RoomListPrimaryFilters.tsx index ebf972d361..892f2b56b7 100644 --- a/src/components/views/rooms/RoomListPanel/RoomListPrimaryFilters.tsx +++ b/src/components/views/rooms/RoomListPanel/RoomListPrimaryFilters.tsx @@ -5,8 +5,9 @@ * Please see LICENSE files in the repository root for full details. */ -import React, { type JSX } from "react"; -import { ChatFilter } from "@vector-im/compound-web"; +import React, { type JSX, useEffect, useId, useRef, useState, type RefObject } from "react"; +import { ChatFilter, IconButton } from "@vector-im/compound-web"; +import ChevronDownIcon from "@vector-im/compound-design-tokens/assets/web/icons/chevron-down"; import type { RoomListViewState } from "../../../viewmodels/roomlist/RoomListViewModel"; import { Flex } from "../../../utils/Flex"; @@ -23,23 +24,146 @@ interface RoomListPrimaryFiltersProps { * The primary filters for the room list */ export function RoomListPrimaryFilters({ vm }: RoomListPrimaryFiltersProps): JSX.Element { + const id = useId(); + const [isExpanded, setIsExpanded] = useState(false); + + const { ref, isWrapping: displayChevron, wrappingIndex } = useCollapseFilters(isExpanded); + const filters = useVisibleFilters(vm.primaryFilters, wrappingIndex); + return ( - {vm.primaryFilters.map((filter) => ( -
  • - - {filter.name} - -
  • - ))} + {displayChevron && ( + setIsExpanded((_expanded) => !_expanded)} + > + + + )} + + {filters.map((filter, i) => ( +
  • + filter.toggle()}> + {filter.name} + +
  • + ))} +
    ); } + +/** + * A hook to manage the wrapping of filters in the room list. + * It observes the filter list and hides filters that are wrapping when the list is not expanded. + * @param isExpanded + * @returns an object containing: + * - `ref`: a ref to put on the filter list element + * - `isWrapping`: a boolean indicating if the filters are wrapping + * - `wrappingIndex`: the index of the first filter that is wrapping + */ +function useCollapseFilters( + isExpanded: boolean, +): { ref: RefObject; isWrapping: boolean; wrappingIndex: number } { + const ref = useRef(null); + const [isWrapping, setIsWrapping] = useState(false); + const [wrappingIndex, setWrappingIndex] = useState(-1); + + useEffect(() => { + if (!ref.current) return; + + const hideFilters = (list: Element): void => { + let isWrapping = false; + Array.from(list.children).forEach((node, i): void => { + const child = node as HTMLElement; + const wrappingClass = "mx_RoomListPrimaryFilters_wrapping"; + child.setAttribute("aria-hidden", "false"); + child.classList.remove(wrappingClass); + + // If the filter list is expanded, all filters are visible + if (isExpanded) return; + + // If the previous element is on the left element of the current one, it means that the filter is wrapping + const previousSibling = child.previousElementSibling as HTMLElement | null; + if (previousSibling && child.offsetLeft < previousSibling.offsetLeft) { + if (!isWrapping) setWrappingIndex(i); + isWrapping = true; + } + + // If the filter is wrapping, we hide it + child.classList.toggle(wrappingClass, isWrapping); + child.setAttribute("aria-hidden", isWrapping.toString()); + }); + + if (!isWrapping) setWrappingIndex(-1); + setIsWrapping(isExpanded || isWrapping); + }; + + hideFilters(ref.current); + const observer = new ResizeObserver((entries) => entries.forEach((entry) => hideFilters(entry.target))); + + observer.observe(ref.current); + return () => { + observer.disconnect(); + }; + }, [isExpanded]); + + return { ref, isWrapping, wrappingIndex }; +} + +/** + * A hook to sort the filters by active state. + * The list is sorted if the current filter index is greater than or equal to the wrapping index. + * If the wrapping index is -1, the filters are not sorted. + * + * @param filters - the list of filters to sort. + * @param wrappingIndex - the index of the first filter that is wrapping. + */ +export function useVisibleFilters( + filters: RoomListViewState["primaryFilters"], + wrappingIndex: number, +): RoomListViewState["primaryFilters"] { + // By default, the filters are not sorted + const [sortedFilters, setSortedFilters] = useState(filters); + + useEffect(() => { + const isActiveFilterWrapping = filters.findIndex((f) => f.active) >= wrappingIndex; + // If the active filter is not wrapping, we don't need to sort the filters + if (!isActiveFilterWrapping || wrappingIndex === -1) { + setSortedFilters(filters); + return; + } + + // Sort the filters with the current filter at first position + setSortedFilters( + filters.slice().sort((filterA, filterB) => { + // If the filter is active, it should be at the top of the list + if (filterA.active && !filterB.active) return -1; + if (!filterA.active && filterB.active) return 1; + // If both filters are active or not, keep their original order + return 0; + }), + ); + }, [filters, wrappingIndex]); + + return sortedFilters; +} diff --git a/src/components/views/settings/tabs/user/InviteRulesAccountSettings.tsx b/src/components/views/settings/tabs/user/InviteRulesAccountSettings.tsx new file mode 100644 index 0000000000..5ceeffa4ef --- /dev/null +++ b/src/components/views/settings/tabs/user/InviteRulesAccountSettings.tsx @@ -0,0 +1,47 @@ +/* +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 FC, useCallback, useState } from "react"; +import { Root } from "@vector-im/compound-web"; +import { logger } from "matrix-js-sdk/src/logger"; + +import { _t } from "../../../../../languageHandler"; +import { useSettingValue } from "../../../../../hooks/useSettings"; +import SettingsStore from "../../../../../settings/SettingsStore"; +import { SettingLevel } from "../../../../../settings/SettingLevel"; +import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch"; + +export const InviteRulesAccountSetting: FC = () => { + const rules = useSettingValue("inviteRules"); + const settingsDisabled = SettingsStore.disabledMessage("inviteRules"); + const [busy, setBusy] = useState(false); + + const onChange = useCallback(async (checked: boolean) => { + try { + setBusy(true); + await SettingsStore.setValue("inviteRules", null, SettingLevel.ACCOUNT, { + allBlocked: !checked, + }); + } catch (ex) { + logger.error(`Unable to set invite rules`, ex); + } finally { + setBusy(false); + } + }, []); + return ( + + + + ); +}; diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx index fa72cfe28e..ecf2766b20 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx @@ -33,6 +33,7 @@ import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch"; import * as TimezoneHandler from "../../../../../TimezoneHandler"; import { type BooleanSettingKey } from "../../../../../settings/Settings.tsx"; import { MediaPreviewAccountSettings } from "./MediaPreviewAccountSettings.tsx"; +import { InviteRulesAccountSetting } from "./InviteRulesAccountSettings.tsx"; interface IProps { closeSettingsFn(success: boolean): void; @@ -242,12 +243,12 @@ export default class PreferencesUserSettingsTab extends React.Component { return
    {tz}
    ; @@ -263,11 +264,13 @@ export default class PreferencesUserSettingsTab extends React.Component - {roomListSettings.length > 0 && ( - - {this.renderGroup(roomListSettings)} - - )} + + {this.renderGroup(PreferencesUserSettingsTab.ROOM_LIST_SETTINGS)} + {/* The settings is on device level where the other room list settings are on account level */} + {newRoomListEnabled && ( + + )} + {this.renderGroup(PreferencesUserSettingsTab.SPACES_SETTINGS, SettingLevel.ACCOUNT)} @@ -337,6 +340,7 @@ export default class PreferencesUserSettingsTab extends React.Component + @@ -355,6 +359,12 @@ export default class PreferencesUserSettingsTab extends React.Component + diff --git a/src/hooks/usePinnedEvents.ts b/src/hooks/usePinnedEvents.ts index f563211129..98f544097b 100644 --- a/src/hooks/usePinnedEvents.ts +++ b/src/hooks/usePinnedEvents.ts @@ -38,6 +38,10 @@ function getPinnedEventIds(room?: Room): string[] { .getState(EventTimeline.FORWARDS) ?.getStateEvents(EventType.RoomPinnedEvents, "") ?.getContent()?.pinned ?? []; + if (!Array.isArray(eventIds)) { + logger.warn("Encountered invalid pinned events state in room", room?.roomId, eventIds); + return []; + } // Limit the number of pinned events to 100 return eventIds.slice(0, 100); } diff --git a/src/i18n/strings/cs.json b/src/i18n/strings/cs.json index f454a2f1e4..33868e7682 100644 --- a/src/i18n/strings/cs.json +++ b/src/i18n/strings/cs.json @@ -922,8 +922,6 @@ "security_key_title": "Klíč pro obnovení" }, "bootstrap_title": "Příprava klíčů", - "cancel_entering_passphrase_description": "Chcete určitě zrušit zadávání přístupové fráze?", - "cancel_entering_passphrase_title": "Zrušit zadávání přístupové fráze?", "confirm_encryption_setup_body": "Kliknutím na tlačítko níže potvrďte nastavení šifrování.", "confirm_encryption_setup_title": "Potvrďte nastavení šifrování", "cross_signing_room_normal": "Místnost je koncově šifrovaná", @@ -2100,7 +2098,6 @@ "room_list": { "add_room_label": "Přidat místnost", "add_space_label": "Přidat prostor", - "appearance": "Vzhled", "breadcrumbs_empty": "Žádné nedávno navštívené místnosti", "breadcrumbs_label": "Nedávno navštívené místnosti", "empty": { @@ -2158,7 +2155,6 @@ }, "room_options": "Možnosti místnosti", "show_less": "Zobrazit méně", - "show_message_previews": "Zobrazit náhledy zpráv", "show_n_more": { "other": "Zobrazit %(count)s dalších", "one": "Zobrazit %(count)s další" @@ -3135,6 +3131,7 @@ "upgraderoom": "Aktualizuje místnost na novou verzi", "upgraderoom_permission_error": "Na provedení tohoto příkazu nemáte dostatečná oprávnění.", "usage": "Použití", + "verify": "Ověří uživatele, relaci a veřejné klíče", "view": "Zobrazí místnost s danou adresou", "whois": "Zobrazuje informace o uživateli" }, diff --git a/src/i18n/strings/cy.json b/src/i18n/strings/cy.json index d73c13a384..2252975325 100644 --- a/src/i18n/strings/cy.json +++ b/src/i18n/strings/cy.json @@ -831,6 +831,14 @@ "room_notifications_total": "Cyfanswm: ", "room_notifications_type": "Math: ", "room_status": "Statws ystafell", + "room_unread_status_count": { + "zero": "Statws heb eu darllen yn yr ystafell:%(status)s , cyfrif:%(count)s", + "one": "Statws heb ei ddarllen yn yr ystafell:%(status)s , cyfrif:%(count)s", + "two": "Statws heb eu darllen yn yr ystafell:%(status)s , cyfrif:%(count)s", + "few": "Statws heb eu darllen yn yr ystafell:%(status)s , cyfrif:%(count)s", + "many": "Statws heb eu darllen yn yr ystafell:%(status)s , cyfrif:%(count)s", + "other": "Statws heb eu darllen yn yr ystafell:%(status)s , cyfrif:%(count)s" + }, "save_setting_values": "Cadw gwerthoedd gosod", "see_history": "Gweld hanes", "send_custom_account_data_event": "Anfon digwyddiad data cyfrif personol", @@ -849,6 +857,14 @@ }, "settings_explorer": "Archwiliwr gosodiadau", "show_hidden_events": "Dangos digwyddiadau cudd yn y llinell amser", + "spaces": { + "zero": "<%(count) s gofodau>", + "one": "", + "two": "<%(count) s ofod>", + "few": "<%(count) s gofod>", + "many": "<%(count) s gofod>", + "other": "<%(count) s gofod>" + }, "state_key": "Allwedd Cyflwr", "thread_root_id": "ID Gwraidd Edefyn: %(threadRootId)s", "threads_timeline": "Llinell amser edafedd", @@ -902,8 +918,6 @@ "security_key_title": "Allwedd Adfer" }, "bootstrap_title": "Gosod allweddi", - "cancel_entering_passphrase_description": "Ydych chi'n siŵr eich bod am ddiddymu'r cyfrinymadrodd?", - "cancel_entering_passphrase_title": "Diddymu cyflwyno cyfrinymadrodd?", "confirm_encryption_setup_body": "Clicio'r botwm isod i gadarnhau gosod amgryptio.", "confirm_encryption_setup_title": "Cadarnhau gosodiad amgryptio", "cross_signing_room_normal": "Mae'r ystafell hon wedi'i hamgryptio o ben-i-ben", @@ -1155,7 +1169,39 @@ "error_fetching_file": "Gwall wrth nôl ffeil", "export_info": "Dyma ddechrau allforio o. Wedi'i allforio gan yn %(exportDate)s.", "export_successful": "Allforio yn llwyddiannus!", + "exported_n_events_in_time": { + "zero": "Wedi nôl %(count)s digwyddiadau o fewn %(seconds)s e", + "one": "Wedi allforio %(count)s digwyddiad o fewn %(seconds)s e", + "two": "Wedi nôl %(count)s ddigwyddiad o fewn %(seconds)s e", + "few": "Wedi nôl %(count)s digwyddiad o fewn %(seconds)s e", + "many": "Wedi nôl %(count)s digwyddiad o fewn %(seconds)s e", + "other": "Wedi nôl %(count)s digwyddiad o fewn %(seconds)s e" + }, "exporting_your_data": "Allforio eich data", + "fetched_n_events": { + "zero": "Wedi nôl %(count)s digwyddiadau hyd yn hyn", + "one": "Wedi nôl %(count)s digwyddiad hyd yn hyn", + "two": "Wedi nôl %(count)s ddigwyddiad hyd yn hyn", + "few": "Wedi nôl %(count)s digwyddiad hyd yn hyn", + "many": "Wedi nôl %(count)s digwyddiad hyd yn hyn", + "other": "Wedi nôl %(count)s digwyddiad hyd yn hyn" + }, + "fetched_n_events_in_time": { + "zero": "Wedi nôl %(count)s digwyddiadau o fewn %(seconds)s e", + "one": "Wedi nôl %(count)s digwyddiad o fewn %(seconds)s e", + "two": "Wedi nôl %(count)s ddigwyddiad o fewn %(seconds)s e", + "few": "Wedi nôl %(count)s digwyddiad o fewn %(seconds)s e", + "many": "Wedi nôl %(count)s digwyddiad o fewn %(seconds)s e", + "other": "Wedi nôl %(count)s digwyddiad o fewn %(seconds)s e" + }, + "fetched_n_events_with_total": { + "zero": "Wedi nôl %(count)s digwyddiadau allan o %(total)s", + "one": "Wedi nôl %(count)s digwyddiad allan o %(total)s", + "two": "Wedi nôl %(count)s ddigwyddiad allan o %(total)s", + "few": "Wedi nôl %(count)s digwyddiad allan o %(total)s", + "many": "Wedi nôl %(count)s digwyddiad allan o %(total)s", + "other": "Wedi nôl %(count)s digwyddiad allan o %(total)s" + }, "fetching_events": "Wrthi'n nôl digwyddiadau…", "file_attached": "Ffeil wedi'i Atodi", "format": "Fformat", @@ -1992,7 +2038,6 @@ "room_list": { "add_room_label": "Ychwanegu ystafell", "add_space_label": "Ychwanegu gofod", - "appearance": "Gwedd", "breadcrumbs_empty": "Dim ystafelloedd yr ymwelwyd â nhw yn ddiweddar", "breadcrumbs_label": "Ymwelwyd ag ystafelloedd yn ddiweddar", "empty": { @@ -2042,7 +2087,6 @@ }, "room_options": "Dewisiadau Ystafelloedd", "show_less": "Dangos llai", - "show_message_previews": "Dangos rhagolygon negeseuon", "show_previews": "Dangos rhagolwg o negeseuon", "sort": "Trefnu", "sort_by": "Trefnu yn ôl", @@ -2966,6 +3010,7 @@ "upgraderoom": "Yn uwchraddio ystafell i fersiwn newydd", "upgraderoom_permission_error": "Nid oes gennych y caniatâd gofynnol i ddefnyddio'r gorchymyn hwn.", "usage": "Defnydd", + "verify": "Yn dilysu defnyddiwr, sesiwn, a pubkey tuple", "view": "Ystafell golygfeydd gyda chyfeiriad a roddwyd", "whois": "Yn arddangos gwybodaeth am ddefnyddiwr" }, @@ -3432,10 +3477,106 @@ "send_state_sending": "Wrthi'n anfon eich neges…", "send_state_sent": "Anfonwyd eich neges", "summary": { - "format": "%(matereList)s %(transitionList)s" + "format": "%(matereList)s %(transitionList)s", + "invite_withdrawn_multiple": { + "zero": "Cafodd %(severalUsers)s eu gwahoddiadau eu dileu", + "one": "Cafodd %(severalUsers)s eu gwahoddiadau eu dileu", + "two": "Cafodd %(severalUsers)s eu gwahoddiadau eu dileu %(count)s gwaith", + "few": "Cafodd %(severalUsers)s eu gwahoddiadau eu dileu %(count)s gwaith", + "many": "Cafodd %(severalUsers)s eu gwahoddiadau eu dileu %(count)s gwaith", + "other": "Cafodd %(severalUsers)s eu gwahoddiadau eu dileu %(count)s gwaith" + }, + "joined": { + "zero": "Ymunodd %(oneUser)s %(count)s gwaith", + "one": "Ymunodd %(oneUser)s", + "two": "Ymunodd %(oneUser)s %(count)s gwaith", + "few": "Ymunodd %(oneUser)s %(count)s gwaith", + "many": "Ymunodd %(oneUser)s %(count)s gwaith", + "other": "Ymunodd %(oneUser)s %(count)s gwaith" + }, + "joined_and_left": { + "zero": "Ymunodd a gadawodd %(oneUser)s %(count)s gwaith", + "one": "Ymunodd a gadawodd %(oneUser)s %(count)s gwaith", + "two": "Ymunodd a gadawodd %(oneUser)s %(count)s gwaith", + "few": "Ymunodd a gadawodd %(oneUser)s %(count)s gwaith", + "many": "Ymunodd a gadawodd %(oneUser)s %(count)s gwaith", + "other": "Ymunodd a gadawodd %(oneUser)s %(count)s gwaith" + }, + "joined_and_left_multiple": { + "zero": "Ymunodd a gadawodd %(severalUsers)s", + "one": "Ymunodd a gadawodd %(severalUsers)s", + "two": "Ymunodd a gadawodd %(severalUsers)s %(count)s gwaith", + "few": "Ymunodd a gadawodd %(severalUsers)s %(count)s gwaith", + "many": "Ymunodd a gadawodd %(severalUsers)s %(count)s gwaith", + "other": "Ymunodd a gadawodd %(severalUsers)s %(count)s gwaith" + }, + "joined_multiple": { + "zero": "Ymunodd %(severalUsers)s", + "one": "Ymunodd %(severalUsers)s %(count)s gwaith", + "two": "Ymunodd %(severalUsers)s %(count)s gwaith", + "few": "Ymunodd %(severalUsers)s %(count)s gwaith", + "many": "Ymunodd %(severalUsers)s %(count)s gwaith", + "other": "Ymunodd %(severalUsers)s %(count)s gwaith" + }, + "left": { + "zero": "Gadawodd %(oneUser)s", + "one": "Gadawodd %(oneUser)s", + "two": "Gadawodd %(oneUser)s %(count)s gwaith", + "few": "Gadawodd %(oneUser)s %(count)s gwaith", + "many": "Gadawodd %(oneUser)s %(count)s gwaith", + "other": "Gadawodd %(oneUser)s %(count)s gwaith" + }, + "left_multiple": { + "zero": "Gadawodd %(severalUsers)s", + "one": "Gadawodd %(severalUsers)s", + "two": "Gadawodd %(severalUsers)s %(count)s gwaith", + "few": "Gadawodd %(severalUsers)s %(count)s gwaith", + "many": "Gadawodd %(severalUsers)s %(count)s gwaith", + "other": "Gadawodd %(severalUsers)s %(count)s gwaith" + }, + "rejected_invite": { + "zero": "Gwrthododd %(severalUsers)s eu gwahoddiadau", + "one": "Gwrthododd %(oneUser)s ei wahoddiad", + "two": "Gwrthododd %(oneUser)s eu gwahoddiadau %(count)s gwaith", + "few": "Gwrthododd %(oneUser)s eu gwahoddiadau %(count)s gwaith", + "many": "Gwrthododd %(oneUser)s eu gwahoddiadau %(count)s gwaith", + "other": "Gwrthododd %(oneUser)s eu gwahoddiadau %(count)s gwaith" + }, + "rejected_invite_multiple": { + "zero": "Gwrthododd %(severalUsers)s eu gwahoddiadau", + "one": "Gwrthododd %(severalUsers)s eu gwahoddiadau", + "two": "Gwrthododd %(severalUsers)s eu gwahoddiadau %(count)s gwaith", + "few": "Gwrthododd %(severalUsers)s eu gwahoddiadau %(count)s gwaith", + "many": "Gwrthododd %(severalUsers)s eu gwahoddiadau %(count)s gwaith", + "other": "Gwrthododd %(severalUsers)s eu gwahoddiadau %(count)s gwaith" + }, + "rejoined": { + "zero": "Ymunodd a gadawodd %(oneUser)s", + "one": "Ymunodd a gadawodd %(oneUser)s", + "two": "Ymunodd a gadawodd %(oneUser)s %(count)s gwaith", + "few": "Ymunodd a gadawodd %(oneUser)s %(count)s gwaith", + "many": "Ymunodd a gadawodd %(oneUser)s %(count)s gwaith", + "other": "Ymunodd a gadawodd %(oneUser)s %(count)s gwaith" + }, + "rejoined_multiple": { + "zero": "Ymunodd a gadawodd %(severalUsers)s", + "one": "Ymunodd a gadawodd %(severalUsers)s", + "two": "Ymunodd a gadawodd %(severalUsers)s %(count)s gwaith", + "few": "Ymunodd a gadawodd %(severalUsers)s %(count)s gwaith", + "many": "Ymunodd a gadawodd %(severalUsers)s %(count)s gwaith", + "other": "Ymunodd a gadawodd %(severalUsers)s %(count)s gwaith" + } }, "thread_info_basic": "O edefyn", "typing_indicator": { + "more_users": { + "zero": "Mae %(names)s ac mae %(count)s eraill yn teipio...", + "one": "Mae %(names)s ac mae un arall yn teipio...", + "two": "Mae %(names)s ac mae %(count)s eraill yn teipio...", + "few": "Mae %(names)s ac mae %(count)s eraill yn teipio...", + "many": "Mae %(names)s ac mae %(count)s eraill yn teipio...", + "other": "Mae %(names)s ac mae %(count)s eraill yn teipio..." + }, "one_user": "Mae %(displayName)s yn teipio…", "two_users": "Mae %(materes)s a %(lastPerson)s yn teipio…" }, diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index e1de098662..602b3f22f5 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -911,15 +911,15 @@ "empty_room_was_name": "Leerer Raum (war %(oldName)s)", "encryption": { "access_secret_storage_dialog": { + "alternatives": "Wenn Sie einen Sicherheitsschlüssel oder eine Sicherheitsphrase haben, funktioniert das auch.", "key_validation_text": { - "wrong_security_key": "Falscher Wiederherstellungsschlüssel" + "wrong_security_key": "Der eingegebene Wiederherstellungsschlüssel ist nicht korrekt." }, + "privacy_warning": "Stellen Sie sicher, dass niemand diesen Bildschirm sehen kann!", "restoring": "Schlüssel aus der Sicherung wiederherstellen", "security_key_title": "Wiederherstellungsschlüssel" }, "bootstrap_title": "Schlüssel werden eingerichtet", - "cancel_entering_passphrase_description": "Bist du sicher, dass du die Eingabe der Passphrase abbrechen möchtest?", - "cancel_entering_passphrase_title": "Eingabe der Passphrase abbrechen?", "confirm_encryption_setup_body": "Klick die Schaltfläche unten um die Einstellungen der Verschlüsselung zu bestätigen.", "confirm_encryption_setup_title": "Bestätige die Einrichtung der Verschlüsselung", "cross_signing_room_normal": "Dieser Raum ist Ende-zu-Ende verschlüsselt", @@ -2091,7 +2091,6 @@ "room_list": { "add_room_label": "Raum hinzufügen", "add_space_label": "Space hinzufügen", - "appearance": "Erscheinungsbild", "breadcrumbs_empty": "Keine kürzlich besuchten Räume", "breadcrumbs_label": "Kürzlich besuchte Räume", "empty": { @@ -2149,7 +2148,6 @@ }, "room_options": "Chatroomoptionen", "show_less": "Weniger anzeigen", - "show_message_previews": "Nachrichtenvorschau anzeigen", "show_n_more": { "other": "%(count)s weitere anzeigen", "one": "%(count)s weitere anzeigen" @@ -3125,6 +3123,7 @@ "upgraderoom": "Aktualisiert den Raum auf eine neue Version", "upgraderoom_permission_error": "Du hast nicht die erforderlichen Berechtigungen, diesen Befehl zu verwenden.", "usage": "Verwendung", + "verify": "Verifiziert Benutzer, Sitzung und öffentlichen Schlüsselpaare", "view": "Raum mit angegebener Adresse betrachten", "whois": "Zeigt Informationen über Benutzer" }, diff --git a/src/i18n/strings/el.json b/src/i18n/strings/el.json index 662d56b6d5..3a25563601 100644 --- a/src/i18n/strings/el.json +++ b/src/i18n/strings/el.json @@ -720,8 +720,6 @@ "security_key_title": "Κλειδί Ασφαλείας" }, "bootstrap_title": "Ρύθμιση κλειδιών", - "cancel_entering_passphrase_description": "Είστε σίγουρος/η ότι θέλετε να ακυρώσετε την εισαγωγή κωδικού;", - "cancel_entering_passphrase_title": "Ακύρωση εισαγωγής κωδικού;", "confirm_encryption_setup_body": "Κάντε κλικ στο κουμπί παρακάτω για να επιβεβαιώσετε τη ρύθμιση της κρυπτογράφησης.", "confirm_encryption_setup_title": "Επιβεβαιώστε τη ρύθμιση κρυπτογράφησης", "cross_signing_room_normal": "Αυτό το δωμάτιο έχει κρυπτογράφηση από άκρο σε άκρο", @@ -2335,6 +2333,7 @@ "upgraderoom": "Αναβαθμίζει το δωμάτιο σε μια καινούργια έκδοση", "upgraderoom_permission_error": "Δεν διαθέτετε τις απαιτούμενες άδειες για να χρησιμοποιήσετε αυτήν την εντολή.", "usage": "Χρήση", + "verify": "Επιβεβαιώνει έναν χρήστη, συνεδρία, και pubkey tuple", "whois": "Εμφανίζει πληροφορίες για έναν χρήστη" }, "space": { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d848f839a5..5763e0a781 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -786,6 +786,7 @@ "cross_signing_status": "Cross-signing status:", "cross_signing_untrusted": "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.", "crypto_not_available": "Cryptographic module is not available", + "device_id": "Device ID", "key_backup_active_version": "Active backup version:", "key_backup_active_version_none": "None", "key_backup_inactive_warning": "Your keys are not being backed up from this session.", @@ -798,6 +799,8 @@ "secret_storage_ready": "ready", "secret_storage_status": "Secret storage:", "self_signing_private_key_cached_status": "Self signing private key:", + "session": "Session", + "session_fingerprint": "Fingerprint (session key)", "title": "End-to-end encryption", "user_signing_private_key_cached_status": "User signing private key:" }, @@ -823,6 +826,7 @@ "low_bandwidth_mode": "Low bandwidth mode", "low_bandwidth_mode_description": "Requires compatible homeserver.", "main_timeline": "Main timeline", + "manual_device_verification": "Manual device verification", "no_receipt_found": "No receipt found", "notification_state": "Notification state is %(notificationState)s", "notifications_debug": "Notifications debug", @@ -920,8 +924,6 @@ "security_key_title": "Recovery key" }, "bootstrap_title": "Setting up keys", - "cancel_entering_passphrase_description": "Are you sure you want to cancel entering passphrase?", - "cancel_entering_passphrase_title": "Cancel entering passphrase?", "confirm_encryption_setup_body": "Click the button below to confirm setting up encryption.", "confirm_encryption_setup_title": "Confirm encryption setup", "cross_signing_room_normal": "This room is end-to-end encrypted", @@ -1009,6 +1011,21 @@ "incoming_sas_dialog_waiting": "Waiting for partner to confirm…", "incoming_sas_user_dialog_text_1": "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.", "incoming_sas_user_dialog_text_2": "Verifying this user will mark their session as trusted, and also mark your session as trusted to them.", + "manual": { + "already_verified": "This device is already verified", + "already_verified_and_wrong_fingerprint": "The supplied fingerprint does not match, but the device is already verified!", + "device_id": "Device ID", + "failure_description": "Failed to verify '%(deviceId)s': %(error)s", + "failure_title": "Verification failed", + "fingerprint": "Fingerprint (session key)", + "no_crypto": "Unable to verify device - crypto is not enabled", + "no_device": "Unable to verify device - device '%(deviceId)s' was not found", + "no_userid": "Unable to verify device - cannot find our User ID", + "success_description": "The device (%(deviceId)s) is now cross-signed", + "success_title": "Verification successful", + "text": "Supply the ID and fingerprint of one of your own devices to verify it. NOTE this allows the other device to send and receive messages as you. IF SOMEONE TOLD YOU TO PASTE SOMETHING HERE, IT IS LIKELY YOU ARE BEING SCAMMED!", + "wrong_fingerprint": "Unable to verify device '%(deviceId)s' - the supplied fingerprint '%(fingerprint)s' does not match the device fingerprint, '%(fprint)s'" + }, "no_key_or_device": "It looks like you don't have a Recovery Key or any other devices you can verify against. This device will not be able to access old encrypted messages. In order to verify your identity on this device, you'll need to reset your verification keys.", "no_support_qr_emoji": "The device you are trying to verify doesn't support scanning a QR code or emoji verification, which is what %(brand)s supports. Try with a different client.", "other_party_cancelled": "The other party cancelled the verification.", @@ -1957,6 +1974,7 @@ }, "face_pile_tooltip_shortcut": "Including %(commaSeparatedMembers)s", "face_pile_tooltip_shortcut_joined": "Including you, %(commaSeparatedMembers)s", + "failed_determine_user": "Cannot determine which user to ignore since the member event has changed.", "failed_reject_invite": "Failed to reject invite", "forget_room": "Forget this room", "forget_space": "Forget this space", @@ -2048,6 +2066,7 @@ "read_topic": "Click to read topic", "rejecting": "Rejecting invite…", "rejoin_button": "Re-join", + "room_is_low_priority": "This is a low priority room", "search": { "all_rooms_button": "Search all rooms", "placeholder": "Search messages…", @@ -2094,9 +2113,9 @@ "room_list": { "add_room_label": "Add room", "add_space_label": "Add space", - "appearance": "Appearance", "breadcrumbs_empty": "No recently visited rooms", "breadcrumbs_label": "Recently visited rooms", + "collapse_filters": "Collapse filter list", "empty": { "no_chats": "No chats yet", "no_chats_description": "Get started by messaging someone or by creating a room", @@ -2104,6 +2123,7 @@ "no_favourites": "You don't have favourite chat yet", "no_favourites_description": "You can add a chat to your favourites in the chat settings", "no_invites": "You don't have any unread invites", + "no_lowpriority": "You don't have any low priority rooms", "no_mentions": "You don't have any unread mentions", "no_people": "You don’t have direct chats with anyone yet", "no_people_description": "You can deselect filters in order to see your other chats", @@ -2113,12 +2133,14 @@ "show_activity": "See all activity", "show_chats": "Show all chats" }, + "expand_filters": "Expand filter list", "failed_add_tag": "Failed to add tag %(tagName)s to room", "failed_remove_tag": "Failed to remove tag %(tagName)s from room", "failed_set_dm_tag": "Failed to set direct message tag", "filters": { "favourite": "Favourites", "invites": "Invites", + "low_priority": "Low priority", "mentions": "Mentions", "people": "People", "rooms": "Rooms", @@ -2152,7 +2174,6 @@ }, "room_options": "Room Options", "show_less": "Show less", - "show_message_previews": "Show message previews", "show_n_more": { "one": "Show %(count)s more", "other": "Show %(count)s more" @@ -2687,6 +2708,9 @@ "inline_url_previews_room": "Enable URL previews by default for participants in this room", "inline_url_previews_room_account": "Enable URL previews for this room (only affects you)", "insert_trailing_colon_mentions": "Insert a trailing colon after user mentions at the start of a message", + "invite_controls": { + "default_label": "Allow users to invite you to rooms" + }, "jump_to_bottom_on_send": "Jump to the bottom of the timeline when you send a message", "key_backup": { "backup_in_progress": "Your keys are being backed up (the first backup could take a few minutes).", @@ -2753,6 +2777,7 @@ "show_in_private": "In private rooms", "show_media": "Always show" }, + "not_supported": "Your server does not implement this feature.", "notifications": { "default_setting_description": "This setting will be applied by default to all your rooms.", "default_setting_section": "I want to be notified for (Default Setting)", @@ -2810,6 +2835,7 @@ "voip": "Audio and Video calls" }, "preferences": { + "Electron.enableContentProtection": "Prevent the window contents from being captured by other apps", "Electron.enableHardwareAcceleration": "Enable hardware acceleration (restart %(appName)s to take effect)", "always_show_menu_bar": "Always show the window menu bar", "autocomplete_delay": "Autocomplete delay (ms)", @@ -2982,6 +3008,7 @@ "show_chat_effects": "Show chat effects (animations when receiving e.g. confetti)", "show_displayname_changes": "Show display name changes", "show_join_leave": "Show join/leave messages (invites/removes/bans unaffected)", + "show_message_previews": "Show message previews", "show_nsfw_content": "Show NSFW content", "show_read_receipts": "Show read receipts sent by other users", "show_redaction_placeholder": "Show a placeholder for removed messages", @@ -3088,6 +3115,8 @@ "jumptodate": "Jump to the given date in the timeline", "jumptodate_invalid_input": "We were unable to understand the given date (%(inputDate)s). Try using the format YYYY-MM-DD.", "lenny": "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message", + "manual_device_verification_confirm_description": "This will allow another device to send and receive messages as you. IF SOMEONE TOLD YOU TO PASTE SOMETHING HERE, IT IS LIKELY YOU ARE BEING SCAMMED! Are you sure you want to verify this other device?", + "manual_device_verification_confirm_title": "Caution: manual device verification", "me": "Displays action", "msg": "Sends a message to the given user", "myavatar": "Changes your profile picture in all rooms", @@ -3128,6 +3157,7 @@ "upgraderoom": "Upgrades a room to a new version", "upgraderoom_permission_error": "You do not have the required permissions to use this command.", "usage": "Usage", + "verify": "Manually verify one of your own devices", "view": "Views room with given address", "whois": "Displays information about a user" }, diff --git a/src/i18n/strings/eo.json b/src/i18n/strings/eo.json index a6f28167ea..5ac803f285 100644 --- a/src/i18n/strings/eo.json +++ b/src/i18n/strings/eo.json @@ -604,8 +604,6 @@ "security_key_title": "Sekureca ŝlosilo" }, "bootstrap_title": "Agordo de klavoj", - "cancel_entering_passphrase_description": "Ĉu vi certe volas nuligi enigon de pasfrazo?", - "cancel_entering_passphrase_title": "Ĉu nuligi enigon de pasfrazo?", "confirm_encryption_setup_body": "Klaku sube la butonon por konfirmi agordon de ĉifrado.", "confirm_encryption_setup_title": "Konfirmi agordon de ĉifrado", "cross_signing_room_normal": "Ĉi tiu ĉambro uzas tutvojan ĉifradon", @@ -1957,6 +1955,7 @@ "upgraderoom": "Gradaltigas ĉambron al nova versio", "upgraderoom_permission_error": "Vi ne havas sufiĉajn permesojn por uzi ĉi tiun komandon.", "usage": "Uzo", + "verify": "Kontrolas opon de uzanto, salutaĵo, kaj publika ŝlosilo", "whois": "Montras informojn pri uzanto" }, "space": { diff --git a/src/i18n/strings/es.json b/src/i18n/strings/es.json index 6abbdbe819..75a08251c5 100644 --- a/src/i18n/strings/es.json +++ b/src/i18n/strings/es.json @@ -748,8 +748,6 @@ "security_key_title": "Clave de seguridad" }, "bootstrap_title": "Configurando claves", - "cancel_entering_passphrase_description": "¿Estas seguro que quieres cancelar el ingresar tu contraseña de recuperación?", - "cancel_entering_passphrase_title": "¿Cancelar el ingresar tu contraseña de recuperación?", "confirm_encryption_setup_body": "Haz clic en el botón de abajo para confirmar la configuración del cifrado.", "confirm_encryption_setup_title": "Confirmar la configuración de cifrado", "cross_signing_room_normal": "Esta sala usa cifrado de extremo a extremo", @@ -2485,6 +2483,7 @@ "upgraderoom": "Actualiza una sala a una nueva versión", "upgraderoom_permission_error": "No tienes los permisos requeridos para usar este comando.", "usage": "Uso", + "verify": "Verifica a un usuario, sesión y tupla de clave pública", "whois": "Muestra información sobre un usuario" }, "space": { diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index 666ed60825..9a27318316 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -786,6 +786,7 @@ "cross_signing_status": "Risttunnustamise olek:", "cross_signing_untrusted": "Sinu kasutajakonto risttunnustamise identiteet on krüptitud andmehoidlas olemas, aga see sessioon teda veel ei usalda.", "crypto_not_available": "Krüptomoodul pole saadaval", + "device_id": "Seadme tunnus", "key_backup_active_version": "Varukoopia aktiivne versioon:", "key_backup_active_version_none": "Puudub", "key_backup_inactive_warning": "See sessioon ei varunda sinu krüptovõtmeid.", @@ -798,6 +799,8 @@ "secret_storage_ready": "on valmis", "secret_storage_status": "Krüptitud andmeruum:", "self_signing_private_key_cached_status": "Privaatvõti allkirjastamiseks sinu nimel:", + "session": "Sessioon", + "session_fingerprint": "Sõrmejälg (sessiooni võti)", "title": "Läbiv krüptimine", "user_signing_private_key_cached_status": "Kasutaja privaatvõti allkirjastamiseks:" }, @@ -823,6 +826,7 @@ "low_bandwidth_mode": "Vähese ribalaiusega režiim", "low_bandwidth_mode_description": "Eeldab, et koduserver toetab sellist funktsionaalsust.", "main_timeline": "Peamine ajajoon", + "manual_device_verification": "Seadme käsitsi verifitseerimine", "no_receipt_found": "Lugemisteatist ei leidu", "notification_state": "Teavituste olek: %(notificationState)s", "notifications_debug": "Teavituste silumine", @@ -920,8 +924,6 @@ "security_key_title": "Taastevõti" }, "bootstrap_title": "Võtame krüptovõtmed kasutusele", - "cancel_entering_passphrase_description": "Kas oled kindel et sa soovid katkestada paroolifraasi sisestamise?", - "cancel_entering_passphrase_title": "Kas katkestame paroolifraasi sisestamise?", "confirm_encryption_setup_body": "Kinnitamaks, et soovid krüptimist seadistada, klõpsi järgnevat nuppu.", "confirm_encryption_setup_title": "Krüptimise seadistuse kinnitamine", "cross_signing_room_normal": "See jututuba on läbivalt krüptitud", @@ -1009,6 +1011,21 @@ "incoming_sas_dialog_waiting": "Ootan teise osapoole kinnitust…", "incoming_sas_user_dialog_text_1": "Selle kasutaja usaldamiseks peaksid ta verifitseerima. Kui sa pruugid läbivalt krüptitud sõnumeid, siis kasutajate verifitseerimine tagab sulle täiendava meelerahu.", "incoming_sas_user_dialog_text_2": "Selle kasutaja verifitseerimisel märgitakse tema sessioon usaldusväärseks ning samuti märgitakse sinu sessioon tema jaoks usaldusväärseks.", + "manual": { + "already_verified": "See seade on juba verifitseeritud", + "already_verified_and_wrong_fingerprint": "Viidatud sõrmejälg ei klapi, aga see seade on juba verifitseeritud!", + "device_id": "Seadme tunnus", + "failure_description": "„%(deviceId)s“ seadme verifitseerimine ei õnnestunud: %(error)s", + "failure_title": "Verifitseerimine ei õnnestunud", + "fingerprint": "Sõrmejälg (sessiooni võti)", + "no_crypto": "Seadme verifitseerimine ei õnnestunud - krüptoteenused pole kasutusel", + "no_device": "Seadme verifitseerimine ei õnnestunud - seadet tunnusega „%(deviceId)s“ polnud võimalik leida", + "no_userid": "Seadme verifitseerimine ei õnnestunud - meie kasutajatunnust ei õnnestu leida", + "success_description": "See seade (%(deviceId)s) on nüüd risttunnustatud", + "success_title": "Verifitseerimine õnnestus", + "text": "Verifitseerimiseks sisesta ühe oma seadme tunnus ja sõrmejälg. Palun arvesta, et see võimaldab muul seadmel saata ja vastu võtta sõnumeid esinedes sinuna. KUI KEEGI PALUS SUL SIIA MIDAGI KOPEERIDA, SIIS ON SEE KAHTLANE JA ILMSELT PROOVITAKSE SIND PETTA!", + "wrong_fingerprint": "„%(deviceId)s“ seadme verifitseerimine ei õnnestunud - lisatud sõrmejälg „%(fingerprint)s“ ja seadme sõrmejälg „%(fprint)s“ pole samad" + }, "no_key_or_device": "Tundub, et sul ei ole ei taastevõtit ega muid seadmeid, mida saaksid verifitseerimiseks kasutada. Siin seadmes ei saa lugeda vanu krüptitud sõnumeid. Enda tuvastamiseks selles seadmes pead oma vanad verifitseerimisvõtmed kustutama.", "no_support_qr_emoji": "See seade, mida sa tahad verifitseerida ei toeta QR-koodi ega emoji-põhist verifitseerimist, aga just neid %(brand)s oskab kasutada. Proovi mõne muu Matrix'i kliendiga.", "other_party_cancelled": "Teine osapool tühistas verifitseerimise.", @@ -1957,6 +1974,7 @@ }, "face_pile_tooltip_shortcut": "Sealhulgas %(commaSeparatedMembers)s", "face_pile_tooltip_shortcut_joined": "Seahulgas Sina, %(commaSeparatedMembers)s", + "failed_determine_user": "Kuna liikmelisuse sündmus on muutunud, siis ei saa tuvastada, millist kasutajat peaks eirama.", "failed_reject_invite": "Kutse tagasilükkamine ei õnnestunud", "forget_room": "Unusta see jututuba", "forget_space": "Unusta see kogukond", @@ -2048,6 +2066,7 @@ "read_topic": "Teema lugemiseks klõpsi", "rejecting": "Hülgan kutset…", "rejoin_button": "Liitu uuesti", + "room_is_low_priority": "See on vähetähtis jututuba", "search": { "all_rooms_button": "Otsi kõikidest jututubadest", "placeholder": "Otsi sõnumeid…", @@ -2094,7 +2113,6 @@ "room_list": { "add_room_label": "Lisa jututuba", "add_space_label": "Lisa kogukonnakeskus", - "appearance": "Välimus", "breadcrumbs_empty": "Hiljuti külastatud jututubasid ei leidu", "breadcrumbs_label": "Hiljuti külastatud jututoad", "empty": { @@ -2119,6 +2137,7 @@ "filters": { "favourite": "Lemmikud", "invites": "Kutsed", + "low_priority": "Vähetähtis", "mentions": "Mainimised", "people": "Inimesed", "rooms": "Jututoad", @@ -2152,7 +2171,6 @@ }, "room_options": "Jututoa valikud", "show_less": "Näita vähem", - "show_message_previews": "Näita sõnumite eelvaateid", "show_n_more": { "one": "Näita veel %(count)s vestlust", "other": "Näita veel %(count)s vestlust" @@ -2687,6 +2705,9 @@ "inline_url_previews_room": "Luba URL'ide vaikimisi eelvaated selles jututoas osalejate jaoks", "inline_url_previews_room_account": "Luba URL'ide eelvaated selle jututoa jaoks (mõjutab vaid sind)", "insert_trailing_colon_mentions": "Mainimiste järel näita sõnumi alguses koolonit", + "invite_controls": { + "default_label": "Luba kasutajatel sind kutsida jututubadesse" + }, "jump_to_bottom_on_send": "Sõnumi saatmiseks hüppa ajajoone lõppu", "key_backup": { "backup_in_progress": "Sinu krüptovõtmeid varundatakse (esimese varukoopia tegemine võib võtta paar minutit).", @@ -2753,6 +2774,7 @@ "show_in_private": "Privaatsetes jututubades", "show_media": "Alati" }, + "not_supported": "See funktsionaalsus pole sinu serveris kasutusel.", "notifications": { "default_setting_description": "See seadistus kehtib vaikimisi kõikides sinu jututubades.", "default_setting_section": "Soovin teavitusi (vaikimisi seadistused)", @@ -2810,6 +2832,7 @@ "voip": "Kõned ja videokõned" }, "preferences": { + "Electron.enableContentProtection": "Blokeeri akna sisu salvestamine teiste rakenduste poolt", "Electron.enableHardwareAcceleration": "Kasuta riistvaralist kiirendust (jõustamiseks käivita %(appName)s uuesti)", "always_show_menu_bar": "Näita aknas alati menüüriba", "autocomplete_delay": "Viivitus automaatsel sõnalõpetusel (ms)", @@ -2982,6 +3005,7 @@ "show_chat_effects": "Näita vestluses edevat graafikat (näiteks kui keegi on saatnud serpentiine)", "show_displayname_changes": "Näita kuvatava nime muutusi", "show_join_leave": "Näita jututubade liitumise ja lahkumise teateid (ei käi kutsete, müksamiste ja keelamiste kohta)", + "show_message_previews": "Näita sõnumite eelvaateid", "show_nsfw_content": "Näita töökeskkonnas mittesobilikku sisu", "show_read_receipts": "Näita teiste kasutajate lugemisteatiseid", "show_redaction_placeholder": "Näita kustutatud sõnumite asemel kohatäidet", @@ -3088,6 +3112,8 @@ "jumptodate": "Vaata ajajoont alates sellest kuupäevast", "jumptodate_invalid_input": "Me ei suutnud sellist kuupäeva mõista (%(inputDate)s). Pigem kasuta aaaa-kk-pp vormingut.", "lenny": "Lisa ( ͡° ͜ʖ ͡°) smaili vormindamata sõnumi algusesse", + "manual_device_verification_confirm_description": "See võimaldab muul seadmel saata ja vastu võtta sõnumeid esinedes sinuna. KUI KEEGI PALUS SUL SIIA MIDAGI KOPEERIDA, SIIS ON SEE KAHTLANE JA ILMSELT PROOVITAKSE SIND PETTA. Kas sa oled kindel, et soovid seda teist seadet verifitseerida?", + "manual_device_verification_confirm_title": "Hoiatus: seadme käsitsi verifitseerimine", "me": "Näitab tegevusi", "msg": "Saadab sõnumi näidatud kasutajale", "myavatar": "Sellega muudad sinu tunnuspilti kõikides jututubades", @@ -3128,6 +3154,7 @@ "upgraderoom": "Uuendab jututoa uue versioonini", "upgraderoom_permission_error": "Sul ei ole piisavalt õigusi selle käsu käivitamiseks.", "usage": "Kasutus", + "verify": "Verifitseeri üks oma seadmetest käsitsi", "view": "Vaata sellise aadressiga jututuba", "whois": "Näitab teavet kasutaja kohta" }, diff --git a/src/i18n/strings/fa.json b/src/i18n/strings/fa.json index 7b375d4db0..acf6654db0 100644 --- a/src/i18n/strings/fa.json +++ b/src/i18n/strings/fa.json @@ -564,8 +564,6 @@ "security_key_title": "کلید امنیتی" }, "bootstrap_title": "تنظیم کلیدها", - "cancel_entering_passphrase_description": "آیا مطمئن هستید که می خواهید وارد کردن عبارت امنیتی را لغو کنید؟", - "cancel_entering_passphrase_title": "وارد کردن عبارت امنیتی لغو شود؟", "confirm_encryption_setup_body": "برای تأیید و فعال‌سازی رمزگذاری ، روی دکمه زیر کلیک کنید.", "confirm_encryption_setup_title": "راه‌اندازی رمزگذاری را تأیید کنید", "cross_signing_room_normal": "این اتاق به صورت سرتاسر رمزشده است", @@ -1716,6 +1714,7 @@ "upgraderoom": "یک اتاق را به نسخه جدید ارتقا دهید", "upgraderoom_permission_error": "شما مجوزهای لازم را برای استفاده از این دستور ندارید.", "usage": "استفاده", + "verify": "یک کاربر، نشست و عبارت کلید عمومی را تائید می‌کند", "whois": "اطلاعات مربوط به کاربر را نمایش می دهد" }, "space": { diff --git a/src/i18n/strings/fi.json b/src/i18n/strings/fi.json index 291b460f2e..7e993adc31 100644 --- a/src/i18n/strings/fi.json +++ b/src/i18n/strings/fi.json @@ -796,8 +796,6 @@ "security_key_title": "Palautusavain" }, "bootstrap_title": "Otetaan avaimet käyttöön", - "cancel_entering_passphrase_description": "Haluatko varmasti peruuttaa salasanan syöttämisen?", - "cancel_entering_passphrase_title": "Peruuta salasanan syöttäminen?", "confirm_encryption_setup_body": "Napsauta alla olevaa painiketta vahvistaaksesi salauksen asettamisen.", "confirm_encryption_setup_title": "Vahvista salauksen asetukset", "cross_signing_room_normal": "Tämä huone käyttää päästä päähän -salausta", @@ -1803,7 +1801,6 @@ "room_list": { "add_room_label": "Lisää huone", "add_space_label": "Lisää avaruus", - "appearance": "Ulkoasu", "breadcrumbs_empty": "Ei hiljattain vierailtuja huoneita", "breadcrumbs_label": "Hiljattain vieraillut huoneet", "empty": { @@ -1845,7 +1842,6 @@ "open_room": "Avoin huone %(roomName)s" }, "show_less": "Näytä vähemmän", - "show_message_previews": "Näytä viestien esikatselut", "show_n_more": { "one": "Näytä %(count)s lisää", "other": "Näytä %(count)s lisää" @@ -2699,6 +2695,7 @@ "upgraderoom": "Päivittää huoneen uuteen versioon", "upgraderoom_permission_error": "Sinulla ei ole vaadittavia oikeuksia tämän komennon käyttämiseksi.", "usage": "Käyttö", + "verify": "Varmentaa käyttäjän, istunnon ja julkiset avaimet", "whois": "Näyttää tietoa käyttäjästä" }, "space": { diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index 9992d5e51f..e7cab1d82a 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -911,15 +911,15 @@ "empty_room_was_name": "Salon vide (précédemment %(oldName)s)", "encryption": { "access_secret_storage_dialog": { + "alternatives": "Si vous avez une clé de récupération ou une phrase de sécurité, cela fonctionnera également.", "key_validation_text": { - "wrong_security_key": "Clé de récupération incorrecte" + "wrong_security_key": "La clé de récupération que vous avez saisie est incorrecte." }, + "privacy_warning": "Assurez vous que personne d'autre ne regarde votre écran !", "restoring": "Restauration des clés depuis la sauvegarde", "security_key_title": "Clé de récupération" }, "bootstrap_title": "Configuration des clés", - "cancel_entering_passphrase_description": "Souhaitez-vous vraiment annuler la saisie de la phrase de passe ?", - "cancel_entering_passphrase_title": "Annuler la saisie du mot de passe ?", "confirm_encryption_setup_body": "Cliquez sur le bouton ci-dessous pour confirmer la configuration du chiffrement.", "confirm_encryption_setup_title": "Confirmer la configuration du chiffrement", "cross_signing_room_normal": "Ce salon est chiffré de bout en bout", @@ -1954,6 +1954,7 @@ }, "face_pile_tooltip_shortcut": "Dont %(commaSeparatedMembers)s", "face_pile_tooltip_shortcut_joined": "Dont vous, %(commaSeparatedMembers)s", + "failed_determine_user": "Impossible de déterminer quel utilisateur à ignorer car l'événement des membres a changé.", "failed_reject_invite": "Échec du rejet de l’invitation", "forget_room": "Oublier ce salon", "forget_space": "Oublier cet espace", @@ -2091,7 +2092,6 @@ "room_list": { "add_room_label": "Ajouter un salon", "add_space_label": "Ajouter un espace", - "appearance": "Apparence", "breadcrumbs_empty": "Aucun salon visité récemment", "breadcrumbs_label": "Salons visités récemment", "empty": { @@ -2116,6 +2116,7 @@ "filters": { "favourite": "Favoris", "invites": "Invitations", + "low_priority": "Faible priorité", "mentions": "Mentions", "people": "Personnes", "rooms": "Salons", @@ -2149,7 +2150,6 @@ }, "room_options": "Options du salon", "show_less": "En voir moins", - "show_message_previews": "Afficher les aperçus des messages", "show_n_more": { "other": "En afficher %(count)s de plus", "one": "En afficher %(count)s de plus" @@ -2807,6 +2807,7 @@ "voip": "Appels audio et vidéo" }, "preferences": { + "Electron.enableContentProtection": "Empêcher le contenu de la fenêtre d'être capturé par d'autres applications", "Electron.enableHardwareAcceleration": "Activer l’accélération matérielle (redémarrez %(appName)s pour l'activer)", "always_show_menu_bar": "Toujours afficher la barre de menu de la fenêtre", "autocomplete_delay": "Délai pour l’autocomplétion (ms)", @@ -2979,6 +2980,7 @@ "show_chat_effects": "Afficher les animations de conversation (animations lors de la réception par ex. de confettis)", "show_displayname_changes": "Afficher les changements de nom d’affichage", "show_join_leave": "Afficher les messages d'arrivée et de départ (les invitations/expulsions/bannissements ne sont pas concernés)", + "show_message_previews": "Afficher les aperçus des messages", "show_nsfw_content": "Afficher le contenu sensible (NSFW)", "show_read_receipts": "Afficher les accusés de lecture envoyés par les autres utilisateurs", "show_redaction_placeholder": "Afficher les messages supprimés", @@ -3125,6 +3127,7 @@ "upgraderoom": "Met à niveau un salon vers une nouvelle version", "upgraderoom_permission_error": "Vous n’avez pas les autorisations nécessaires pour utiliser cette commande.", "usage": "Utilisation", + "verify": "Vérifie un utilisateur, une session et une collection de clés publiques", "view": "Affiche le salon avec cette adresse", "whois": "Affiche des informations à propos de l’utilisateur" }, diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index be37d40bc6..8b32535f57 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -676,8 +676,6 @@ "security_key_title": "Chave de Seguridade" }, "bootstrap_title": "Configurando as chaves", - "cancel_entering_passphrase_description": "¿Estás seguro de que non queres escribir a frase de paso?", - "cancel_entering_passphrase_title": "Cancelar a escrita da frase de paso?", "confirm_encryption_setup_body": "Preme no botón inferior para confirmar os axustes do cifrado.", "confirm_encryption_setup_title": "Confirma os axustes de cifrado", "cross_signing_room_normal": "Esta sala está cifrada extremo-a-extremo", @@ -2255,6 +2253,7 @@ "upgraderoom": "Subir a sala de versión", "upgraderoom_permission_error": "Non tes os permisos suficientes para usar este comando.", "usage": "Uso", + "verify": "Verifica unha usuaria, sesión e chave pública", "whois": "Mostra información acerca da usuaria" }, "space": { diff --git a/src/i18n/strings/he.json b/src/i18n/strings/he.json index 22a1cecc89..c4783212ef 100644 --- a/src/i18n/strings/he.json +++ b/src/i18n/strings/he.json @@ -574,8 +574,6 @@ "security_key_title": "מפתח אבטחה" }, "bootstrap_title": "מגדיר מפתחות", - "cancel_entering_passphrase_description": "האם אתם בטוחים שהינכם רוצים לבטל?", - "cancel_entering_passphrase_title": "בטל הקלדת סיסמא?", "confirm_encryption_setup_body": "לחץ על הלחצן למטה כדי לאשר את הגדרת ההצפנה.", "confirm_encryption_setup_title": "אשר את הגדרת ההצפנה", "cross_signing_room_normal": "חדר זה מוצפן מקצה לקצה", @@ -1874,6 +1872,7 @@ "upgraderoom": "משדרג את החדר לגרסא חדשה", "upgraderoom_permission_error": "אין לכם הרשאות להשתמש בפקודה זו.", "usage": "שימוש", + "verify": "מוודא משתמש, התחברות וצמד מפתח ציבורי", "whois": "מציג מידע אודות משתמש" }, "space": { diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index 76f822fee4..c5d7cd6704 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -909,14 +909,13 @@ "encryption": { "access_secret_storage_dialog": { "key_validation_text": { - "wrong_security_key": "Hibás helyreállítási kulcs" + "wrong_security_key": "A megadott helyreállítási kulcs helytelen." }, + "privacy_warning": "Győződjön meg arról, hogy senki sem látja ezt a képernyőt!", "restoring": "Kulcsok helyreállítása mentésből", "security_key_title": "Helyreállítási kulcs" }, "bootstrap_title": "Kulcsok beállítása", - "cancel_entering_passphrase_description": "Biztos, hogy megszakítja a jelmondat bevitelét?", - "cancel_entering_passphrase_title": "Megszakítja a jelmondat bevitelét?", "confirm_encryption_setup_body": "Az alábbi gomb megnyomásával erősítsd meg, hogy megadod a titkosítási beállításokat.", "confirm_encryption_setup_title": "Erősítse meg a titkosítási beállításokat", "cross_signing_room_normal": "Ez a szoba végpontok közti titkosítást használ", @@ -2076,7 +2075,6 @@ "room_list": { "add_room_label": "Szoba hozzáadása", "add_space_label": "Tér hozzáadása", - "appearance": "Megjelenés", "breadcrumbs_empty": "Nincsenek nemrégiben meglátogatott szobák", "breadcrumbs_label": "Nemrég meglátogatott szobák", "empty": { @@ -2132,7 +2130,6 @@ }, "room_options": "Szobabeállítások", "show_less": "Kevesebb megjelenítése", - "show_message_previews": "Üzenetelőnézetek megjelenítése", "show_n_more": { "Még %(count)s megjelenítése": "one" }, @@ -3102,6 +3099,7 @@ "upgraderoom": "Új verzióra fejleszti a szobát", "upgraderoom_permission_error": "A parancs használatához nincs meg a megfelelő jogosultsága.", "usage": "Használat", + "verify": "Felhasználó, munkamenet és nyilvános kulcs hármas ellenőrzése", "view": "Megadott címmel rendelkező szobák megjelenítése", "whois": "Információt jelenít meg a felhasználóról" }, @@ -3223,8 +3221,8 @@ "start_group_chat_button": "Csoportos csevegés indítása" }, "stickers": { - "empty": "Nincs engedélyezett matrica csomagod", - "empty_add_prompt": "Adj hozzá párat" + "empty": "Jelenleg nincsenek engedélyezve matricacsomagok", + "empty_add_prompt": "Adjon hozzá néhányat" }, "terms": { "column_document": "Dokumentum", diff --git a/src/i18n/strings/id.json b/src/i18n/strings/id.json index 4df0b7707b..9f7c8b10f8 100644 --- a/src/i18n/strings/id.json +++ b/src/i18n/strings/id.json @@ -917,8 +917,6 @@ "security_key_title": "Kunci pemulihan" }, "bootstrap_title": "Menyiapkan kunci", - "cancel_entering_passphrase_description": "Apakah Anda yakin untuk membatalkan pemasukkan frasa sandi?", - "cancel_entering_passphrase_title": "Batalkan memasukkan frasa sandi?", "confirm_encryption_setup_body": "Klik tombol di bawah untuk mengkonfirmasi menyiapkan enkripsi.", "confirm_encryption_setup_title": "Konfirmasi pengaturan enkripsi", "cross_signing_room_normal": "Ruangan ini dienkripsi secara ujung ke ujung", @@ -2086,7 +2084,6 @@ "room_list": { "add_room_label": "Tambahkan ruangan", "add_space_label": "Tambahkan space", - "appearance": "Penampilan", "breadcrumbs_empty": "Tidak ada ruangan yang baru saja dilihat", "breadcrumbs_label": "Ruangan yang baru saja dilihat", "empty": { @@ -2144,7 +2141,6 @@ }, "room_options": "Opsi Ruangan", "show_less": "Tampilkan lebih sedikit", - "show_message_previews": "Tampilkan pratinjau pesan", "show_n_more": { "one": "Tampilkan %(count)s lagi", "other": "Tampilkan %(count)s lagi" @@ -3120,6 +3116,7 @@ "upgraderoom": "Meningkatkan ruangan ke versi yang baru", "upgraderoom_permission_error": "Anda tidak memiliki izin yang dibutuhkan untuk menggunakan perintah ini.", "usage": "Penggunaan", + "verify": "Memverifikasi sebuah pengguna, sesi, dan tupel pubkey", "view": "Menampilkan ruangan dengan alamat yang ditentukan", "whois": "Menampilkan informasi tentang sebuah pengguna" }, diff --git a/src/i18n/strings/is.json b/src/i18n/strings/is.json index f57cdf156e..69792b62d1 100644 --- a/src/i18n/strings/is.json +++ b/src/i18n/strings/is.json @@ -679,8 +679,6 @@ "security_key_title": "Öryggislykill" }, "bootstrap_title": "Set upp dulritunarlykla", - "cancel_entering_passphrase_description": "Viltu örugglega hætta við að setja inn lykilfrasa?", - "cancel_entering_passphrase_title": "Hætta við að setja inn lykilfrasa?", "confirm_encryption_setup_body": "Smelltu á hnappinn hér að neðan til að staðfesta uppsetningu á dulritun.", "confirm_encryption_setup_title": "Staðfestu uppsetningu dulritunar", "cross_signing_room_normal": "Þessi spjallrás er enda-í-enda dulrituð", @@ -2185,6 +2183,7 @@ "upgraderoom": "Uppfærir spjallrás í nýja útgáfu", "upgraderoom_permission_error": "Þú hefur ekki nauðsynlegar heimildir til að nota þessa skipun.", "usage": "Notkun", + "verify": "Sannreynir auðkenni notanda, setu og dreifilykils", "whois": "Birtir upplýsingar um notanda" }, "space": { diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index 66ea31ad9c..7213b88974 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -801,8 +801,6 @@ "security_key_title": "Chiave di sicurezza" }, "bootstrap_title": "Configurazione chiavi", - "cancel_entering_passphrase_description": "Sei sicuro di volere annullare l'inserimento della frase?", - "cancel_entering_passphrase_title": "Annullare l'inserimento della password?", "confirm_encryption_setup_body": "Clicca il pulsante sotto per confermare l'impostazione della crittografia.", "confirm_encryption_setup_title": "Conferma impostazione crittografia", "cross_signing_room_normal": "Questa stanza è cifrata end-to-end", @@ -2712,6 +2710,7 @@ "upgraderoom": "Aggiorna una stanza ad una nuova versione", "upgraderoom_permission_error": "Non hai l'autorizzazione necessaria per usare questo comando.", "usage": "Utilizzo", + "verify": "Verifica un utente, una sessione e una tupla pubblica", "view": "Visualizza la stanza con l'indirizzo dato", "whois": "Mostra le informazioni di un utente" }, diff --git a/src/i18n/strings/ja.json b/src/i18n/strings/ja.json index e4e61e8907..c19ebfd082 100644 --- a/src/i18n/strings/ja.json +++ b/src/i18n/strings/ja.json @@ -760,8 +760,6 @@ "security_key_title": "セキュリティーキー" }, "bootstrap_title": "鍵のセットアップ", - "cancel_entering_passphrase_description": "パスフレーズの入力をキャンセルしてよろしいですか?", - "cancel_entering_passphrase_title": "パスフレーズの入力をキャンセルしますか?", "confirm_encryption_setup_body": "以下のボタンをクリックして、暗号化の設定を承認してください。", "confirm_encryption_setup_title": "暗号化の設定を承認してください", "cross_signing_room_normal": "このルームはエンドツーエンドで暗号化されています", @@ -2462,6 +2460,7 @@ "upgraderoom": "ルームを新しいバージョンにアップグレード", "upgraderoom_permission_error": "このコマンドを実行するのに必要な権限がありません。", "usage": "用法", + "verify": "ユーザー、セッション、およびpubkeyタプルを認証", "whois": "ユーザーの情報を表示" }, "space": { diff --git a/src/i18n/strings/ka.json b/src/i18n/strings/ka.json index 514e069bfa..440c373cac 100644 --- a/src/i18n/strings/ka.json +++ b/src/i18n/strings/ka.json @@ -665,8 +665,6 @@ "security_key_title": "უსაფრთხოების გასაღები" }, "bootstrap_title": "გასაღებების დაყენება", - "cancel_entering_passphrase_description": "დარწმუნებული ხართ, რომ გსურთ გააუქმოთ პაროლის შეყვანა?", - "cancel_entering_passphrase_title": "გააუქმოს პაროლის შეყვანა?", "confirm_encryption_setup_body": "დააწკაპუნეთ ქვემოთ მოცემულ ღილაკზე დაშიფვრის დაყენების დასადასტურებლად.", "confirm_encryption_setup_title": "დაადასტურეთ დაშიფვრის დაყენება", "cross_signing_room_normal": "", diff --git a/src/i18n/strings/lo.json b/src/i18n/strings/lo.json index 782980078d..3feb855896 100644 --- a/src/i18n/strings/lo.json +++ b/src/i18n/strings/lo.json @@ -668,8 +668,6 @@ "security_key_title": "ກະແຈຄວາມປອດໄພ" }, "bootstrap_title": "ການຕັ້ງຄ່າກະແຈ", - "cancel_entering_passphrase_description": "ທ່ານແນ່ໃຈບໍ່ວ່າຕ້ອງການຍົກເລີກການໃສ່ປະໂຫຍກລະຫັດຜ່ານ?", - "cancel_entering_passphrase_title": "ຍົກເລີກການໃສ່ປະໂຫຍກລະຫັດຜ່ານບໍ?", "confirm_encryption_setup_body": "ກົດທີ່ປຸ່ມຂ້າງລຸ່ມນີ້ເພື່ອຢືນຢັນການຕັ້ງຄ່າການເຂົ້າລະຫັດ.", "confirm_encryption_setup_title": "ຢືນຢັນການຕັ້ງຄ່າການເຂົ້າລະຫັດ", "cross_signing_room_normal": "ຫ້ອງນີ້ຖືກເຂົ້າລະຫັດແບບຕົ້ນທາງ-ເຖິງປາຍທາງ", @@ -2182,6 +2180,7 @@ "upgraderoom": "ຍົກລະດັບຫ້ອງເປັນລຸ້ນໃໝ່", "upgraderoom_permission_error": "ທ່ານບໍ່ມີສິດໃຊ້ຄໍາສັ່ງນີ້.", "usage": "ການນໍາໃຊ້", + "verify": "ຢືນຢັນຜູ້ໃຊ້, ລະບົບ, ແລະ pubkey tuple", "whois": "ສະແດງຂໍ້ມູນກ່ຽວກັບຜູ້ໃຊ້" }, "space": { diff --git a/src/i18n/strings/lt.json b/src/i18n/strings/lt.json index 101e8fbc0c..ce5a36d807 100644 --- a/src/i18n/strings/lt.json +++ b/src/i18n/strings/lt.json @@ -505,8 +505,6 @@ "security_key_title": "Saugumo Raktas" }, "bootstrap_title": "Raktų nustatymas", - "cancel_entering_passphrase_description": "Ar tikrai norite atšaukti slaptafrazės įvedimą?", - "cancel_entering_passphrase_title": "Atšaukti slaptafrazės įvedimą?", "confirm_encryption_setup_body": "Paspauskite mygtuką žemiau, kad patvirtintumėte šifravimo nustatymą.", "confirm_encryption_setup_title": "Patvirtinti šifravimo sąranką", "cross_signing_room_normal": "Šis kambarys visapusiškai užšifruotas", @@ -1739,6 +1737,7 @@ "upgraderoom": "Atnaujina kambarį į naują versiją", "upgraderoom_permission_error": "Jūs neturite reikalingų leidimų naudoti šią komandą.", "usage": "Naudojimas", + "verify": "Patvirtina vartotojo, seanso ir pubkey daugiadalę duomenų struktūrą", "whois": "Parodo informaciją apie vartotoją" }, "space": { @@ -1940,9 +1939,12 @@ "unknown": "%(senderDisplayName)s pakeitė prisijungimo taisyklę į %(rule)s" }, "m.room.member": { + "accepted_3pid_invite": "%(targetName)s priėmė kvietimą, skirtą %(displayName)s", + "accepted_invite": "%(targetName)s priėmė kvietimą", "ban": "%(senderName)s užblokavo %(targetName)s", "change_avatar": "%(senderName)s pakeitė savo profilio nuotrauką", "change_name": "%(oldDisplayName)s pasikeitė savo rodomą vardą į %(displayName)s", + "invite": "%(senderName)s pakvietė %(targetName)s", "join": "%(targetName)s prisijungė prie kambario", "kick": "%(senderName)s pašalino %(targetName)s", "kick_reason": "%(senderName)s pašalino %(targetName)s: %(reason)s", diff --git a/src/i18n/strings/lv.json b/src/i18n/strings/lv.json index 739e9a81b0..18c1d8361b 100644 --- a/src/i18n/strings/lv.json +++ b/src/i18n/strings/lv.json @@ -840,8 +840,6 @@ "security_key_title": "Drošības atslēga" }, "bootstrap_title": "Atslēgu iestatīšana", - "cancel_entering_passphrase_description": "Vai tiešām atcelt paroles vārdkopas ievadīšanu?", - "cancel_entering_passphrase_title": "Atcelt frāzveida paroles ievadi?", "confirm_encryption_setup_body": "Noklikšķiniet uz tālāk esošās pogas, lai apstiprinātu šifrēšanas iestatīšanu.", "confirm_encryption_setup_title": "Apstiprināt šifrēšanas iestatīšanu", "cross_signing_room_normal": "Šajā istabā tiek veikta pilnīga šifrēšana", @@ -2643,6 +2641,7 @@ "upgraderoom": "Atjaunina istabu uz jaunu versiju", "upgraderoom_permission_error": "Nav šīs komandas izmantošanai nepieciešamo atļauju.", "usage": "Lietojums", + "verify": "Verificē lietotāju, sesiju un publiskās atslēgas", "view": "Skata istabu ar norādīto adresi", "whois": "Parāda lietotāja informāciju" }, diff --git a/src/i18n/strings/mg_MG.json b/src/i18n/strings/mg_MG.json index 1ec5ed76c5..c6666dac0b 100644 --- a/src/i18n/strings/mg_MG.json +++ b/src/i18n/strings/mg_MG.json @@ -797,8 +797,6 @@ "security_key_title": "Kitendry fiarovana" }, "bootstrap_title": "Fametrahana fanalahidy", - "cancel_entering_passphrase_description": "Tena te-hanafoana ny fampidirana fehezanteny ve ianao?", - "cancel_entering_passphrase_title": "Hanafoana ny fidirana amin'ny teny miafina ?", "confirm_encryption_setup_body": "Kitiho ny bokotra etsy ambany hanamafisana ny fametrahana encryption.", "confirm_encryption_setup_title": "Hamafiso ny fanamboarana fanafenana", "cross_signing_room_normal": "Ity efitrano ity dia misy encryption avy hatrany", diff --git a/src/i18n/strings/nb_NO.json b/src/i18n/strings/nb_NO.json index cab5ac8a05..799e67b4a6 100644 --- a/src/i18n/strings/nb_NO.json +++ b/src/i18n/strings/nb_NO.json @@ -786,6 +786,7 @@ "cross_signing_status": "Status for krysssignering:", "cross_signing_untrusted": "Kontoen din har en krysssigneringsidentitet i hemmelig lagring, men den er ennå ikke klarert av denne sesjonen.", "crypto_not_available": "Kryptografisk modul er ikke tilgjengelig", + "device_id": "Enhets-ID", "key_backup_active_version": "Aktiv sikkerhetskopiversion:", "key_backup_active_version_none": "Ingen", "key_backup_inactive_warning": "Nøklene dine blir ikke sikkerhetskopiert fra denne sesjonen.", @@ -798,6 +799,8 @@ "secret_storage_ready": "klar", "secret_storage_status": "Hemmelig lagringsplass:", "self_signing_private_key_cached_status": "Selvsignert privat nøkkel:", + "session": "Sesjon", + "session_fingerprint": "Fingeravtrykk (sesjonsnøkkel)", "title": "Ende-til-ende-kryptering", "user_signing_private_key_cached_status": "Brukersignert privat nøkkel:" }, @@ -823,6 +826,7 @@ "low_bandwidth_mode": "Lav båndbreddemodus", "low_bandwidth_mode_description": "Krever kompatibel hjemmeserver.", "main_timeline": "Hovedtidslinje", + "manual_device_verification": "Manuell enhetsverifisering", "no_receipt_found": "Ingen kvittering funnet", "notification_state": "Varslingsstatus er %(notificationState)s", "notifications_debug": "Feilsøking av varsler", @@ -920,8 +924,6 @@ "security_key_title": "Gjenopprettingsnøkkel" }, "bootstrap_title": "Setter opp nøkler", - "cancel_entering_passphrase_description": "Er du sikker på at du vil avbryte inntasting av passordfrase?", - "cancel_entering_passphrase_title": "Avbryte inntastingen av passordfrase?", "confirm_encryption_setup_body": "Klikk på knappen nedenfor for å bekrefte konfigureringen av kryptering.", "confirm_encryption_setup_title": "Bekreft krypteringsoppsett", "cross_signing_room_normal": "Dette rommet er ende-til-ende-kryptert", @@ -1009,6 +1011,17 @@ "incoming_sas_dialog_waiting": "Venter på at partneren skal bekrefte...", "incoming_sas_user_dialog_text_1": "Bekreft denne brukeren for å markere dem som klarerte. Tillit til brukere gir deg ekstra trygghet når du bruker ende-til-ende-krypterte meldinger.", "incoming_sas_user_dialog_text_2": "Bekreftelse av denne brukeren vil markere økten som klarert, og også merke økten din som klarert for dem.", + "manual": { + "already_verified": "Denne enheten er allerede verifisert", + "already_verified_and_wrong_fingerprint": "Det medfølgende fingeravtrykket stemmer ikke overens, men enheten er allerede verifisert!", + "device_id": "Enhets-ID", + "failure_description": "Kunne ikke verifisere '%(deviceId)s': %(error)s", + "failure_title": "Verifisering mislyktes", + "fingerprint": "Fingeravtrykk (sesjonsnøkkel)", + "no_crypto": "Kan ikke verifisere enheten - krypto er ikke aktivert", + "no_device": "Kunne ikke verifisere enheten - enheten '%(deviceId)s' ble ikke funnet", + "no_userid": "Kan ikke verifisere enheten - finner ikke bruker-ID" + }, "no_key_or_device": "Det ser ut til at du ikke har en gjenopprettingsnøkkel eller andre enheter du kan verifisere mot. Denne enheten vil ikke kunne få tilgang til gamle krypterte meldinger. For å bekrefte identiteten din på denne enheten, må du tilbakestille verifiseringsnøklene dine.", "no_support_qr_emoji": "Enheten du prøver å bekrefte støtter ikke skanning av en QR-kode eller emoji-verifikasjon, som er det som %(brand)s støtter. Prøv med en annen klient.", "other_party_cancelled": "Den andre parten kansellerte verifiseringen.", @@ -1957,6 +1970,7 @@ }, "face_pile_tooltip_shortcut": "Inkludert %(commaSeparatedMembers)s", "face_pile_tooltip_shortcut_joined": "Inkludert deg, %(commaSeparatedMembers)s", + "failed_determine_user": "Kan ikke avgjøre hvilken bruker som skal ignoreres siden medlemshendelsen har endret seg.", "failed_reject_invite": "Kunne ikke avvise invitasjonen", "forget_room": "Glem dette rommet", "forget_space": "Glem dette området", @@ -2048,6 +2062,7 @@ "read_topic": "Klikk for å lese emnet", "rejecting": "Avviser invitasjon...", "rejoin_button": "Bli med igjen", + "room_is_low_priority": "Dette er et lavt prioritert rom", "search": { "all_rooms_button": "Søk i alle rom", "placeholder": "Søk i meldinger...", @@ -2094,7 +2109,6 @@ "room_list": { "add_room_label": "Legg til et rom", "add_space_label": "Legg til område", - "appearance": "Utseende", "breadcrumbs_empty": "Ingen nylig besøkte rom", "breadcrumbs_label": "Nylig besøkte rom", "empty": { @@ -2119,6 +2133,7 @@ "filters": { "favourite": "Favoritter", "invites": "Invitasjoner", + "low_priority": "Lav prioritet", "mentions": "Omtaler", "people": "Personer", "rooms": "Rom", @@ -2152,7 +2167,6 @@ }, "room_options": "Rominnstillinger", "show_less": "Vis mindre", - "show_message_previews": "Aktiver forhåndsvisning av meldinger", "show_n_more": { "Vis %(count)s til": "Vis %(count)s mer" }, @@ -2686,6 +2700,9 @@ "inline_url_previews_room": "Skru på URL-forhåndsvisninger som standard for deltakerne i dette rommet", "inline_url_previews_room_account": "Skru på URL-forhåndsvisninger for dette rommet (Påvirker bare deg)", "insert_trailing_colon_mentions": "Sett inn et etterfølgende kolon etter at brukeromtaler i starten av en melding", + "invite_controls": { + "default_label": "Tillat brukere å invitere deg til rom" + }, "jump_to_bottom_on_send": "Gå til bunnen av tidslinjen når du vil sende en melding", "key_backup": { "backup_in_progress": "Nøklene dine blir sikkerhetskopiert (den første sikkerhetskopieringen kan ta noen minutter).", @@ -2752,6 +2769,7 @@ "show_in_private": "I private rom", "show_media": "Vis alltid" }, + "not_supported": "Serveren din implementerer ikke denne funksjonen.", "notifications": { "default_setting_description": "Denne innstillingen vil bli brukt som standard for alle rommene dine.", "default_setting_section": "Jeg ønsker å bli varslet for (standardinnstilling)", @@ -2809,6 +2827,7 @@ "voip": "Lyd- og videosamtaler" }, "preferences": { + "Electron.enableContentProtection": "Forhindre at innholdet i vinduet fanges opp av andre apper", "Electron.enableHardwareAcceleration": "Aktiver maskinvareakselerasjon (start %(appName)s på nytt for at det skal tre i kraft)", "always_show_menu_bar": "Vis alltid vinduets menylinje", "autocomplete_delay": "Autofullføringsforsinkelse (ms)", @@ -2981,6 +3000,7 @@ "show_chat_effects": "Vis chatteffekter (animasjoner når du mottar f.eks. konfetti)", "show_displayname_changes": "Vis visningsnavnendringer", "show_join_leave": "Vis bli med/forlat meldinger (invitasjoner/fjernede/utestengte upåvirket)", + "show_message_previews": "Vise forhåndsvisninger av meldinger", "show_nsfw_content": "Vis NSFW-innhold", "show_read_receipts": "Vis lesekvitteringer sendt av andre brukere", "show_redaction_placeholder": "Vis en stattholder for fjernede meldinger", @@ -3127,6 +3147,7 @@ "upgraderoom": "Oppgraderer et rom til en ny versjon", "upgraderoom_permission_error": "Du har ikke de rette tilgangene til å bruke denne kommandoen.", "usage": "Bruk", + "verify": "Verifiserer en bruker-, økt- og pubkey-tuple", "view": "Viser rom med oppgitt adresse", "whois": "Viser informasjon om en bruker" }, diff --git a/src/i18n/strings/nl.json b/src/i18n/strings/nl.json index c8cfd1e8ba..348a0b43d3 100644 --- a/src/i18n/strings/nl.json +++ b/src/i18n/strings/nl.json @@ -699,8 +699,6 @@ "security_key_title": "Veiligheidssleutel" }, "bootstrap_title": "Sleutelconfiguratie", - "cancel_entering_passphrase_description": "Weet je zeker, dat je het invoeren van je wachtwoord wilt afbreken?", - "cancel_entering_passphrase_title": "Wachtwoord annuleren?", "confirm_encryption_setup_body": "Klik op de knop hieronder om het instellen van de versleuting te bevestigen.", "confirm_encryption_setup_title": "Bevestig versleuting instelling", "cross_signing_room_normal": "Deze kamer is eind-tot-eind-versleuteld", @@ -2301,6 +2299,7 @@ "upgraderoom": "Upgrade deze kamer naar een nieuwere versie", "upgraderoom_permission_error": "Je beschikt niet over de vereiste machtigingen om deze opdracht uit te voeren.", "usage": "Gebruik", + "verify": "Verifieert de combinatie van persoon, sessie en publieke sleutel", "whois": "Geeft informatie weer over een persoon" }, "space": { diff --git a/src/i18n/strings/pl.json b/src/i18n/strings/pl.json index 42eb0de4af..ce09d10a57 100644 --- a/src/i18n/strings/pl.json +++ b/src/i18n/strings/pl.json @@ -923,8 +923,6 @@ "security_key_title": "Klucz przywracania" }, "bootstrap_title": "Konfigurowanie kluczy", - "cancel_entering_passphrase_description": "Czy na pewno chcesz anulować wpisywanie hasła?", - "cancel_entering_passphrase_title": "Anulować wpisywanie hasła?", "confirm_encryption_setup_body": "Kliknij przycisk poniżej, aby potwierdzić ustawienie szyfrowania.", "confirm_encryption_setup_title": "Potwierdź ustawienie szyfrowania", "cross_signing_room_normal": "Ten pokój jest szyfrowany end-to-end", @@ -2105,7 +2103,6 @@ "room_list": { "add_room_label": "Dodaj pokój", "add_space_label": "Dodaj przestrzeń", - "appearance": "Wygląd", "breadcrumbs_empty": "Brak ostatnio odwiedzonych pokojów", "breadcrumbs_label": "Ostatnio odwiedzane pokoje", "empty": { @@ -2158,7 +2155,6 @@ }, "room_options": "Opcje pokoju", "show_less": "Pokaż mniej", - "show_message_previews": "Pokaż podglądy wiadomości", "show_n_more": { "one": "Pokaż %(count)s więcej", "few": "Pokaż %(count)s więcej", @@ -3133,6 +3129,7 @@ "upgraderoom": "Ulepsza pokój do nowej wersji", "upgraderoom_permission_error": "Nie posiadasz wymaganych uprawnień do użycia tego polecenia.", "usage": "Użycie", + "verify": "Weryfikuje użytkownika, sesję oraz klucz publiczny", "view": "Przegląda pokój z podanym adresem", "whois": "Pokazuje informacje na temat użytkownika" }, diff --git a/src/i18n/strings/pt.json b/src/i18n/strings/pt.json index db6df857a6..eefb0a8254 100644 --- a/src/i18n/strings/pt.json +++ b/src/i18n/strings/pt.json @@ -892,8 +892,6 @@ "security_key_title": "Chave de recuperação" }, "bootstrap_title": "A configurar chaves", - "cancel_entering_passphrase_description": "Tem a certeza que quer cancelar a introdução da frase-passe?", - "cancel_entering_passphrase_title": "Cancelar a introdução da frase-passe?", "confirm_encryption_setup_body": "Clica no botão abaixo para confirmar a configuração da encriptação.", "confirm_encryption_setup_title": "Confirma a configuração da encriptação", "cross_signing_room_normal": "Esta sala é encriptada de ponta a ponta", diff --git a/src/i18n/strings/pt_BR.json b/src/i18n/strings/pt_BR.json index c0f38b3163..ebe5b8c12f 100644 --- a/src/i18n/strings/pt_BR.json +++ b/src/i18n/strings/pt_BR.json @@ -786,6 +786,7 @@ "cross_signing_status": "Status de assinatura cruzada:", "cross_signing_untrusted": "Sua conta tem uma identidade de assinatura cruzada no armazenamento secreto, mas ela ainda não é confiável para esta sessão.", "crypto_not_available": "O módulo criptográfico não está disponível", + "device_id": "ID do dispositivo", "key_backup_active_version": "Versão de backup ativo:", "key_backup_active_version_none": "Nenhuma", "key_backup_inactive_warning": "Suas chaves não estão sendo copiadas nesta sessão.", @@ -798,6 +799,8 @@ "secret_storage_ready": "pronto", "secret_storage_status": "Armazenamento secreto:", "self_signing_private_key_cached_status": "Chave privada auto-assinada:", + "session": "Sessão", + "session_fingerprint": "Impressão digital (chave de sessão)", "title": "Criptografia de ponta a ponta", "user_signing_private_key_cached_status": "Chave privada de assinatura do usuário:" }, @@ -823,6 +826,7 @@ "low_bandwidth_mode": "Modo de baixa largura de banda", "low_bandwidth_mode_description": "Requer servidor doméstico compatível.", "main_timeline": "Linha do tempo principal", + "manual_device_verification": "Verificação manual do dispositivo", "no_receipt_found": "Nenhum recibo encontrado", "notification_state": "O estado da notificação é%(notificationState)s", "notifications_debug": "Depuração de notificações", @@ -920,8 +924,6 @@ "security_key_title": "Chave de recuperação" }, "bootstrap_title": "Configurar chaves", - "cancel_entering_passphrase_description": "Tem certeza que quer cancelar a introdução da frase de senha?", - "cancel_entering_passphrase_title": "Cancelar a introdução da frase de senha?", "confirm_encryption_setup_body": "Clique no botão abaixo para confirmar a configuração da criptografia.", "confirm_encryption_setup_title": "Confirmar a configuração de criptografia", "cross_signing_room_normal": "Esta sala é criptografada de ponta a ponta", @@ -1009,6 +1011,21 @@ "incoming_sas_dialog_waiting": "Aguardando a confirmação do parceiro…", "incoming_sas_user_dialog_text_1": "Confirme este usuário para torná-lo confiável. Confiar nos usuários fornece segurança adicional ao trocar mensagens criptografadas de ponta a ponta.", "incoming_sas_user_dialog_text_2": "Se você confirmar esse usuário, a sessão será marcada como confiável para você e para ele.", + "manual": { + "already_verified": "Este dispositivo já está verificado", + "already_verified_and_wrong_fingerprint": "A impressão digital fornecida não corresponde, mas o dispositivo já foi verificado!", + "device_id": "ID do dispositivo", + "failure_description": "Falha ao verificar '%(deviceId)s': %(error)s", + "failure_title": "A verificação falhou", + "fingerprint": "Impressão digital (chave de sessão)", + "no_crypto": "Não é possível verificar o dispositivo - a criptografia não está ativada", + "no_device": "Não foi possível verificar o dispositivo - o dispositivo '%(deviceId)s' não foi encontrado", + "no_userid": "Não foi possível verificar o dispositivo - não foi possível encontrar nossa ID de usuário", + "success_description": "O dispositivo (%(deviceId)s) agora tem assinatura cruzada", + "success_title": "Verificação bem-sucedida", + "text": "Forneça o ID e a impressão digital de um de seus próprios dispositivos para verificá-lo. OBSERVE que isso permite que o outro dispositivo envie e receba mensagens como você. SE ALGUÉM LHE DISSE PARA COLAR ALGO AQUI, É PROVÁVEL QUE VOCÊ ESTEJA SENDO ENGANADO!", + "wrong_fingerprint": "Não é possível verificar o dispositivo '%(deviceId)s' - a impressão digital fornecida '%(fingerprint)s' não corresponde à impressão digital do dispositivo, '%(fprint)s'" + }, "no_key_or_device": "Parece que você não tem uma chave de segurança ou qualquer outro dispositivo que possa ser verificado. Este dispositivo não poderá acessar mensagens criptografadas antigas. Para verificar sua identidade neste dispositivo, você precisará redefinir suas chaves de verificação.", "no_support_qr_emoji": "O dispositivo que você está tentando verificar não suporta a leitura de um código QR ou verificação de emoji, que é o que %(brand)s suporta. Tente com um cliente diferente.", "other_party_cancelled": "Seu contato cancelou a confirmação.", @@ -1956,6 +1973,7 @@ }, "face_pile_tooltip_shortcut": "Incluindo %(commaSeparatedMembers)s", "face_pile_tooltip_shortcut_joined": "Incluindo você, %(commaSeparatedMembers)s", + "failed_determine_user": "Não é possível determinar qual usuário ignorar, pois o evento do membro foi alterado.", "failed_reject_invite": "Não foi possível recusar o convite", "forget_room": "Esquecer esta sala", "forget_space": "Esqueça este espaço", @@ -2047,6 +2065,7 @@ "read_topic": "Clique para ler o tópico", "rejecting": "Rejeitando o convite...", "rejoin_button": "Entrar novamente", + "room_is_low_priority": "Esta é uma sala de baixa prioridade", "search": { "all_rooms_button": "Pesquisar todos as salas", "placeholder": "Pesquisar mensagens...", @@ -2093,7 +2112,6 @@ "room_list": { "add_room_label": "Adicionar sala", "add_space_label": "Adicionar espaço", - "appearance": "Aparência", "breadcrumbs_empty": "Nenhuma sala foi visitada recentemente", "breadcrumbs_label": "Salas visitadas recentemente", "empty": { @@ -2118,6 +2136,7 @@ "filters": { "favourite": "Favoritos", "invites": "Convites", + "low_priority": "Baixa prioridade", "mentions": "Menções", "people": "Pessoas", "rooms": "Salas", @@ -2151,7 +2170,6 @@ }, "room_options": "Opções da Sala", "show_less": "Mostrar menos", - "show_message_previews": "Mostrar prévias de mensagens", "show_n_more": { "other": "Mostrar %(count)s a mais", "one": "Mostrar %(count)s a mais" @@ -2686,6 +2704,9 @@ "inline_url_previews_room": "Ativar, para todos os participantes desta sala, a visualização de links", "inline_url_previews_room_account": "Ativar, para esta sala, a visualização de links (só afeta você)", "insert_trailing_colon_mentions": "Insira dois pontos à direita após o usuário mencionar no início de uma mensagem", + "invite_controls": { + "default_label": "Permitir que usuários convidem você para salas" + }, "jump_to_bottom_on_send": "Vá para o final da linha do tempo ao enviar uma mensagem", "key_backup": { "backup_in_progress": "O backup de suas chaves está sendo feito (o primeiro backup pode demorar alguns minutos).", @@ -2752,6 +2773,7 @@ "show_in_private": "Em salas privadas", "show_media": "Mostrar sempre" }, + "not_supported": "Seu servidor não implementa esse recurso.", "notifications": { "default_setting_description": "Essa configuração será aplicada por padrão a todas as suas salas.", "default_setting_section": "Quero ser notificado sobre (configuração padrão)", @@ -2809,6 +2831,7 @@ "voip": "Chamadas de áudio e vídeo" }, "preferences": { + "Electron.enableContentProtection": "Impedir que o conteúdo da janela seja capturado por outros aplicativos", "Electron.enableHardwareAcceleration": "Ativar a aceleração de hardware (reinicie %(appName)s para fazer efeito)", "always_show_menu_bar": "Mostrar a barra de menu na janela", "autocomplete_delay": "Atraso no preenchimento automático (ms)", @@ -2981,6 +3004,7 @@ "show_chat_effects": "Mostrar efeitos na conversa (por exemplo: animações ao receber confetes)", "show_displayname_changes": "Mostrar alterações de nome e sobrenome", "show_join_leave": "Mostrar mensagens de entrada/saída (convites/remoções/bans não são afetados)", + "show_message_previews": "Mostrar prévias de mensagens", "show_nsfw_content": "Mostrar conteúdo NSFW (\"Não seguro para o trabalho\")", "show_read_receipts": "Mostrar confirmações de leitura dos outros usuários", "show_redaction_placeholder": "Mostrar um marcador para as mensagens removidas", @@ -3127,6 +3151,7 @@ "upgraderoom": "Atualiza a sala para uma nova versão", "upgraderoom_permission_error": "Você não tem as permissões necessárias para usar este comando.", "usage": "Uso", + "verify": "Verifique manualmente um de seus próprios dispositivos", "view": "Visualizações da sala com o endereço fornecido", "whois": "Exibe informação sobre um usuário" }, diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index 108d2e0cd8..44eeefdf92 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -11,6 +11,7 @@ "other": "%(count)s непрочитанных сообщения(-й), включая упоминания.", "one": "1 непрочитанное упоминание." }, + "recent_rooms": "Недавние комнаты", "room_name": "Комната %(name)s", "room_status_bar": "Строка состояния комнаты", "seek_bar_label": "Панель поиска аудио", @@ -44,6 +45,8 @@ "create_a_room": "Создать комнату", "create_account": "Создать учётную запись", "decline": "Отклонить", + "decline_and_block": "Отклонить и заблокировать", + "decline_invite": "Отклонить приглашение", "delete": "Удалить", "deny": "Запретить", "disable": "Отключить", @@ -102,6 +105,7 @@ "reply": "Ответить", "reply_in_thread": "Обсудить", "report_content": "Пожаловаться на сообщение", + "report_room": "Комната отчетов", "resend": "Переотправить", "reset": "Сброс", "resume": "Возобновить", @@ -143,6 +147,7 @@ "view_message": "Посмотреть сообщение", "view_source": "Исходный код", "yes": "Да", + "yes_dismiss": "Да, отклонить", "zoom_in": "Увеличить", "zoom_out": "Уменьшить" }, @@ -257,6 +262,7 @@ "error_expired": "Срок действия входа истек. Пожалуйста, попробуйте еще раз.", "error_expired_title": "Вход в систему не был завершён вовремя", "error_insecure_channel_detected": "Не удалось установить безопасное соединение с новым устройством. Существующие устройства по-прежнему в безопасности, и вам не нужно беспокоиться о них.", + "error_insecure_channel_detected_instructions": "Что дальше?", "error_insecure_channel_detected_instructions_1": "Попробуйте снова войти на другое устройство с помощью QR-кода, если возникла проблема с сетью", "error_insecure_channel_detected_instructions_2": "Если вы столкнулись с аналогичной проблемой, попробуйте переподключиться к сети Wi-Fi или используйте мобильный интернет", "error_insecure_channel_detected_instructions_3": "Если это не помогло, войдите вручную.", @@ -371,6 +377,7 @@ "fallback_button": "Начать аутентификацию", "mas_cross_signing_reset_cta": "Перейти к учетной записи", "mas_cross_signing_reset_description": "Сбросьте свои данные через поставщика учетной записи, а затем вернитесь и нажмите «Повторить».", + "mas_cross_signing_reset_title": "Перейдите в свою учетную запись, чтобы сбросить свою личность", "msisdn": "Текстовое сообщение отправлено на %(msisdn)s", "msisdn_token_incorrect": "Неверный код проверки", "msisdn_token_prompt": "Введите полученный код:", @@ -507,8 +514,10 @@ "matrix": "Matrix", "message": "Отправить личное сообщение", "message_layout": "Макет сообщения", + "message_timestamp_invalid": "Недопустимая метка времени", "microphone": "Микрофон", "model": "Модель", + "moderation_and_safety": "Модерация и безопасность", "modern": "Современный", "mute": "Приглушить", "n_members": { @@ -731,6 +740,12 @@ "twemoji": "Изображение смайликов Twemoji © Twitter, Inc и другие авторы используются в соответствии с условиями CC-BY 4.0.", "twemoji_colr": "twemoji-colr шрифт от автора © Mozilla Foundation используется на условиях Apache 2.0." }, + "decline_invitation_dialog": { + "confirm": "Вы действительно хотите отклонить приглашение присоединиться \"%(roomName)s\"?", + "reason_description": "Опишите причину сообщения о проблеме.", + "report_room_description": "Сообщите об этой комнате своему поставщику учетной записи.", + "title": "Отклонить приглашение" + }, "desktop_default_device_name": "%(brand)s Рабочий стол: %(platformName)s", "devtools": { "active_widgets": "Активные виджеты", @@ -741,14 +756,20 @@ "crypto": { "4s_public_key_in_account_data": "в данных учётной записи", "4s_public_key_not_in_account_data": "не найдено", + "4s_public_key_status": "Публичный ключ секретного хранилища:", + "backup_key_cached": "сохранено локально", "backup_key_cached_status": "Кэшированный резервный ключ:", + "backup_key_not_stored": "не сохранено", + "backup_key_stored": "в секретном хранилище", "backup_key_stored_status": "Сохраненный резервный ключ:", "backup_key_well_formed": "корректный", "cross_signing": "Кросс-подпись", "cross_signing_cached": "сохранено локально", "cross_signing_not_ready": "Кросс-подпись не настроена.", + "cross_signing_private_keys_in_storage": "в секретном хранилище", "cross_signing_private_keys_in_storage_status": "Приватные ключи для кросс-подписи:", "cross_signing_private_keys_not_in_storage": "не найдено в хранилище", + "cross_signing_public_keys_on_device": "в памяти", "cross_signing_public_keys_on_device_status": "Публичные ключи для кросс-подписи:", "cross_signing_ready": "Кросс-подпись готова к использованию.", "cross_signing_status": "Статус кросс-подписи:", @@ -757,10 +778,17 @@ "key_backup_active_version": "Активная резервная версия:", "key_backup_active_version_none": "Нет", "key_backup_inactive_warning": "Резервное копирование ваших ключей из этого сеанса не выполняется.", + "key_backup_latest_version": "Последняя версия резервной копии на сервере:", "key_storage": "Хранилище ключей", "master_private_key_cached_status": "Приватный мастер-ключ:", + "not_found": "не найдено", + "not_found_locally": "не найдено локально", + "secret_storage_not_ready": "не готов", "secret_storage_ready": "готово", - "title": "Сквозное шифрование" + "secret_storage_status": "Секретное хранилище:", + "self_signing_private_key_cached_status": "Самоподписанный закрытый ключ:", + "title": "Сквозное шифрование", + "user_signing_private_key_cached_status": "Закрытый ключ подписи пользователей:" }, "developer_mode": "Режим разработчика", "developer_tools": "Инструменты разработчика", @@ -801,9 +829,9 @@ "room_notifications_type": "Тип: ", "room_status": "Статус комнаты", "room_unread_status_count": { - "one": "Статус непрочитанной комнаты: %(status)s%(status)s, количество: %(count)s", - "few": "Статус непрочитанных комнат: %(status)s%(status)s, количество: %(count)s", - "many": "Статус непрочитанных комнат: %(status)s%(status)s, количество: %(count)s" + "one": "Статус непрочитанной комнаты: %(status)s, количество: %(count)s", + "few": "Статус непрочитанных комнат: %(status)s, количество: %(count)s", + "many": "Статус непрочитанных комнат: %(status)s, количество: %(count)s" }, "save_setting_values": "Сохранить значения настроек", "see_history": "Посмотреть историю", @@ -818,6 +846,9 @@ "setting_colon": "Настройки:", "setting_definition": "Установка определения:", "setting_id": "ID настроек", + "settings": { + "elementCallUrl": "URL-адрес Element Call" + }, "settings_explorer": "Посмотреть настройки", "show_hidden_events": "Показывать скрытые события в ленте сообщений", "spaces": { @@ -827,7 +858,7 @@ }, "state_key": "Ключ состояния", "thread_root_id": "Идентификатор Root ID: %(threadRootId)s", - "threads_timeline": "Хронология тем", + "threads_timeline": "Хронология обсуждений", "title": "Инструменты разработчика", "toggle_event": "переключить событие", "toolbox": "Панель инструментов", @@ -871,15 +902,15 @@ "empty_room_was_name": "Пустая комната (без %(oldName)s)", "encryption": { "access_secret_storage_dialog": { + "alternatives": "Если у вас есть ключ безопасности или защитная фраза, это тоже подойдет.", "key_validation_text": { - "wrong_security_key": "Неправильный ключ восстановления" + "wrong_security_key": "Введенный ключ восстановления неверен." }, + "privacy_warning": "Убедитесь, что никто не видит этот экран!", "restoring": "Восстановление ключей из резервной копии", "security_key_title": "Ключ восстановления" }, "bootstrap_title": "Настройка ключей", - "cancel_entering_passphrase_description": "Вы уверены, что хотите отменить ввод кодовой фразы?", - "cancel_entering_passphrase_title": "Отменить ввод кодовой фразы?", "confirm_encryption_setup_body": "Нажмите кнопку ниже, чтобы подтвердить настройку шифрования.", "confirm_encryption_setup_title": "Подтвердите настройку шифрования", "cross_signing_room_normal": "Эта комната зашифрована сквозным шифрованием", @@ -914,6 +945,8 @@ "title": "Новый метод восстановления", "warning": "Если вы не задали новый способ восстановления, злоумышленник может получить доступ к вашей учётной записи. Смените пароль учётной записи и сразу же задайте новый способ восстановления в настройках." }, + "pinned_identity_changed": "Идентичность %(displayName)s (%(userId)s), похоже, изменилась. Узнать больше", + "pinned_identity_changed_no_displayname": "Похоже, что личность %(userId)s изменилась. Узнайте больше", "recovery_method_removed": { "description_1": "Этот сеанс обнаружил, что ваши секретная фраза и ключ безопасности для защищенных сообщений были удалены.", "description_2": "Если вы сделали это по ошибке, вы можете настроить защищённые сообщения в этом сеансе, что снова зашифрует историю сообщений в этом сеансе с помощью нового метода восстановления.", @@ -923,6 +956,7 @@ "reset_all_button": "Забыли или потеряли все варианты восстановления? Сбросить всё", "set_up_recovery": "Настроить восстановление", "set_up_recovery_later": "Не сейчас", + "set_up_recovery_toast_description": "Создайте ключ восстановления, который можно использовать для восстановления зашифрованной истории сообщений в случае потери доступа к своим устройствам.", "set_up_toast_description": "Защита от потери доступа к зашифрованным сообщениям и данным", "set_up_toast_title": "Настроить безопасное резервное копирование", "setup_secure_backup": { @@ -962,7 +996,7 @@ "incoming_sas_dialog_waiting": "Ожидаем подтверждения от партнера…", "incoming_sas_user_dialog_text_1": "Проверить этого пользователя, чтобы отметить его, как доверенного. Доверенные пользователи дают вам больше уверенности при использовании шифрованных сообщений.", "incoming_sas_user_dialog_text_2": "Подтверждение этого пользователя сделает его сеанс доверенным у вас, а также сделает ваш сеанс доверенным у него.", - "no_key_or_device": "Похоже, у вас нет бумажного ключа, или других сеансов, с которыми вы могли бы свериться. В этом сеансе вы не сможете получить доступ к старым зашифрованным сообщениям. Чтобы подтвердить свою личность в этом сеансе, вам нужно будет сбросить свои ключи шифрования.", + "no_key_or_device": "Похоже, у вас нет Ключа Восстановления, или других сеансов, с которыми вы могли бы свериться. В этом сеансе вы не сможете получить доступ к старым зашифрованным сообщениям. Чтобы подтвердить свою личность в этом сеансе, вам нужно будет сбросить свои ключи шифрования.", "no_support_qr_emoji": "Устройство, которое вы пытаетесь проверить, не поддерживает сканирование QR-кода или проверку смайликов, которые поддерживает %(brand)s. Попробуйте использовать другой клиент.", "other_party_cancelled": "Другая сторона отменила проверку.", "prompt_encrypted": "Подтвердите всех пользователей в комнате, чтобы обеспечить безопасность.", @@ -1012,8 +1046,8 @@ "verify_emoji_prompt_qr": "Если вы не можете отсканировать код выше, попробуйте сравнить уникальные смайлы.", "verify_later": "Я заверю позже", "verify_using_device": "Сверить с другим сеансом", - "verify_using_key": "Заверить бумажным ключом", - "verify_using_key_or_phrase": "Проверка с помощью ключа безопасности или фразы", + "verify_using_key": "Подтвердить Ключом Восстановления", + "verify_using_key_or_phrase": "Проверка с помощью Ключа или Фразы Восстановления", "waiting_for_user_accept": "Ожидание принятия от %(displayName)s…", "waiting_other_device": "Ожидает проверки на другом устройстве…", "waiting_other_device_details": "Ожидает проверки на другом устройстве, %(deviceName)s (%(deviceId)s)…", @@ -1064,6 +1098,10 @@ }, "error_app_open_in_another_tab": "%(brand)s был открыт в другой вкладке.", "error_app_opened_in_another_window": "%(brand)s открыт в другом окне. Нажмите \"%(label)s\" чтобы использовать %(brand)s в данном окне и отключить другое.", + "error_database_closed_description": { + "for_desktop": "Возможно, ваш диск переполнен. Освободите место и перезагрузите компьютер.", + "for_web": "Если вы очистили данные браузера, то это сообщение ожидаемо. %(brand)s также может быть открыт в другой вкладке, или ваш диск заполнен. Пожалуйста, освободите место и перезагрузите" + }, "error_database_closed_title": "База данных неожиданно закрылась", "error_dialog": { "copy_room_link_failed": { @@ -1098,7 +1136,11 @@ "m.sticker": "%(senderName)s: %(stickerName)s", "m.text": "%(senderName)s: %(message)s", "prefix": { - "poll": "Опрос" + "audio": "Аудио", + "file": "Файл", + "image": "Изображение", + "poll": "Опрос", + "video": "Видео" } }, "export_chat": { @@ -1170,7 +1212,7 @@ "platform_username": "Ваша платформа и имя пользователя будут отмечены, чтобы мы могли максимально использовать ваш отзыв.", "pro_type": "СОВЕТ ДЛЯ ПРОФЕССИОНАЛОВ: если вы запустите ошибку, отправьте журналы отладки, чтобы помочь нам отследить проблему.", "send_feedback_action": "Отправить отзыв", - "sent": "Отзыв отправлен" + "sent": "Отзыв отправлен! Спасибо, мы очень благодарны!" }, "file_panel": { "empty_description": "Прикрепите файлы из чата или просто перетащите их в комнату.", @@ -1191,6 +1233,7 @@ "change": "Изменить сервер идентификации", "change_prompt": "Отключиться от сервера идентификации и вместо этого подключиться к ?", "change_server_prompt": "Если вы не хотите использовать для обнаружения вас и быть обнаруженным вашими существующими контактами, введите другой идентификационный сервер ниже.", + "changed": "Ваш сервер идентификации изменен", "checking": "Проверка сервера", "description_connected": "В настоящее время вы используете для поиска вами ваших контактов а также вас вашими оппонентами. Вы можете изменить ваш сервер идентификации ниже.", "description_disconnected": "Вы в настоящее время не используете сервер идентификации. Чтобы найти известные вам контакты, и чтобы они могли найти вас, укажите сервер ниже.", @@ -1222,6 +1265,7 @@ "other": "В %(spaceName)s и %(count)s других пространствах." }, "incompatible_browser": { + "continue": "Продолжить в любом случае", "detail_no_continue": "Попробуйте обновить этот браузер, если вы используете не последнюю версию, и повторите попытку.", "learn_more": "Подробнее", "linux": "Linux", @@ -1356,12 +1400,14 @@ "navigate_next_message_edit": "Перейдите к следующему сообщению для редактирования", "navigate_prev_history": "Предыдущая недавно посещенная комната или пространство", "navigate_prev_message_edit": "Перейдите к предыдущему сообщению для редактирования", + "next_landmark": "Перейти к следующему ориентиру", "next_room": "Следующая команата или ЛС", "next_unread_room": "Следующая непрочитанная комната или ЛС", "number": "[количество]", "open_user_settings": "Открыть пользовательские настройки", "page_down": "Page Down", "page_up": "Page Up", + "prev_landmark": "Перейти к предыдущему ориентиру", "prev_room": "Предыдущая комната или ЛС", "prev_unread_room": "Предыдущая непрочитанная комната или ЛС", "room_list_collapse_section": "Свернуть секцию списка комнат", @@ -1372,7 +1418,7 @@ "scroll_down_timeline": "Листать ленту сообщений вниз", "scroll_up_timeline": "Листать временную шкалу вверх", "search": "Поиск (должен быть включен)", - "send_sticker": "Отправить наклейку", + "send_sticker": "Отправить стикер", "shift": "Shift", "space": "Подпространство", "switch_to_space": "Перейти к пространству по номеру", @@ -1406,8 +1452,12 @@ "dynamic_room_predecessors": "Родитель динамической комнаты", "dynamic_room_predecessors_description": "Включите MSC3946 (для поддержки архивных комнат)", "element_call_video_rooms": "Видеокомнаты Element Call", + "exclude_insecure_devices": "Исключить небезопасные устройства при отправке/получении сообщений", + "exclude_insecure_devices_description": "Когда этот режим включен, зашифрованные сообщения не будут передаваться непроверенным устройствам, а сообщения с непроверенных устройств будут отображаться как ошибка. Обратите внимание, что если вы включите этот режим, возможно, вы не сможете общаться с пользователями, которые не подтвердили свои устройства.", "experimental_description": "Время экспериментов? Попробуйте наши последние наработки. Эти функции не заверешены; они могут быть нестабильными, постоянно меняющимися, или вовсе отброшенными. Узнайте больше.", "experimental_section": "Предпросмотр", + "extended_profiles_msc_support": "Требуется, чтобы ваш сервер поддерживал MSC4133", + "feature_disable_call_per_sender_encryption": "Отключить шифрование для каждого отправителя Element Call", "feature_wysiwyg_composer_description": "Используйте форматированный текст вместо Markdown в редакторе сообщений.", "group_calls": "Новый опыт группового вызова", "group_developer": "Разработка", @@ -1419,6 +1469,7 @@ "group_rooms": "Комнаты", "group_spaces": "Пространства", "group_themes": "Темы", + "group_threads": "Обсуждения", "group_ui": "Пользовательский интерфейс", "group_voip": "Голос и видео", "group_widgets": "Виджеты", @@ -1440,6 +1491,7 @@ "notification_settings_beta_caption": "Представляем вам более простой способ изменения настроек уведомлений. Настройте свои настройки так %(brand)s, как вам удобно.", "notification_settings_beta_title": "Настройки уведомлений", "notifications": "Включить панель уведомлений в заголовке комнаты", + "release_announcement": "Объявление о новой версии", "render_reaction_images": "Обработка пользовательских изображений в реакциях", "render_reaction_images_description": "Иногда их называют \"пользовательскими эмодзи\".", "report_to_moderators": "Пожаловаться модераторам", @@ -1447,7 +1499,7 @@ "sliding_sync": "Режим Sliding Sync", "sliding_sync_description": "В активной разработке, нельзя отключить.", "sliding_sync_disabled_notice": "Выйдите из системы и снова войдите, чтобы отключить", - "sliding_sync_server_no_support": "На вашем сервере отсутствует встроенная поддержка", + "sliding_sync_server_no_support": "На вашем сервере отсутствует поддержка", "under_active_development": "В активной разработке.", "unrealiable_e2e": "Ненадежно в зашифрованных комнатах", "video_rooms": "Видеокомнаты", @@ -1499,6 +1551,8 @@ "last_person_warning": "Вы здесь единственный человек. Если вы уйдете, никто не сможет присоединиться в будущем, включая вас.", "leave_room_question": "Уверены, что хотите покинуть '%(roomName)s'?", "leave_space_question": "Уверены, что хотите покинуть пространство \"%(spaceName)s\"?", + "room_leave_admin_warning": "Вы единственный администратор в данной комнате. Если вы покинете ее, никто не сможет изменить настройки комнаты или выполнить другие важные действия.", + "room_leave_mod_warning": "Вы единственный модератор в этой комнате. Если вы уйдете, никто не сможет изменить настройки комнаты или выполнить другие важные действия.", "room_rejoin_warning": "Эта комната не является публичной. Вы не сможете войти без приглашения.", "space_rejoin_warning": "Это пространство не публично. Вы не сможете вновь войти без приглашения." }, @@ -1563,10 +1617,13 @@ }, "filter_placeholder": "Поиск по участникам", "invite_button_no_perms_tooltip": "У вас нет разрешения приглашать пользователей", + "invited_label": "Приглашены", + "no_matches": "Нет совпадений", "power_label": "%(userName)s (уровень прав %(powerLevelNumber)s)" }, "member_list_back_action_label": "Участники комнаты", "message_edit_dialog_title": "Правки сообщения", + "migrating_crypto": "Наберитесь терпения. Мы обновляем %(brand)s, чтобы сделать шифрование быстрые и надежнее.", "mobile_guide": { "toast_accept": "Использовать приложение", "toast_description": "%(brand)s работает в экспериментальном режиме в мобильном браузере. Для лучших впечатлений и новейших функций используйте наше родное бесплатное приложение.", @@ -1592,6 +1649,8 @@ "keyword": "Ключевое слово", "keyword_new": "Новое ключевое слово", "level_activity": "Активность", + "level_highlight": "Выделение", + "level_muted": "Звук отключен", "level_none": "Пусто", "level_notification": "Уведомление", "level_unsent": "Не отправлено", @@ -1771,6 +1830,7 @@ "description": "Все прикрепленные сообщения можно найти здесь. Наведите курсор на любое сообщение и нажмите «Закрепить», чтобы добавить его.", "title": "Все новые закрепленные сообщения" }, + "reply_thread": "Ответ на сообщение в обсуждениях", "unpin_all": { "button": "Открепить все сообщения", "content": "Убедитесь, что вы действительно хотите удалить все прикреплённые сообщения. Это действие нельзя отменить.", @@ -1869,7 +1929,8 @@ }, "face_pile_tooltip_label": { "one": "Посмотреть 1 участника", - "other": "Просмотреть всех %(count)s участников" + "few": "Посмотреть %(count)s участника", + "many": "Посмотреть %(count)s участников" }, "face_pile_tooltip_shortcut": "Включая %(commaSeparatedMembers)s", "face_pile_tooltip_shortcut_joined": "Включая вас, %(commaSeparatedMembers)s", @@ -2017,7 +2078,9 @@ "no_favourites": "У вас пока нет чатов в Избранное", "no_favourites_description": "Вы можете добавить в Избранное в настройках чата", "no_people": "У вас пока нет личных чатов", + "no_people_description": "Вы можете убрать фильтры, чтобы увидеть другие ваши чаты", "no_rooms": "Вы еще не находитесь ни в одной комнате", + "no_rooms_description": "Вы можете убрать фильтры, чтобы увидеть другие ваши чаты", "no_unread": "Поздравляю! У вас нет непрочитанных сообщений", "show_chats": "Показать все чаты" }, @@ -2039,12 +2102,14 @@ "list_title": "Список комнат", "more_options": { "copy_link": "Скопировать ссылку на комнату", + "favourited": "Избранное", "leave_room": "Покинуть комнату", "low_priority": "Низкий приоритет", "mark_read": "Отметить как прочитанное", "mark_unread": "Отметить как непрочитанное" }, "notification_options": "Настройки уведомлений", + "primary_filters": "Фильтры комнат", "redacting_messages_status": { "one": "Удаляются сообщения в %(count)s комнате", "other": "Удаляются сообщения в %(count)s комнатах" @@ -2404,6 +2469,9 @@ "code_block_expand_default": "По умолчанию отображать блоки кода целиком", "code_block_line_numbers": "Показывать номера строк в блоках кода", "disable_historical_profile": "Показать текущее изображение профиля и имя пользователя в истории сообщений", + "discovery": { + "title": "Как вас найти" + }, "emoji_autocomplete": "Предлагать смайлики при наборе", "enable_markdown": "Использовать Markdown", "enable_markdown_description": "Начинайте сообщения с /plain, чтобы отправлять их без markdown.", @@ -2434,6 +2502,7 @@ "confirm": "Удалить хранилище ключей", "description": "Удаление хранилища ключей приведёт к удалению вашей идентификации и ключей сообщений с сервера, а также отключению следующих функций безопасности:", "list_first": "Нет зашифрованной истории сообщений на новых устройствах", + "list_second": "Вы потеряете доступ к своим зашифрованным сообщениям, если вы выйдете из %(brand)s везде", "title": "Вы уверены, что хотите отключить хранение ключей и удалить их?" }, "device_not_verified_button": "Проверить это устройство", @@ -2453,8 +2522,15 @@ "enter_recovery_key": "Введите ключ восстановления", "forgot_recovery_key": "Забыли ключ восстановления?", "save_key_description": "Не сообщайте эту информацию никому!", - "save_key_title": "Ключ восстановления" - } + "save_key_title": "Ключ восстановления", + "set_up_recovery": "Настройка восстановления", + "set_up_recovery_confirm_button": "Завершить настройку", + "set_up_recovery_confirm_title": "Для подтверждения введите ключ восстановления", + "set_up_recovery_save_key_description": "Запишите ключ восстановления в безопасном месте, например в диспетчере паролей, зашифрованной заметке или физическом сейфе.", + "set_up_recovery_save_key_title": "Сохраните ключ восстановления в безопасном месте", + "title": "Восстановление" + }, + "title": "Шифрование" }, "general": { "account_management_section": "Управление учётной записью", @@ -2468,6 +2544,14 @@ "add_msisdn_dialog_title": "Добавить номер телефона", "add_msisdn_instructions": "Текстовое сообщение было отправлено +%(msisdn)s. Пожалуйста, введите проверочный код, который он содержит.", "add_msisdn_misconfigured": "Поток add/bind с MSISDN настроен неправильно", + "allow_spellcheck": "Разрешить проверку орфографии", + "application_language": "Язык приложения", + "application_language_reload_hint": "Приложение перезагрузится после выбора другого языка", + "avatar_remove_progress": "Удаление изображения…", + "avatar_save_progress": "Загрузка изображения...", + "avatar_upload_error_text": "Формат файла не поддерживается или размер изображения превышает %(size)s.", + "avatar_upload_error_text_generic": "Формат файла может не поддерживаться.", + "avatar_upload_error_title": "Не удалось загрузить изображение профиля", "confirm_adding_email_body": "Нажмите кнопку ниже для подтверждения этого почтового адреса.", "confirm_adding_email_title": "Подтвердите добавление почтового адреса", "deactivate_confirm_body": "Вы уверены, что хотите деактивировать свою учётную запись? Это необратимое действие.", @@ -2483,10 +2567,13 @@ "deactivate_confirm_erase_label": "Скрыть мои сообщения от новых участников", "deactivate_section": "Деактивировать учётную запись", "deactivate_warning": "Деактивация вашей учётной записи является необратимым действием — будьте осторожны!", - "discovery_email_empty": "Параметры поиска по электронной почты появятся после добавления её выше.", + "discovery_email_empty": "Параметры поиска по электронной почты появятся после её добавления.", "discovery_email_verification_instructions": "Проверьте ссылку в вашем почтовом ящике(папка \"Входящие\")", "discovery_msisdn_empty": "Параметры поиска по номеру телефона появятся после его добавления.", "discovery_needs_terms": "Подтвердите условия предоставления услуг сервера идентификации (%(serverName)s), чтобы вас можно было обнаружить по адресу электронной почты или номеру телефона.", + "discovery_needs_terms_title": "Позвольте людям найти вас", + "display_name": "Отображаемое имя", + "display_name_error": "Невозможно установить отображаемое имя", "email_address_in_use": "Этот адрес электронной почты уже используется", "email_address_label": "Адрес электронной почты", "email_not_verified": "Ваш адрес электронной почты еще не проверен", @@ -2511,7 +2598,7 @@ "error_share_msisdn_discovery": "Не удается предоставить общий доступ к номеру телефона", "identity_server_no_token": "Не найден токен доступа для идентификации", "identity_server_not_set": "Сервер идентификации не установлен", - "language_section": "Язык и регион", + "language_section": "Язык", "msisdn_in_use": "Этот номер телефона уже используется", "msisdn_label": "Номер телефона", "msisdn_verification_field_label": "Код подтверждения", @@ -2520,9 +2607,14 @@ "oidc_manage_button": "Настройки аккаунта", "password_change_section": "Установите новый пароль…", "password_change_success": "Ваш пароль успешно изменён.", + "personal_info": "Личная информация", + "profile_subtitle": "Так вас видят другие пользователи приложения.", "remove_email_prompt": "Удалить %(email)s?", "remove_msisdn_prompt": "Удалить %(phone)s?", - "spell_check_locale_placeholder": "Выберите регион" + "spell_check_locale_placeholder": "Выберите регион", + "unable_to_load_emails": "Не удалось загрузить адреса электронной почты", + "unable_to_load_msisdns": "Не удалось загрузить номера телефонов", + "username": "Имя пользователя" }, "inline_url_previews_default": "Предпросмотр ссылок по умолчанию", "inline_url_previews_room": "Включить предпросмотр ссылок для участников этой комнаты по умолчанию", @@ -2545,13 +2637,13 @@ "enter_phrase_description": "Введите секретную фразу, известную только вам, так как она используется для защиты ваших данных. В целях безопасности не следует повторно использовать пароль своей учетной записи.", "enter_phrase_title": "Введите секретную фразу", "enter_phrase_to_confirm": "Введите секретную фразу второй раз, чтобы подтвердить ее.", - "generate_security_key_description": "Мы создадим ключ безопасности для вас, чтобы вы могли хранить его в надежном месте, например, в менеджере паролей или сейфе.", + "generate_security_key_description": "Мы создадим ключ восстановления, который вы сможете хранить в безопасном месте, например в менеджере паролей или сейфе.", "generate_security_key_title": "Создание ключа безопасности", "pass_phrase_match_failed": "Они не совпадают.", "pass_phrase_match_success": "Они совпадают!", "phrase_strong_enough": "Отлично! Эта контрольная фраза выглядит достаточно сильной.", "secret_storage_query_failure": "Невозможно запросить состояние секретного хранилища", - "security_key_safety_reminder": "Храните ключ безопасности в надежном месте, например в менеджере паролей или сейфе, так как он используется для защиты ваших зашифрованных данных.", + "security_key_safety_reminder": "Храните Ключ Восстановления в надежном месте, например в менеджере паролей или сейфе, так как он используется для защиты ваших зашифрованных данных.", "set_phrase_again": "Задать другой пароль.", "settings_reminder": "Вы также можете настроить безопасное резервное копирование и управлять своими ключами в настройках.", "title_confirm_phrase": "Подтвердите секретную фразу", @@ -2577,12 +2669,20 @@ "phrase_strong_enough": "Отлично! Эта парольная фраза выглядит достаточно надежной" }, "keyboard": { + "dialog_title": "Настройки: Клавиатура", "title": "Горячие клавиши" }, + "labs": { + "dialog_title": "Настройки: Лаборатория" + }, + "labs_mjolnir": { + "dialog_title": "Настройки: Игнорируемые пользователи" + }, "notifications": { "default_setting_description": "Эта настройка будет применена по умолчанию ко всем вашим комнатам.", "default_setting_section": "Я хочу получать уведомления о (настройка по умолчанию)", "desktop_notification_message_preview": "Показать предварительный просмотр сообщения в уведомлении на рабочем столе", + "dialog_title": "Настройки: Уведомления", "email_description": "Получать по электронной почте сводку пропущенных уведомлений", "email_section": "Информация об электронной почте", "email_select": "Выберите, на какие электронные письма вы хотите отправлять резюме. Управляйте электронной почтой в .", @@ -2627,7 +2727,7 @@ "rule_encrypted_room_one_to_one": "Зашифрованные сообщения в персональных чатах", "rule_invite_for_me": "Приглашения в комнаты", "rule_message": "Сообщения в конференциях", - "rule_room_one_to_one": "Сообщения в 1:1 чатах", + "rule_room_one_to_one": "Сообщения в личных чатах", "rule_roomnotif": "Сообщения, содержащие @room", "rule_suppress_notices": "Сообщения от ботов", "rule_tombstone": "При обновлении комнат", @@ -2641,12 +2741,14 @@ "code_blocks_heading": "Блоки кода", "compact_modern": "Использовать более компактный \"Современный\" макет", "composer_heading": "Редактор", + "dialog_title": "Настройки: Параметры", "enable_hardware_acceleration": "Включить аппаратное ускорение", "enable_tray_icon": "Показывать значок в трее и сворачивать в него окно при закрытии", "keyboard_heading": "Горячие клавиши", "keyboard_view_shortcuts_button": "Чтобы просмотреть все сочетания клавиш, нажмите здесь.", "media_heading": "Медиа", "presence_description": "Поделитесь своей активностью и статусом с другими.", + "publish_timezone": "Опубликовать часовой пояс в профиле", "rm_lifetime": "Задержка прочтения сообщения (мс)", "rm_lifetime_offscreen": "Задержка прочтения сообщения при отсутствии активности (мс)", "room_directory_heading": "Каталог комнат", @@ -2654,7 +2756,8 @@ "show_avatars_pills": "Показывать аватары в упоминаниях пользователей, комнатах и событиях", "show_polls_button": "Показывать кнопку опроса", "surround_text": "Обводить выделенный текст при вводе специальных символов", - "time_heading": "Отображение времени" + "time_heading": "Отображение времени", + "user_timezone": "Установить часовой пояс" }, "prompt_invite": "Подтверждать отправку приглашений на потенциально недействительные matrix ID", "replace_plain_emoji": "Автоматически заменять текстовые смайлики на графические", @@ -2663,6 +2766,8 @@ "bulk_options_accept_all_invites": "Принять все приглашения (%(invitedRooms)s)", "bulk_options_reject_all_invites": "Отклонить все %(invitedRooms)s приглашения", "bulk_options_section": "Основные опции", + "dehydrated_device_enabled": "Устройство в автономном режиме", + "dialog_title": "Настройки: Безопасность и конфиденциальность", "e2ee_default_disabled_warning": "Администратор вашего сервера отключил сквозное шифрование по умолчанию в приватных комнатах и диалогах.", "enable_message_search": "Включить поиск сообщений в зашифрованных комнатах", "encryption_section": "Шифрование", @@ -2725,6 +2830,7 @@ "device_unverified_description_current": "Заверьте текущий сеанс для усиления защиты переписки.", "device_verified_description": "Этот сеанс готов к безопасному обмену сообщениями.", "device_verified_description_current": "Ваш текущий сеанс готов к защищенной переписке.", + "dialog_title": "Настройки: Сеансы", "error_pusher_state": "Не удалось установить состояние push-службы", "error_set_name": "Не удалось установить имя сессии", "filter_all": "Все", @@ -2741,6 +2847,7 @@ "inactive_sessions_list_description": "Сочтите выйти из старых сеансов (%(inactiveAgeDays)s дней и более), которые вы более не используете.", "ip": "IP-адрес", "last_activity": "Последняя активность", + "manage": "Управление этим сеансом", "mobile_session": "Сеанс мобильного устройства", "n_sessions_selected": { "one": "%(count)s сеанс выбран", @@ -2766,8 +2873,9 @@ "session_id": "ID сеанса", "show_details": "Показать подробности", "sign_in_with_qr": "Привязать новое устройство", - "sign_in_with_qr_button": "Показать QR код", - "sign_in_with_qr_description": "Вы можете использовать это устройство для входа на новом устройство с помощью QR-кода. Вам необходимо отсканировать данный QR-код на новом устройстве.", + "sign_in_with_qr_button": "Показать QR-код", + "sign_in_with_qr_description": "Используйте QR-код для входа на другое устройство и настройки безопасного обмена сообщениями.", + "sign_in_with_qr_unsupported": "Не поддерживается вашим поставщиком учетных записей", "sign_out": "Выйти из этого сеанса", "sign_out_all_other_sessions": "Выйти из всех остальных сеансов (%(otherSessionsCount)s)", "sign_out_confirm_description": { @@ -2809,7 +2917,9 @@ "show_redaction_placeholder": "Плашки вместо удалённых сообщений", "show_stickers_button": "Показывать кнопку наклеек", "show_typing_notifications": "Уведомлять о наборе текста", + "showbold": "Показывать всю активность в списке комнат (точки или количество непрочитанных сообщений)", "sidebar": { + "dialog_title": "Настройки: Боковая панель", "metaspaces_favourites_description": "Сгруппируйте все свои любимые комнаты и людей в одном месте.", "metaspaces_home_all_rooms": "Показать все комнаты", "metaspaces_home_all_rooms_description": "Показать все комнаты на Главной, даже если они находятся в пространстве.", @@ -2818,10 +2928,14 @@ "metaspaces_orphans_description": "Сгруппируйте все комнаты, которые не являются частью пространства, в одном месте.", "metaspaces_people_description": "Сгруппируйте всех своих людей в одном месте.", "metaspaces_subsection": "Пространства для показа", + "metaspaces_video_rooms": "Видеокомнаты и конференции", + "metaspaces_video_rooms_description": "Сгруппировать все частные видеокомнаты и конференции.", + "metaspaces_video_rooms_description_invite_extension": "В конференции вы можете приглашать людей за пределами matrix.", "spaces_explainer": "Пространства — это способ сгруппировать комнаты и людей. Помимо пространств, в которых вы находитесь, вы также можете использовать готовые помещения.", "title": "Боковая панель" }, "start_automatically": "Автозапуск при входе в систему", + "tac_only_notifications": "Показывать уведомления только в центре активностей обсуждений", "use_12_hour_format": "Отображать время в 12 часовом формате (напр. 2:30pm)", "use_command_enter_send_message": "Cmd + Enter, чтобы отправить сообщение", "use_command_f_search": "Используйте Command + F для поиска в ленте сообщений", @@ -2835,6 +2949,7 @@ "audio_output_empty": "Аудиовыход не обнаружен", "auto_gain_control": "Авторегулировка усиления", "connection_section": "Соединение", + "dialog_title": "Настройки: Голос и видео", "echo_cancellation": "Эхоподавление", "enable_fallback_ice_server": "Разрешить резервный сервер помощи при вызове (%(server)s)", "enable_fallback_ice_server_description": "Только применяется, когда у домашнего сервера нет своего TURN-сервера. Ваш IP-адрес будет виден на время звонка.", @@ -2853,8 +2968,12 @@ "warning": "ВНИМАНИЕ: " }, "share": { + "link_copied": "Ссылка скопирована", "permalink_message": "Ссылка на выбранное сообщение", "permalink_most_recent": "Ссылка на последнее сообщение", + "share_call": "Ссылка-приглашение на конференцию", + "share_call_subtitle": "Ссылка для внешних пользователей, желающих присоединиться к вызову без учетной записи matrix:", + "title_link": "Поделиться ссылкой", "title_message": "Поделиться сообщением", "title_room": "Поделиться комнатой", "title_user": "Поделиться пользователем" @@ -2939,6 +3058,7 @@ "upgraderoom": "Обновляет комнату до новой версии", "upgraderoom_permission_error": "У вас нет необходимых разрешений для использования этой команды.", "usage": "Использование", + "verify": "Проверяет пользователя, сеанс и публичные ключи", "view": "Просмотр комнаты с указанным адресом", "whois": "Показать информацию о пользователе" }, @@ -3088,18 +3208,26 @@ "thread_view_back_action_label": "Вернуться к обсуждению", "threads": { "all_threads": "Все обсуждения", - "all_threads_description": "Показывает все обсуждения из текущей комнаты", + "all_threads_description": "Отобразит все обсуждения из текущей комнаты", "count_of_reply": { "one": "%(count)s ответ", "few": "%(count)s ответа", "many": "%(count)s ответов" }, + "empty_description": "Используйте “%(replyInThread)s” при наведении курсора на сообщение.", + "empty_title": "Обсуждения помогают поддерживать тему разговоров и их легко отслеживать.", "error_start_thread_existing_relation": "Невозможно создать обсуждение из события с существующей связью", + "mark_all_read": "Отметить все как прочитанные", "my_threads": "Мои обсуждения", - "my_threads_description": "Показывает все обсуждения, в которых вы принимали участие", - "open_thread": "Открыть ветку", + "my_threads_description": "Отобразит все обсуждения, в которых вы принимали участие", + "open_thread": "Открыть обсуждение", "show_thread_filter": "Показать:" }, + "threads_activity_centre": { + "header": "Активность обсуждений", + "no_rooms_with_threads_notifs": "У вас пока нет комнат с уведомлениями в обсуждениях.", + "no_rooms_with_unread_threads": "У вас пока нет комнат с непрочитанными обсуждениями." + }, "time": { "about_day_ago": "около суток назад", "about_hour_ago": "около часа назад", @@ -3141,9 +3269,19 @@ }, "creation_summary_dm": "%(creator)s начал(а) этот чат.", "creation_summary_room": "%(creator)s создал(а) и настроил(а) комнату.", + "decryption_failure": { + "blocked": "Отправитель заблокировал получение этого сообщения, поскольку ваше устройство не проверено.", + "historical_event_no_key_backup": "История сообщений недоступна на этом устройстве", + "historical_event_unverified_device": "Вам необходимо подтвердить это устройство для доступа к истории сообщений.", + "historical_event_user_not_joined": "У вас нет доступа к этому сообщению", + "sender_identity_previously_verified": "Подтвержденная личность изменилась", + "unable_to_decrypt": "Не удалось расшифровать сообщение" + }, "disambiguated_profile": "%(displayName)s (%(matrixId)s)", "download_action_decrypting": "Расшифровка", "download_action_downloading": "Загрузка", + "download_failed": "Загрузка не удалась", + "e2e_state": "Состояние сквозного шифрования", "edits": { "tooltip_label": "Изменено %(date)s. Нажмите для посмотра истории изменений.", "tooltip_sub": "Нажмите для просмотра правок", @@ -3197,7 +3335,7 @@ }, "m.file": { "error_decrypting": "Ошибка расшифровки вложения", - "error_invalid": "Недопустимый файл%(extra)s" + "error_invalid": "Недопустимый формат файла" }, "m.image": { "error": "Невозможно показать изображение из-за ошибки", diff --git a/src/i18n/strings/sk.json b/src/i18n/strings/sk.json index 07e7ffeeb1..08fc2c7d5f 100644 --- a/src/i18n/strings/sk.json +++ b/src/i18n/strings/sk.json @@ -929,8 +929,6 @@ "security_key_title": "Kľúč na obnovenie" }, "bootstrap_title": "Príprava kľúčov", - "cancel_entering_passphrase_description": "Naozaj chcete zrušiť zadávanie prístupovej frázy?", - "cancel_entering_passphrase_title": "Zrušiť zadanie prístupovej frázy?", "confirm_encryption_setup_body": "Kliknutím na tlačidlo nižšie potvrdíte nastavenie šifrovania.", "confirm_encryption_setup_title": "Potvrdiť nastavenie šifrovania", "cross_signing_room_normal": "Táto miestnosť je end-to-end šifrovaná", @@ -2124,7 +2122,6 @@ "room_list": { "add_room_label": "Pridať miestnosť", "add_space_label": "Pridať priestor", - "appearance": "Vzhľad", "breadcrumbs_empty": "Žiadne nedávno navštívené miestnosti", "breadcrumbs_label": "Nedávno navštívené miestnosti", "empty": { @@ -2149,6 +2146,7 @@ "filters": { "favourite": "Obľúbené", "invites": "Pozvánky", + "low_priority": "Nízka priorita", "mentions": "Zmienky", "people": "Ľudia", "rooms": "Miestnosti", @@ -2184,7 +2182,6 @@ }, "room_options": "Možnosti miestnosti", "show_less": "Zobraziť menej", - "show_message_previews": "Zobraziť náhľady správ", "show_n_more": { "one": "Zobraziť %(count)s ďalšiu", "few": "Zobraziť %(count)s ďalšie", @@ -3167,6 +3164,7 @@ "upgraderoom": "Aktualizuje miestnosť na novšiu verziu", "upgraderoom_permission_error": "Na použitie tohoto príkazu nemáte dostatočné povolenia.", "usage": "Použitie", + "verify": "Overí používateľa, reláciu a verejné kľúče", "view": "Zobrazí miestnosti s danou adresou", "whois": "Zobrazuje informácie o používateľovi" }, diff --git a/src/i18n/strings/sq.json b/src/i18n/strings/sq.json index 15ea9a3df8..9637f7a820 100644 --- a/src/i18n/strings/sq.json +++ b/src/i18n/strings/sq.json @@ -762,8 +762,6 @@ "security_key_title": "Kyç Sigurie" }, "bootstrap_title": "Ujdisje kyçesh", - "cancel_entering_passphrase_description": "Jeni i sigurt se doni të anulohet dhënie frazëkalimi?", - "cancel_entering_passphrase_title": "Të anulohet dhënue frazëkalimi?", "confirm_encryption_setup_body": "Klikoni mbi butonin më poshtë që të ripohoni ujdisjen e fshehtëzimit.", "confirm_encryption_setup_title": "Ripohoni ujdisje fshehtëzimi", "cross_signing_room_normal": "Kjo dhomë është e fshehtëzuar skaj-më-skaj", @@ -2532,6 +2530,7 @@ "upgraderoom": "E kalon një dhomë te një version i ri i përmirësuar", "upgraderoom_permission_error": "S’keni lejet e domosdoshme për përdorimin e këtij urdhri.", "usage": "Përdorim", + "verify": "Verifikon një përdorues, sesion dhe një set kyçesh publikë", "whois": "Shfaq të dhëna rreth një përdoruesi" }, "space": { diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 1715f2276f..6dedd8e10a 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -916,8 +916,6 @@ "security_key_title": "Säkerhetsnyckel" }, "bootstrap_title": "Sätter upp nycklar", - "cancel_entering_passphrase_description": "Är du säker på att du vill avbryta inmatning av lösenfrasen?", - "cancel_entering_passphrase_title": "Avbryta inmatning av lösenfras?", "confirm_encryption_setup_body": "Klicka på knappen nedan för att bekräfta inställning av kryptering.", "confirm_encryption_setup_title": "Bekräfta krypteringsinställning", "cross_signing_room_normal": "Det här rummet är totalsträckskrypterat", @@ -2087,7 +2085,6 @@ "room_list": { "add_room_label": "Lägg till rum", "add_space_label": "Lägg till utrymme", - "appearance": "Utseende", "breadcrumbs_empty": "Inga nyligen besökta rum", "breadcrumbs_label": "Nyligen besökta rum", "empty": { @@ -2140,7 +2137,6 @@ }, "room_options": "Rumsalternativ", "show_less": "Visa mindre", - "show_message_previews": "Visa förhandsgranskningar av meddelanden", "show_n_more": { "other": "Visa %(count)s till", "one": "Visa %(count)s till" @@ -3102,6 +3098,7 @@ "upgraderoom": "Uppgraderar ett rum till en ny version", "upgraderoom_permission_error": "Du har inte de behörigheter som krävs för att använda det här kommandot.", "usage": "Användande", + "verify": "Verifierar en användar-, sessions- och pubkey-tupel", "view": "Visar rum med den angivna adressen", "whois": "Visar information om en användare" }, @@ -3778,10 +3775,10 @@ "unavailable": "Otillgänglig" }, "update_room_access_modal": { - "description": "För att skapa en delningslänk måste du tillåta gäster att gå med i det här rummet. Detta kan göra rummet mindre säkert. När du är klar med samtalet kan du göra rummet privat igen.", - "dont_change_description": "Alternativt kan du hålla samtalet i ett separat rum.", + "description": "För att skapa en delningslänk, gör det här rummet offentligt eller aktivera alternativet för användare att be om att få gå med. Detta gör att gäster kan gå med utan att bli inbjudna.", + "dont_change_description": "Om du inte vill ändra åtkomst till det här rummet kan du skapa ett nytt rum för samtalslänken.", "no_change": "Jag vill inte ändra åtkomstnivån.", - "title": "Ändra åtkomstnivå för rummet" + "title": "Tillåt gästanvändare att gå med i det här rummet" }, "upload_failed_generic": "Filen '%(fileName)s' kunde inte laddas upp.", "upload_failed_size": "Filen '%(fileName)s' överstiger denna hemserverns storleksgräns för uppladdningar", diff --git a/src/i18n/strings/tr.json b/src/i18n/strings/tr.json index 20cc904f17..264880a2d4 100644 --- a/src/i18n/strings/tr.json +++ b/src/i18n/strings/tr.json @@ -890,8 +890,6 @@ "security_key_title": "Güvenlik anahtarı" }, "bootstrap_title": "Anahtarları ayarla", - "cancel_entering_passphrase_description": "Parola girmeyi iptal etmek istediğinizden emin misiniz?", - "cancel_entering_passphrase_title": "Parola girişini iptal et?", "confirm_encryption_setup_body": "Şifreleme kurulumunu onaylamak için aşağıdaki düğmeye tıklayın.", "confirm_encryption_setup_title": "Şifreleme kurulumunu onayla", "cross_signing_room_normal": "Bu oda uçtan uça şifreli", @@ -3030,6 +3028,7 @@ "upgraderoom": "Bir odayı yeni bir versiyona yükseltir", "upgraderoom_permission_error": "Bu komutu kullanmak için gerekli izinlere sahip değilsin.", "usage": "Kullanım", + "verify": "Bir kullanıcı, oturum ve açık anahtar çiftini doğrular", "view": "Verilen adresli odayı görüntüle", "whois": "Bir kullanıcı hakkındaki bilgileri görüntüler" }, diff --git a/src/i18n/strings/uk.json b/src/i18n/strings/uk.json index 614cfca299..6be282da55 100644 --- a/src/i18n/strings/uk.json +++ b/src/i18n/strings/uk.json @@ -159,6 +159,7 @@ "view_message": "Переглянути повідомлення", "view_source": "Переглянути код", "yes": "Так", + "yes_dismiss": "Так, відхилити", "zoom_in": "Збільшити", "zoom_out": "Зменшити" }, @@ -913,15 +914,15 @@ "empty_room_was_name": "Порожня кімната (були %(oldName)s)", "encryption": { "access_secret_storage_dialog": { + "alternatives": "Якщо у вас є ключ безпеки або фраза безпеки, це теж спрацює.", "key_validation_text": { - "wrong_security_key": "Неправильний ключ відновлення" + "wrong_security_key": "Ви ввели некоректний ключ відновлення." }, + "privacy_warning": "Переконайтеся, що ніхто не бачить цей екран!", "restoring": "Відновлення ключів із резервної копії", "security_key_title": "Ключ відновлення" }, "bootstrap_title": "Налаштовування ключів", - "cancel_entering_passphrase_description": "Ви точно хочете скасувати введення парольної фрази?", - "cancel_entering_passphrase_title": "Скасувати введення парольної фрази?", "confirm_encryption_setup_body": "Клацніть на кнопку внизу, щоб підтвердити налаштування шифрування.", "confirm_encryption_setup_title": "Підтвердити налаштування шифрування", "cross_signing_room_normal": "Ця кімната є наскрізно зашифрованою", @@ -973,6 +974,8 @@ "setup_secure_backup": { "explainer": "Створіть резервну копію ключів перед виходом, щоб не втратити їх." }, + "turn_on_key_storage": "Увімкнути сховище ключів", + "turn_on_key_storage_description": "Зберігайте свій криптографічний ідентифікатор і ключі повідомлень на сервері. Це дозволить вам переглядати історію повідомлень на будь-яких нових пристроях. %1$s.", "udd": { "interactive_verification_button": "Звірити інтерактивно за допомогою емоджі", "other_ask_verify_text": "Попросіть цього користувача звірити сеанс, або звірте його власноруч унизу.", @@ -1956,6 +1959,7 @@ }, "face_pile_tooltip_shortcut": "Включно з %(commaSeparatedMembers)s", "face_pile_tooltip_shortcut_joined": "Включно з вами, %(commaSeparatedMembers)s", + "failed_determine_user": "Неможливо визначити, якого користувача ігнорувати, оскільки подія учасника змінилася.", "failed_reject_invite": "Не вдалось відхилити запрошення", "forget_room": "Забути цю кімнату", "forget_space": "Забути цей простір", @@ -2095,7 +2099,6 @@ "room_list": { "add_room_label": "Додати кімнату", "add_space_label": "Додати простір", - "appearance": "Вигляд", "breadcrumbs_empty": "Немає недавно відвіданих кімнат", "breadcrumbs_label": "Недавно відвідані кімнати", "empty": { @@ -2104,11 +2107,14 @@ "no_chats_description_no_room_rights": "Розпочніть користування, написавши комусь повідомлення", "no_favourites": "У вас ще немає обраних бесід", "no_favourites_description": "Ви можете додати бесіду до обраних у її налаштуваннях", + "no_invites": "У вас немає непрочитаних запрошень", + "no_mentions": "У вас немає непрочитаних згадок", "no_people": "У вас ще немає особистих бесід", "no_people_description": "Ви можете очистити фільтри, щоб побачити інші ваші бесіди", "no_rooms": "Ви ще не входили до кімнат", "no_rooms_description": "Ви можете очистити фільтри, щоб побачити інші ваші бесіди", "no_unread": "Вітаємо! У вас немає непрочитаних повідомлень", + "show_activity": "Переглянути всю діяльність", "show_chats": "Показати всі бесіди" }, "failed_add_tag": "Не вдалось додати до кімнати мітку %(tagName)s", @@ -2116,6 +2122,9 @@ "failed_set_dm_tag": "Не вдалося встановити мітку особистого повідомлення", "filters": { "favourite": "Обрані", + "invites": "Запрошення", + "low_priority": "Неважливі", + "mentions": "Згадування", "people": "Люди", "rooms": "Кімнати", "unread": "Непрочитані" @@ -2148,7 +2157,6 @@ }, "room_options": "Параметри кімнати", "show_less": "Згорнути", - "show_message_previews": "Показати попередній перегляд повідомлень", "show_n_more": { "other": "Показати ще %(count)s", "one": "Показати ще %(count)s" @@ -2449,6 +2457,10 @@ "recent_changes_heading": "Останні зміни, котрі ще не отримано", "title": "Сервер не відповідає" }, + "service_worker_error": { + "description": "%(brand)s потребує Service worker для завантаження автентифікованих медіа з репозиторіїв вмісту Matrix. Ваш браузер не підтримує цю функцію, тому медіафайли можуть не завантажуватися.", + "title": "Не вдалося завантажити Service Worker" + }, "seshat": { "error_initialising": "Не вдалося почати пошук, перевірте налаштування, щоб дізнатися більше", "reset_button": "Очистити сховище подій", @@ -2542,6 +2554,8 @@ "session_key": "Ключ сеансу:", "title": "Додатково" }, + "confirm_key_storage_off": "Ви впевнені, що хочете вимкнути сховище ключів?", + "confirm_key_storage_off_description": "Якщо ви вийдете з усіх своїх пристроїв, ви втратите історію повідомлень і вам потрібно буде знову підтвердити всі наявні контакти. Докладніше", "delete_key_storage": { "breadcrumb_page": "Видалити сховище ключів", "confirm": "Видалити сховище ключів", @@ -2800,6 +2814,7 @@ "voip": "Голосові та відеовиклики" }, "preferences": { + "Electron.enableContentProtection": "Запобігати захопленню вмісту вікна іншими застосунками", "Electron.enableHardwareAcceleration": "Увімкнути апаратне прискорення (перезапустіть %(appName)s, щоб зміни вступили в силу)", "always_show_menu_bar": "Завжди показувати рядок меню", "autocomplete_delay": "Затримка автозаповнення (мс)", @@ -2972,6 +2987,7 @@ "show_chat_effects": "Показувати ефекти бесід (анімації отримання, наприклад, конфеті)", "show_displayname_changes": "Показувати зміни псевдонімів", "show_join_leave": "Показувати повідомлення про приєднання/виходи (не стосується запрошень/вилучень/блокувань]", + "show_message_previews": "Показати попередній перегляд повідомлень", "show_nsfw_content": "Показати матеріали NSFW", "show_read_receipts": "Показувати мітки прочитання, надіслані іншими користувачами", "show_redaction_placeholder": "Показувати замісну позначку замість видалених повідомлень", @@ -3118,6 +3134,7 @@ "upgraderoom": "Поліпшує кімнату до нової версії", "upgraderoom_permission_error": "Вам бракує дозволу на використання цієї команди.", "usage": "Використання", + "verify": "Звіряє користувача, сеанс та супровід відкритого ключа", "view": "Перегляд кімнати з вказаною адресою", "whois": "Показує відомості про користувача" }, diff --git a/src/i18n/strings/vi.json b/src/i18n/strings/vi.json index a0c91663ff..d8cfdbece1 100644 --- a/src/i18n/strings/vi.json +++ b/src/i18n/strings/vi.json @@ -723,8 +723,6 @@ "security_key_title": "Chìa khóa bảo mật" }, "bootstrap_title": "Đang thiết lập khóa bảo mật", - "cancel_entering_passphrase_description": "Bạn có chắc chắn muốn hủy nhập cụm mật khẩu không?", - "cancel_entering_passphrase_title": "Hủy nhập cụm mật khẩu?", "confirm_encryption_setup_body": "Nhấp vào nút bên dưới để xác nhận thiết lập mã hóa.", "confirm_encryption_setup_title": "Xác nhận thiết lập mã hóa", "cross_signing_room_normal": "Phòng này được mã hóa end-to-end", @@ -2449,6 +2447,7 @@ "upgraderoom": "Nâng cấp phòng lên phiên bản mới", "upgraderoom_permission_error": "Bạn không có quyền để dùng lệnh này.", "usage": "Cách sử dụng", + "verify": "Xác thực người dùng, thiết bị và tuple pubkey", "view": "Phòng truyền hình với địa chỉ đã cho", "whois": "Hiển thị thông tin về người dùng" }, diff --git a/src/i18n/strings/zh_Hans.json b/src/i18n/strings/zh_Hans.json index 43e1ac008c..68a7ea94e9 100644 --- a/src/i18n/strings/zh_Hans.json +++ b/src/i18n/strings/zh_Hans.json @@ -733,8 +733,6 @@ "security_key_title": "安全密钥" }, "bootstrap_title": "设置密钥", - "cancel_entering_passphrase_description": "你确定要取消输入口令词组吗?", - "cancel_entering_passphrase_title": "取消输入口令词组?", "confirm_encryption_setup_body": "点击下方按钮以确认设置加密。", "confirm_encryption_setup_title": "确认加密设置", "cross_signing_room_normal": "此房间是端到端加密的", @@ -2400,6 +2398,7 @@ "upgraderoom": "将房间升级到新版本", "upgraderoom_permission_error": "你没有权限使用此命令。", "usage": "用法", + "verify": "验证用户、会话和公钥元组", "whois": "显示关于用户的信息" }, "space": { diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index 18cc169d97..2dcf0c1d47 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -791,8 +791,6 @@ "security_key_title": "安全金鑰" }, "bootstrap_title": "正在產生金鑰", - "cancel_entering_passphrase_description": "您確定要取消輸入安全密語嗎?", - "cancel_entering_passphrase_title": "取消輸入安全密語?", "confirm_encryption_setup_body": "點擊下方按鈕以確認設定加密。", "confirm_encryption_setup_title": "確認加密設定", "cross_signing_room_normal": "此聊天室已端對端加密", @@ -2645,6 +2643,7 @@ "upgraderoom": "升級聊天室到新版本", "upgraderoom_permission_error": "您沒有使用此指令的必要權限。", "usage": "使用方法", + "verify": "驗證使用者、工作階段與公開金鑰組合", "view": "檢視指定聊天室的地址", "whois": "顯示關於使用者的資訊" }, diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 36030713ec..3ca7503cd1 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -47,6 +47,8 @@ import { type RecentEmojiData } from "../emojipicker/recent.ts"; import { type Assignable } from "../@types/common.ts"; import { SortingAlgorithm } from "../stores/room-list-v3/skip-list/sorters/index.ts"; import MediaPreviewConfigController from "./controllers/MediaPreviewConfigController.ts"; +import InviteRulesConfigController from "./controllers/InviteRulesConfigController.ts"; +import { type ComputedInviteConfig } from "../@types/invite-rules.ts"; export const defaultWatchManager = new WatchManager(); @@ -349,7 +351,9 @@ export interface Settings { "Electron.alwaysShowMenuBar": IBaseSetting; "Electron.showTrayIcon": IBaseSetting; "Electron.enableHardwareAcceleration": IBaseSetting; + "Electron.enableContentProtection": IBaseSetting; "mediaPreviewConfig": IBaseSetting; + "inviteRules": IBaseSetting; "Developer.elementCallUrl": IBaseSetting; } @@ -433,6 +437,11 @@ export const SETTINGS: Settings = { supportedLevels: LEVELS_ROOM_SETTINGS, default: MediaPreviewConfigController.default, }, + "inviteRules": { + controller: new InviteRulesConfigController(), + supportedLevels: [SettingLevel.ACCOUNT], + default: InviteRulesConfigController.default, + }, "feature_report_to_moderators": { isFeature: true, labsGroup: LabGroup.Moderation, @@ -894,7 +903,7 @@ export const SETTINGS: Settings = { "VideoView.flipVideoHorizontally": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, displayName: _td("settings|voip|mirror_local_feed"), - default: false, + default: true, }, "theme": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, @@ -1136,6 +1145,7 @@ export const SETTINGS: Settings = { "RoomList.showMessagePreview": { supportedLevels: [SettingLevel.DEVICE], default: false, + displayName: _td("settings|show_message_previews"), }, "RightPanel.phasesGlobal": { supportedLevels: [SettingLevel.DEVICE], @@ -1382,6 +1392,11 @@ export const SETTINGS: Settings = { displayName: _td("settings|preferences|enable_hardware_acceleration"), default: true, }, + "Electron.enableContentProtection": { + supportedLevels: [SettingLevel.PLATFORM], + displayName: _td("settings|preferences|enable_hardware_acceleration"), + default: false, + }, "Developer.elementCallUrl": { supportedLevels: [SettingLevel.DEVICE], displayName: _td("devtools|settings|elementCallUrl"), diff --git a/src/settings/controllers/InviteRulesConfigController.ts b/src/settings/controllers/InviteRulesConfigController.ts new file mode 100644 index 0000000000..61c7323009 --- /dev/null +++ b/src/settings/controllers/InviteRulesConfigController.ts @@ -0,0 +1,88 @@ +/* +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 { type MatrixClient, type IContent } from "matrix-js-sdk/src/matrix"; + +import { type SettingLevel } from "../SettingLevel.ts"; +import MatrixClientBackedController from "./MatrixClientBackedController.ts"; +import { + type ComputedInviteConfig as ComputedInviteRules, + INVITE_RULES_ACCOUNT_DATA_TYPE, + type InviteConfigAccountData, +} from "../../@types/invite-rules.ts"; +import { _t } from "../../languageHandler.tsx"; + +/** + * Handles invite filtering rules provided by MSC4155. + * This handler does not make use of the roomId parameter. + */ +export default class InviteRulesConfigController extends MatrixClientBackedController { + public static readonly default: ComputedInviteRules = { + allBlocked: false, + }; + + private static getValidSettingData(content: IContent): ComputedInviteRules { + const expectedConfig = content as InviteConfigAccountData; + return { + allBlocked: !!expectedConfig.blocked_users?.includes("*"), + }; + } + + public initMatrixClient(newClient: MatrixClient): void { + newClient.doesServerSupportUnstableFeature("org.matrix.msc4155").then((result) => { + this.featureSupported = result; + }); + } + + public featureSupported?: boolean; + + public constructor() { + super(); + this.featureSupported = false; + } + + private getValue = (): ComputedInviteRules => { + const accountData = + this.client?.getAccountData(INVITE_RULES_ACCOUNT_DATA_TYPE)?.getContent() ?? {}; + return InviteRulesConfigController.getValidSettingData(accountData); + }; + + public getValueOverride(_level: SettingLevel): ComputedInviteRules { + return this.getValue(); + } + + public get settingDisabled(): true | string { + return this.featureSupported ? true : _t("settings|not_supported"); + } + + public async beforeChange( + _level: SettingLevel, + _roomId: string | null, + newValue: ComputedInviteRules, + ): Promise { + if (!this.client) { + return false; + } + const existingContent = this.client + .getAccountData(INVITE_RULES_ACCOUNT_DATA_TYPE) + ?.getContent(); + const newContent: InviteConfigAccountData = { + ...existingContent, + blocked_users: [...(existingContent?.blocked_users ?? [])], + }; + if (newValue.allBlocked && !newContent.blocked_users!.includes("*")) { + newContent.blocked_users!.push("*"); + } else if (!newValue.allBlocked && newContent.blocked_users?.includes("*")) { + newContent.blocked_users = newContent.blocked_users.filter((u) => u !== "*"); + } else { + // No changes required. + return false; + } + await this.client.setAccountData(INVITE_RULES_ACCOUNT_DATA_TYPE, newContent); + return true; + } +} diff --git a/src/settings/handlers/AccountSettingsHandler.ts b/src/settings/handlers/AccountSettingsHandler.ts index 3ea9db158e..29117778d6 100644 --- a/src/settings/handlers/AccountSettingsHandler.ts +++ b/src/settings/handlers/AccountSettingsHandler.ts @@ -204,6 +204,9 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa case "mediaPreviewConfig": // Handled in MediaPreviewConfigController. return; + case "inviteRules": + // Handled in InviteRulesConfigController. + return; default: return this.setAccountData(DEFAULT_SETTINGS_EVENT_TYPE, settingName, newValue); } diff --git a/src/settings/handlers/RoomAccountSettingsHandler.ts b/src/settings/handlers/RoomAccountSettingsHandler.ts index 8fdf412adf..6ed09a9801 100644 --- a/src/settings/handlers/RoomAccountSettingsHandler.ts +++ b/src/settings/handlers/RoomAccountSettingsHandler.ts @@ -122,6 +122,9 @@ export default class RoomAccountSettingsHandler extends MatrixClientBackedSettin case "mediaPreviewConfig": // Handled in MediaPreviewConfigController. return; + case "inviteRules": + // Handled in InviteRulesConfigController. + return; default: return this.setRoomAccountData(roomId, DEFAULT_SETTINGS_EVENT_TYPE, settingName, newValue); } diff --git a/src/stores/room-list-v3/skip-list/sorters/RecencySorter.ts b/src/stores/room-list-v3/skip-list/sorters/RecencySorter.ts index 07c902e3ee..53e8ae4331 100644 --- a/src/stores/room-list-v3/skip-list/sorters/RecencySorter.ts +++ b/src/stores/room-list-v3/skip-list/sorters/RecencySorter.ts @@ -9,6 +9,7 @@ import type { Room } from "matrix-js-sdk/src/matrix"; import { type Sorter, SortingAlgorithm } from "."; import { getLastTs } from "../../../room-list/algorithms/tag-sorting/RecentAlgorithm"; import { RoomNotificationStateStore } from "../../../notifications/RoomNotificationStateStore"; +import { DefaultTagID } from "../../../room-list/models"; export class RecencySorter implements Sorter { public constructor(private myUserId: string) {} @@ -19,11 +20,9 @@ export class RecencySorter implements Sorter { } public comparator(roomA: Room, roomB: Room, cache?: any): number { - // Check mute status first; muted rooms should be at the bottom - const isRoomAMuted = RoomNotificationStateStore.instance.getRoomState(roomA).muted; - const isRoomBMuted = RoomNotificationStateStore.instance.getRoomState(roomB).muted; - if (isRoomAMuted && !isRoomBMuted) return 1; - if (isRoomBMuted && !isRoomAMuted) return -1; + // First check if the rooms are low priority or muted + const exceptionalOrdering = this.getScore(roomA) - this.getScore(roomB); + if (exceptionalOrdering !== 0) return exceptionalOrdering; // Then check recency; recent rooms should be at the top const roomALastTs = this.getTs(roomA, cache); @@ -35,6 +34,28 @@ export class RecencySorter implements Sorter { return SortingAlgorithm.Recency; } + /** + * This sorter mostly sorts rooms by recency but there are two exceptions: + * 1. Muted rooms are sorted to the bottom of the list. + * 2. Low priority rooms are sorted to the bottom of the list but before muted rooms. + * + * The following method provides a numerical value that takes care of this + * exceptional ordering. For two rooms A and B, it works as follows: + * - If getScore(A) - getScore(B) > 0, A should come after B + * - If getScore(A) - getScore(B) < 0, A should come before B + * - If getScore(A) - getScore(B) = 0, no special ordering needed, just use recency + */ + private getScore(room: Room): number { + const isLowPriority = !!room.tags[DefaultTagID.LowPriority]; + const isMuted = RoomNotificationStateStore.instance.getRoomState(room).muted; + // These constants are chosen so that the following order is maintained: + // Low priority rooms -> Low priority and muted rooms -> Muted rooms + if (isMuted && isLowPriority) return 5; + else if (isMuted) return 10; + else if (isLowPriority) return 2; + else return 0; + } + private getTs(room: Room, cache?: { [roomId: string]: number }): number { const ts = cache?.[room.roomId] ?? getLastTs(room, this.myUserId); if (cache) { diff --git a/src/stores/spaces/SpaceStore.ts b/src/stores/spaces/SpaceStore.ts index 015c9fa0fd..2945d486c2 100644 --- a/src/stores/spaces/SpaceStore.ts +++ b/src/stores/spaces/SpaceStore.ts @@ -1302,11 +1302,11 @@ export class SpaceStoreClass extends AsyncStoreWithClient { const newValue = SettingsStore.getValue("Spaces.allRoomsInHome"); if (this.allRoomsInHome !== newValue) { this._allRoomsInHome = newValue; - this.emit(UPDATE_HOME_BEHAVIOUR, this.allRoomsInHome); if (this.enabledMetaSpaces.includes(MetaSpace.Home)) { this.rebuildHomeSpace(); } this.sendUserProperties(); + this.emit(UPDATE_HOME_BEHAVIOUR, this.allRoomsInHome); } break; } diff --git a/src/utils/exportUtils/Exporter.ts b/src/utils/exportUtils/Exporter.ts index 5409186941..4085126491 100644 --- a/src/utils/exportUtils/Exporter.ts +++ b/src/utils/exportUtils/Exporter.ts @@ -25,8 +25,17 @@ type BlobFile = { blob: Blob; }; +type FileDetails = { + directory: string; + name: string; + date: string; + extension: string; + count?: number; +}; + export default abstract class Exporter { protected files: BlobFile[] = []; + protected fileNames: Map = new Map(); protected cancelled = false; protected constructor( @@ -241,6 +250,19 @@ export default abstract class Exporter { return [fileName, "." + ext]; } + protected makeUniqueFilePath(details: FileDetails): string { + const makePath = ({ directory, name, date, extension, count = 0 }: FileDetails): string => + `${directory}/${name}-${date}${count > 0 ? ` (${count})` : ""}${extension}`; + const defaultPath = makePath(details); + const count = this.fileNames.get(defaultPath) || 0; + this.fileNames.set(defaultPath, count + 1); + if (count > 0) { + return makePath({ ...details, count }); + } + + return defaultPath; + } + public getFilePath(event: MatrixEvent): string { const mediaType = event.getContent().msgtype; let fileDirectory: string; @@ -263,7 +285,12 @@ export default abstract class Exporter { if (event.getType() === "m.sticker") fileExt = ".png"; if (isVoiceMessage(event)) fileExt = ".ogg"; - return fileDirectory + "/" + fileName + "-" + fileDate + fileExt; + return this.makeUniqueFilePath({ + directory: fileDirectory, + name: fileName, + date: fileDate, + extension: fileExt, + }); } protected isReply(event: MatrixEvent): boolean { diff --git a/src/vector/platform/ElectronPlatform.tsx b/src/vector/platform/ElectronPlatform.tsx index c8b544daf1..2bacbe337d 100644 --- a/src/vector/platform/ElectronPlatform.tsx +++ b/src/vector/platform/ElectronPlatform.tsx @@ -52,8 +52,6 @@ interface SquirrelUpdate { const SSO_ID_KEY = "element-desktop-ssoid"; -const isMac = navigator.platform.toUpperCase().includes("MAC"); - function platformFriendlyName(): string { // used to use window.process but the same info is available here if (navigator.userAgent.includes("Macintosh")) { @@ -73,13 +71,6 @@ function platformFriendlyName(): string { } } -function onAction(payload: ActionPayload): void { - // Whitelist payload actions, no point sending most across - if (["call_state"].includes(payload.action)) { - window.electron!.send("app_onAction", payload); - } -} - function getUpdateCheckStatus(status: boolean | string): UpdateStatus { if (status === true) { return { status: UpdateCheckStatus.Downloading }; @@ -97,9 +88,11 @@ export default class ElectronPlatform extends BasePlatform { private readonly ipc = new IPCManager("ipcCall", "ipcReply"); private readonly eventIndexManager: BaseEventIndexManager = new SeshatIndexManager(); private readonly initialised: Promise; + private readonly electron: Electron; private protocol!: string; private sessionId!: string; private config!: IConfigOptions; + private supportedSettings?: Record; public constructor() { super(); @@ -107,15 +100,15 @@ export default class ElectronPlatform extends BasePlatform { if (!window.electron) { throw new Error("Cannot instantiate ElectronPlatform, window.electron is not set"); } + this.electron = window.electron; - dis.register(onAction); /* IPC Call `check_updates` returns: true if there is an update available false if there is not or the error if one is encountered */ - window.electron.on("check_updates", (event, status) => { + this.electron.on("check_updates", (event, status) => { dis.dispatch({ action: Action.CheckUpdates, ...getUpdateCheckStatus(status), @@ -124,44 +117,44 @@ export default class ElectronPlatform extends BasePlatform { // `userAccessToken` (IPC) is requested by the main process when appending authentication // to media downloads. A reply is sent over the same channel. - window.electron.on("userAccessToken", () => { - window.electron!.send("userAccessToken", MatrixClientPeg.get()?.getAccessToken()); + this.electron.on("userAccessToken", () => { + this.electron.send("userAccessToken", MatrixClientPeg.get()?.getAccessToken()); }); // `homeserverUrl` (IPC) is requested by the main process. A reply is sent over the same channel. - window.electron.on("homeserverUrl", () => { - window.electron!.send("homeserverUrl", MatrixClientPeg.get()?.getHomeserverUrl()); + this.electron.on("homeserverUrl", () => { + this.electron.send("homeserverUrl", MatrixClientPeg.get()?.getHomeserverUrl()); }); // `serverSupportedVersions` is requested by the main process when it needs to know if the // server supports a particular version. This is primarily used to detect authenticated media // support. A reply is sent over the same channel. - window.electron.on("serverSupportedVersions", async () => { - window.electron!.send("serverSupportedVersions", await MatrixClientPeg.get()?.getVersions()); + this.electron.on("serverSupportedVersions", async () => { + this.electron.send("serverSupportedVersions", await MatrixClientPeg.get()?.getVersions()); }); // try to flush the rageshake logs to indexeddb before quit. - window.electron.on("before-quit", function () { + this.electron.on("before-quit", function () { logger.log("element-desktop closing"); rageshake.flush(); }); - window.electron.on("update-downloaded", this.onUpdateDownloaded); + this.electron.on("update-downloaded", this.onUpdateDownloaded); - window.electron.on("preferences", () => { + this.electron.on("preferences", () => { dis.fire(Action.ViewUserSettings); }); - window.electron.on("userDownloadCompleted", (ev, { id, name }) => { + this.electron.on("userDownloadCompleted", (ev, { id, name }) => { const key = `DOWNLOAD_TOAST_${id}`; const onAccept = (): void => { - window.electron!.send("userDownloadAction", { id, open: true }); + this.electron.send("userDownloadAction", { id, open: true }); ToastStore.sharedInstance().dismissToast(key); }; const onDismiss = (): void => { - window.electron!.send("userDownloadAction", { id }); + this.electron.send("userDownloadAction", { id }); }; ToastStore.sharedInstance().addOrReplaceToast({ @@ -180,7 +173,7 @@ export default class ElectronPlatform extends BasePlatform { }); }); - window.electron.on("openDesktopCapturerSourcePicker", async () => { + this.electron.on("openDesktopCapturerSourcePicker", async () => { const { finished } = Modal.createDialog(DesktopCapturerSourcePicker); const [source] = await finished; // getDisplayMedia promise does not return if no dummy is passed here as source @@ -192,11 +185,20 @@ export default class ElectronPlatform extends BasePlatform { this.initialised = this.initialise(); } + protected onAction(payload: ActionPayload): void { + super.onAction(payload); + // Whitelist payload actions, no point sending most across + if (["call_state"].includes(payload.action)) { + this.electron.send("app_onAction", payload); + } + } + private async initialise(): Promise { - const { protocol, sessionId, config } = await window.electron!.initialise(); + const { protocol, sessionId, config, supportedSettings } = await this.electron.initialise(); this.protocol = protocol; this.sessionId = sessionId; this.config = config; + this.supportedSettings = supportedSettings; } public async getConfig(): Promise { @@ -248,7 +250,7 @@ export default class ElectronPlatform extends BasePlatform { if (this.notificationCount === count) return; super.setNotificationCount(count); - window.electron!.send("setBadgeCount", count); + this.electron.send("setBadgeCount", count); } public supportsNotifications(): boolean { @@ -288,7 +290,7 @@ export default class ElectronPlatform extends BasePlatform { } public loudNotification(ev: MatrixEvent, room: Room): void { - window.electron!.send("loudNotification"); + this.electron.send("loudNotification"); } public needsUrlTooltips(): boolean { @@ -300,21 +302,16 @@ export default class ElectronPlatform extends BasePlatform { } public supportsSetting(settingName?: string): boolean { - switch (settingName) { - case "Electron.showTrayIcon": // Things other than Mac support tray icons - case "Electron.alwaysShowMenuBar": // This isn't relevant on Mac as Menu bars don't live in the app window - return !isMac; - default: - return true; - } + if (settingName === undefined) return true; + return this.supportedSettings?.[settingName] === true; } public getSettingValue(settingName: string): Promise { - return this.ipc.call("getSettingValue", settingName); + return this.electron.getSettingValue(settingName); } public setSettingValue(settingName: string, value: any): Promise { - return this.ipc.call("setSettingValue", settingName, value); + return this.electron.setSettingValue(settingName, value); } public async canSelfUpdate(): Promise { @@ -324,14 +321,14 @@ export default class ElectronPlatform extends BasePlatform { public startUpdateCheck(): void { super.startUpdateCheck(); - window.electron!.send("check_updates"); + this.electron.send("check_updates"); } public installUpdate(): void { // IPC to the main process to install the update, since quitAndInstall // doesn't fire the before-quit event so the main process needs to know // it should exit. - window.electron!.send("install_update"); + this.electron.send("install_update"); } public getDefaultDeviceDisplayName(): string { diff --git a/src/vector/platform/IPCManager.ts b/src/vector/platform/IPCManager.ts index dc183f0cfb..5a5bb9b0fb 100644 --- a/src/vector/platform/IPCManager.ts +++ b/src/vector/platform/IPCManager.ts @@ -11,7 +11,7 @@ import { type ElectronChannel } from "../../@types/global"; interface IPCPayload { id?: number; - error?: string; + error?: string | { message: string }; reply?: any; } @@ -53,7 +53,12 @@ export class IPCManager { const callbacks = this.pendingIpcCalls[payload.id]; delete this.pendingIpcCalls[payload.id]; if (payload.error) { - callbacks.reject(payload.error); + // `seshat.ts` sends a JavaScript object with a `message` property. Turn it into a proper Error. + let error = payload.error; + if (typeof error === "object" && error.message) { + error = new Error(error.message); + } + callbacks.reject(error); } else { callbacks.resolve(payload.reply); } diff --git a/test/CreateCrossSigning-test.ts b/test/CreateCrossSigning-test.ts index cd2bf904cd..09bb3b5a63 100644 --- a/test/CreateCrossSigning-test.ts +++ b/test/CreateCrossSigning-test.ts @@ -45,7 +45,7 @@ describe("CreateCrossSigning", () => { const makeRequest = jest.fn(); await authUploadDeviceSigningKeys!(makeRequest); - expect(makeRequest).toHaveBeenCalledWith({}); + expect(makeRequest).toHaveBeenCalledWith(null); }); it("should prompt user if upload failed with UIA", async () => { diff --git a/test/test-utils/test-utils.ts b/test/test-utils/test-utils.ts index 8243fe5083..1c147023c2 100644 --- a/test/test-utils/test-utils.ts +++ b/test/test-utils/test-utils.ts @@ -99,6 +99,7 @@ export function createTestClient(): MatrixClient { getDevices: jest.fn().mockResolvedValue({ devices: [{ device_id: "ABCDEFGHI" }] }), getSessionId: jest.fn().mockReturnValue("iaszphgvfku"), credentials: { userId: "@userId:matrix.org" }, + getAccessToken: jest.fn(), secretStorage: { get: jest.fn(), @@ -156,6 +157,7 @@ export function createTestClient(): MatrixClient { getSessionBackupPrivateKey: jest.fn().mockResolvedValue(null), isSecretStorageReady: jest.fn().mockResolvedValue(false), deleteKeyBackupVersion: jest.fn(), + crossSignDevice: jest.fn(), }), getPushActionsForEvent: jest.fn(), diff --git a/test/unit-tests/SlashCommands-test.tsx b/test/unit-tests/SlashCommands-test.tsx index b30bc69176..884031237e 100644 --- a/test/unit-tests/SlashCommands-test.tsx +++ b/test/unit-tests/SlashCommands-test.tsx @@ -9,18 +9,21 @@ Please see LICENSE files in the repository root for full details. import { type MatrixClient, Room, RoomMember } from "matrix-js-sdk/src/matrix"; import { KnownMembership } from "matrix-js-sdk/src/types"; import { mocked } from "jest-mock"; +import { act, waitFor } from "jest-matrix-react"; import { type Command, Commands, getCommand } from "../../src/SlashCommands"; import { createTestClient } from "../test-utils"; import { LocalRoom, LOCAL_ROOM_ID_PREFIX } from "../../src/models/LocalRoom"; import SettingsStore from "../../src/settings/SettingsStore"; import { SdkContextClass } from "../../src/contexts/SDKContext"; -import Modal from "../../src/Modal"; +import Modal, { type ComponentType, type IHandle } from "../../src/Modal"; import WidgetUtils from "../../src/utils/WidgetUtils"; import { WidgetType } from "../../src/widgets/WidgetType"; import { warnSelfDemote } from "../../src/components/views/right_panel/UserInfo"; import dispatcher from "../../src/dispatcher/dispatcher"; import { SettingLevel } from "../../src/settings/SettingLevel"; +import QuestionDialog from "../../src/components/views/dialogs/QuestionDialog"; +import ErrorDialog from "../../src/components/views/dialogs/ErrorDialog"; jest.mock("../../src/components/views/right_panel/UserInfo"); @@ -253,6 +256,56 @@ describe("SlashCommands", () => { }); }); + describe("/verify", () => { + it("should return usage if no args", () => { + const command = findCommand("verify")!; + expect(command.run(client, roomId, null, undefined).error).toBe(command.getUsage()); + }); + + it("should attempt manual verification after confirmation", async () => { + // Given we say yes to prompt + const spy = jest.spyOn(Modal, "createDialog"); + spy.mockReturnValue({ finished: Promise.resolve([true]) } as unknown as IHandle); + + // When we run the command + const command = findCommand("verify")!; + await act(() => command.run(client, roomId, null, "mydeviceid myfingerprint")); + + // Then the prompt is displayed + expect(spy).toHaveBeenCalledWith( + QuestionDialog, + expect.objectContaining({ title: "Caution: manual device verification" }), + ); + + // And then we attempt the verification + await waitFor(() => + expect(spy).toHaveBeenCalledWith( + ErrorDialog, + expect.objectContaining({ title: "Verification failed" }), + ), + ); + }); + + it("should not do manual verification if cancelled", async () => { + // Given we say no to prompt + const spy = jest.spyOn(Modal, "createDialog"); + spy.mockReturnValue({ finished: Promise.resolve([false]) } as unknown as IHandle); + + // When we run the command + const command = findCommand("verify")!; + command.run(client, roomId, null, "mydeviceid myfingerprint"); + + // Then the prompt is displayed + expect(spy).toHaveBeenCalledWith( + QuestionDialog, + expect.objectContaining({ title: "Caution: manual device verification" }), + ); + + // But nothing else happens + expect(spy).not.toHaveBeenCalledWith(ErrorDialog, expect.anything()); + }); + }); + describe("/addwidget", () => { it("should parse html iframe snippets", async () => { jest.spyOn(WidgetUtils, "canUserModifyWidgets").mockReturnValue(true); diff --git a/test/unit-tests/components/structures/MatrixChat-test.tsx b/test/unit-tests/components/structures/MatrixChat-test.tsx index 62fcf40f4d..94fc9a925a 100644 --- a/test/unit-tests/components/structures/MatrixChat-test.tsx +++ b/test/unit-tests/components/structures/MatrixChat-test.tsx @@ -127,7 +127,7 @@ describe("", () => { setGuest: jest.fn(), setNotifTimelineSet: jest.fn(), getAccountData: jest.fn(), - doesServerSupportUnstableFeature: jest.fn(), + doesServerSupportUnstableFeature: jest.fn().mockResolvedValue(false), getDevices: jest.fn().mockResolvedValue({ devices: [] }), getProfileInfo: jest.fn().mockResolvedValue({ displayname: "Ernie", diff --git a/test/unit-tests/components/structures/RoomView-test.tsx b/test/unit-tests/components/structures/RoomView-test.tsx index 2d0d7f0d36..387027f30a 100644 --- a/test/unit-tests/components/structures/RoomView-test.tsx +++ b/test/unit-tests/components/structures/RoomView-test.tsx @@ -77,6 +77,7 @@ import { type ViewUserPayload } from "../../../../src/dispatcher/payloads/ViewUs import { CallStore } from "../../../../src/stores/CallStore.ts"; import MediaDeviceHandler, { MediaDeviceKindEnum } from "../../../../src/MediaDeviceHandler.ts"; import Modal from "../../../../src/Modal.tsx"; +import ErrorDialog from "../../../../src/components/views/dialogs/ErrorDialog.tsx"; // Used by group calls jest.spyOn(MediaDeviceHandler, "getDevices").mockResolvedValue({ @@ -237,6 +238,7 @@ describe("RoomView", () => { member.membership = KnownMembership.Invite; member.events.member = new MatrixEvent({ sender: "@bob:example.org", + content: { membership: KnownMembership.Invite }, }); room.getMyMembership = jest.fn().mockReturnValue(KnownMembership.Invite); room.getMember = jest.fn().mockReturnValue(member); @@ -271,10 +273,45 @@ describe("RoomView", () => { finished: Promise.resolve([true, true, false]), close: jest.fn(), }); - await fireEvent.click(getByRole("button", { name: "Decline and block" })); + await act(() => fireEvent.click(getByRole("button", { name: "Decline and block" }))); expect(cli.leave).toHaveBeenCalledWith(room.roomId); expect(cli.setIgnoredUsers).toHaveBeenCalledWith(["@carol:example.org", "@bob:example.org"]); }); + it("prevents ignoring own user", async () => { + const member = new RoomMember(room.roomId, cli.getSafeUserId()); + member.membership = KnownMembership.Invite; + member.events.member = new MatrixEvent({ + /* + It doesn't matter that this is an invite event coming from own user, we just + want to simulate a situation where the sender of the membership event somehow + ends up being own user. + */ + sender: cli.getSafeUserId(), + content: { membership: KnownMembership.Invite }, + }); + jest.spyOn(room, "getMyMembership").mockReturnValue(KnownMembership.Invite); + jest.spyOn(room, "getMember").mockReturnValue(member); + + const { getByRole } = await mountRoomView(); + cli.getIgnoredUsers.mockReturnValue(["@carol:example.org"]); + jest.spyOn(Modal, "createDialog").mockReturnValue({ + finished: Promise.resolve([true, true, false]), + close: jest.fn(), + }); + + await act(() => fireEvent.click(getByRole("button", { name: "Decline and block" }))); + + // Should show error in a modal dialog + await waitFor(() => { + expect(Modal.createDialog).toHaveBeenLastCalledWith(ErrorDialog, { + title: "Failed to reject invite", + description: "Cannot determine which user to ignore since the member event has changed.", + }); + }); + + // The ignore call should not go through + expect(cli.setIgnoredUsers).not.toHaveBeenCalled(); + }); it("handles declining an invite and reporting the room", async () => { const { getByRole } = await mountRoomView(); jest.spyOn(Modal, "createDialog").mockReturnValue({ diff --git a/test/unit-tests/components/structures/__snapshots__/RoomView-test.tsx.snap b/test/unit-tests/components/structures/__snapshots__/RoomView-test.tsx.snap index b99ca68c28..8d909a6797 100644 --- a/test/unit-tests/components/structures/__snapshots__/RoomView-test.tsx.snap +++ b/test/unit-tests/components/structures/__snapshots__/RoomView-test.tsx.snap @@ -1363,7 +1363,7 @@ exports[`RoomView should not display the timeline when the room encryption is lo aria-label="Open room settings" aria-live="off" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" - data-color="4" + data-color="5" data-testid="avatar-img" data-type="round" role="button" @@ -1390,7 +1390,7 @@ exports[`RoomView should not display the timeline when the room encryption is lo - !11:example.org + !12:example.org @@ -1571,7 +1571,7 @@ exports[`RoomView should not display the timeline when the room encryption is lo aria-label="Open room settings" aria-live="off" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" - data-color="4" + data-color="5" data-testid="avatar-img" data-type="round" role="button" @@ -1598,7 +1598,7 @@ exports[`RoomView should not display the timeline when the room encryption is lo - !11:example.org + !12:example.org @@ -1952,7 +1952,7 @@ exports[`RoomView video rooms should render joined video room view 1`] = ` aria-label="Open room settings" aria-live="off" class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52" - data-color="3" + data-color="4" data-testid="avatar-img" data-type="round" role="button" @@ -1979,7 +1979,7 @@ exports[`RoomView video rooms should render joined video room view 1`] = ` - !16:example.org + !17:example.org diff --git a/test/unit-tests/components/viewmodels/avatars/RoomAvatarViewModel-test.tsx b/test/unit-tests/components/viewmodels/avatars/RoomAvatarViewModel-test.tsx index ffaa152a53..5f29f3ff33 100644 --- a/test/unit-tests/components/viewmodels/avatars/RoomAvatarViewModel-test.tsx +++ b/test/unit-tests/components/viewmodels/avatars/RoomAvatarViewModel-test.tsx @@ -8,10 +8,14 @@ import { renderHook, waitFor } from "jest-matrix-react"; import { JoinRule, type MatrixClient, type Room, RoomMember, User } from "matrix-js-sdk/src/matrix"; -import { useRoomAvatarViewModel } from "../../../../../src/components/viewmodels/avatars/RoomAvatarViewModel"; +import { + AvatarBadgeDecoration, + useRoomAvatarViewModel, +} from "../../../../../src/components/viewmodels/avatars/RoomAvatarViewModel"; import { createTestClient, mkStubRoom } from "../../../../test-utils"; import DMRoomMap from "../../../../../src/utils/DMRoomMap"; import * as PresenceIndicatorModule from "../../../../../src/components/views/avatars/WithPresenceIndicator"; +import { DefaultTagID } from "../../../../../src/stores/room-list/models"; jest.mock("../../../../../src/utils/room/getJoinedNonFunctionalMembers", () => ({ getJoinedNonFunctionalMembers: jest.fn().mockReturnValue([]), @@ -32,35 +36,61 @@ describe("RoomAvatarViewModel", () => { jest.spyOn(PresenceIndicatorModule, "usePresence").mockReturnValue(null); }); - it("should has hasDecoration to false", async () => { + it("should have badgeDecoration set to LowPriority", () => { + room.tags[DefaultTagID.LowPriority] = {}; const { result: vm } = renderHook(() => useRoomAvatarViewModel(room)); - expect(vm.current.hasDecoration).toBe(false); + expect(vm.current.badgeDecoration).toBe(AvatarBadgeDecoration.LowPriority); }); - it("should has isVideoRoom set to true", () => { + it("should have badgeDecoration set to VideoRoom", () => { jest.spyOn(room, "isCallRoom").mockReturnValue(true); const { result: vm } = renderHook(() => useRoomAvatarViewModel(room)); - expect(vm.current.isVideoRoom).toBe(true); - expect(vm.current.hasDecoration).toBe(true); + expect(vm.current.badgeDecoration).toBe(AvatarBadgeDecoration.VideoRoom); }); - it("should has isPublic set to true", () => { + it("should have badgeDecoration set to PublicRoom", () => { jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Public); - const { result: vm } = renderHook(() => useRoomAvatarViewModel(room)); - expect(vm.current.isPublic).toBe(true); - expect(vm.current.hasDecoration).toBe(true); + expect(vm.current.badgeDecoration).toBe(AvatarBadgeDecoration.PublicRoom); + }); + + it("should set badgeDecoration based on priority", () => { + // 1. Presence has the least priority + const user = User.createUser("userId", matrixClient); + const roomMember = new RoomMember(room.roomId, "userId"); + roomMember.user = user; + jest.spyOn(PresenceIndicatorModule, "useDmMember").mockReturnValue(roomMember); + jest.spyOn(PresenceIndicatorModule, "usePresence").mockReturnValue(PresenceIndicatorModule.Presence.Online); + + const { result: vm1 } = renderHook(() => useRoomAvatarViewModel(room)); + expect(vm1.current.badgeDecoration).toBe(AvatarBadgeDecoration.Presence); + + // 2. With presence and public room, presence takes precedence + jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Public); + // Render again, it's easier than mocking the event emitter. + const { result: vm, rerender } = renderHook(() => useRoomAvatarViewModel(room)); + expect(vm.current.badgeDecoration).toBe(AvatarBadgeDecoration.PublicRoom); + + // 3. With presence, public-room and video room, video room takes precedence + jest.spyOn(room, "isCallRoom").mockReturnValue(true); + rerender(room); + expect(vm.current.badgeDecoration).toBe(AvatarBadgeDecoration.VideoRoom); + + // 4. With presence, public room, video room and low priority, low priority takes precedence + room.tags[DefaultTagID.LowPriority] = {}; + rerender(room); + expect(vm.current.badgeDecoration).toBe(AvatarBadgeDecoration.LowPriority); }); it("should recompute isPublic when room changed", async () => { const { result: vm, rerender } = renderHook((props) => useRoomAvatarViewModel(props), { initialProps: room }); - expect(vm.current.isPublic).toBe(false); + expect(vm.current.badgeDecoration).not.toBe(AvatarBadgeDecoration.PublicRoom); const publicRoom = mkStubRoom("roomId2", "roomName2", matrixClient); jest.spyOn(publicRoom, "getJoinRule").mockReturnValue(JoinRule.Public); rerender(publicRoom); - await waitFor(() => expect(vm.current.isPublic).toBe(true)); + await waitFor(() => expect(vm.current.badgeDecoration).toBe(AvatarBadgeDecoration.PublicRoom)); }); it("should return presence", async () => { diff --git a/test/unit-tests/components/viewmodels/roomlist/RoomListHeaderViewModel-test.tsx b/test/unit-tests/components/viewmodels/roomlist/RoomListHeaderViewModel-test.tsx index 09109366cf..f9fe45202a 100644 --- a/test/unit-tests/components/viewmodels/roomlist/RoomListHeaderViewModel-test.tsx +++ b/test/unit-tests/components/viewmodels/roomlist/RoomListHeaderViewModel-test.tsx @@ -13,7 +13,7 @@ import { range } from "lodash"; import { useRoomListHeaderViewModel } from "../../../../../src/components/viewmodels/roomlist/RoomListHeaderViewModel"; import SpaceStore from "../../../../../src/stores/spaces/SpaceStore"; import { mkStubRoom, stubClient, withClientContextRenderOptions } from "../../../../test-utils"; -import SettingsStore, { type CallbackFn } from "../../../../../src/settings/SettingsStore"; +import SettingsStore from "../../../../../src/settings/SettingsStore"; import defaultDispatcher from "../../../../../src/dispatcher/dispatcher"; import { Action } from "../../../../../src/dispatcher/actions"; import { @@ -24,7 +24,6 @@ import { showSpaceSettings, } from "../../../../../src/utils/space"; import { createRoom, hasCreateRoomRights } from "../../../../../src/components/viewmodels/roomlist/utils"; -import { SettingLevel } from "../../../../../src/settings/SettingLevel"; import RoomListStoreV3 from "../../../../../src/stores/room-list-v3/RoomListStoreV3"; import { SortOption } from "../../../../../src/components/viewmodels/roomlist/useSorter"; import { SortingAlgorithm } from "../../../../../src/stores/room-list-v3/skip-list/sorters"; @@ -238,38 +237,4 @@ describe("useRoomListHeaderViewModel", () => { expect(vm.current.activeSortOption).toEqual(SortOption.AToZ); }); }); - - describe("message preview toggle", () => { - it("should return shouldShowMessagePreview based on setting", () => { - jest.spyOn(SettingsStore, "getValue").mockImplementation(() => true); - const { result: vm } = render(); - expect(vm.current.shouldShowMessagePreview).toEqual(true); - }); - - it("should update when setting changes", async () => { - jest.spyOn(SettingsStore, "getValue").mockImplementation(() => true); - - let watchFn: CallbackFn; - jest.spyOn(SettingsStore, "watchSetting").mockImplementation((settingName, _roomId, fn) => { - if (settingName === "RoomList.showMessagePreview") watchFn = fn; - return ""; - }); - const { result: vm } = render(); - expect(vm.current.shouldShowMessagePreview).toEqual(true); - - jest.spyOn(SettingsStore, "getValue").mockImplementation(() => false); - act(() => watchFn("RoomList.showMessagePreview", "", SettingLevel.DEVICE, false, false)); - expect(vm.current.shouldShowMessagePreview).toEqual(false); - }); - - it("should change setting on toggle", () => { - jest.spyOn(SettingsStore, "getValue").mockImplementation(() => true); - const fn = jest.spyOn(SettingsStore, "setValue").mockImplementation(async () => {}); - - const { result: vm } = render(); - expect(vm.current.shouldShowMessagePreview).toEqual(true); - act(() => vm.current.toggleMessagePreview()); - expect(fn).toHaveBeenCalledWith("RoomList.showMessagePreview", null, "device", false); - }); - }); }); diff --git a/test/unit-tests/components/viewmodels/roomlist/RoomListViewModel-test.tsx b/test/unit-tests/components/viewmodels/roomlist/RoomListViewModel-test.tsx index 3f4337608e..9607cf9218 100644 --- a/test/unit-tests/components/viewmodels/roomlist/RoomListViewModel-test.tsx +++ b/test/unit-tests/components/viewmodels/roomlist/RoomListViewModel-test.tsx @@ -66,9 +66,17 @@ describe("RoomListViewModel", () => { mockAndCreateRooms(); const { result: vm } = renderHook(() => useRoomListViewModel()); // should have 6 filters - expect(vm.current.primaryFilters).toHaveLength(6); + expect(vm.current.primaryFilters).toHaveLength(7); // check the order - for (const [i, name] of ["Unreads", "People", "Rooms", "Mentions", "Invites", "Favourites"].entries()) { + for (const [i, name] of [ + "Unreads", + "People", + "Rooms", + "Mentions", + "Invites", + "Favourites", + "Low priority", + ].entries()) { expect(vm.current.primaryFilters[i].name).toEqual(name); expect(vm.current.primaryFilters[i].active).toEqual(false); } diff --git a/test/unit-tests/components/views/avatars/RoomAvatarView-test.tsx b/test/unit-tests/components/views/avatars/RoomAvatarView-test.tsx index 020b92227d..00ab886b4f 100644 --- a/test/unit-tests/components/views/avatars/RoomAvatarView-test.tsx +++ b/test/unit-tests/components/views/avatars/RoomAvatarView-test.tsx @@ -12,6 +12,7 @@ import { mocked } from "jest-mock"; import { RoomAvatarView } from "../../../../../src/components/views/avatars/RoomAvatarView"; import { mkStubRoom, stubClient } from "../../../../test-utils"; import { + AvatarBadgeDecoration, type RoomAvatarViewState, useRoomAvatarViewModel, } from "../../../../../src/components/viewmodels/avatars/RoomAvatarViewModel"; @@ -19,6 +20,7 @@ import DMRoomMap from "../../../../../src/utils/DMRoomMap"; import { Presence } from "../../../../../src/components/views/avatars/WithPresenceIndicator"; jest.mock("../../../../../src/components/viewmodels/avatars/RoomAvatarViewModel", () => ({ + ...jest.requireActual("../../../../../src/components/viewmodels/avatars/RoomAvatarViewModel"), useRoomAvatarViewModel: jest.fn(), })); @@ -33,9 +35,7 @@ describe("", () => { beforeEach(() => { defaultValue = { - hasDecoration: true, - isPublic: true, - isVideoRoom: true, + badgeDecoration: undefined, presence: null, }; @@ -43,13 +43,27 @@ describe("", () => { }); it("should not render a decoration", () => { - mocked(useRoomAvatarViewModel).mockReturnValue({ ...defaultValue, hasDecoration: false }); + mocked(useRoomAvatarViewModel).mockReturnValue({ ...defaultValue }); const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); }); + it("should render a low priority room decoration", () => { + mocked(useRoomAvatarViewModel).mockReturnValue({ + ...defaultValue, + badgeDecoration: AvatarBadgeDecoration.LowPriority, + }); + const { asFragment } = render(); + + expect(screen.getByLabelText("This is a low priority room")).toBeInTheDocument(); + expect(asFragment()).toMatchSnapshot(); + }); + it("should render a video room decoration", () => { - mocked(useRoomAvatarViewModel).mockReturnValue({ ...defaultValue, hasDecoration: true, isVideoRoom: true }); + mocked(useRoomAvatarViewModel).mockReturnValue({ + ...defaultValue, + badgeDecoration: AvatarBadgeDecoration.VideoRoom, + }); const { asFragment } = render(); expect(screen.getByLabelText("This room is a video room")).toBeInTheDocument(); @@ -59,9 +73,7 @@ describe("", () => { it("should render a public room decoration", () => { mocked(useRoomAvatarViewModel).mockReturnValue({ ...defaultValue, - hasDecoration: true, - isPublic: true, - isVideoRoom: false, + badgeDecoration: AvatarBadgeDecoration.PublicRoom, }); const { asFragment } = render(); @@ -69,19 +81,6 @@ describe("", () => { expect(asFragment()).toMatchSnapshot(); }); - it("should not render a public room decoration if the room is a video room", () => { - mocked(useRoomAvatarViewModel).mockReturnValue({ - ...defaultValue, - hasDecoration: true, - isPublic: true, - isVideoRoom: true, - }); - render(); - - expect(screen.getByLabelText("This room is a video room")).toBeInTheDocument(); - expect(screen.queryByLabelText("This room is public")).toBeNull(); - }); - it.each([ { presence: Presence.Online, label: "Online" }, { presence: Presence.Offline, label: "Offline" }, @@ -90,7 +89,7 @@ describe("", () => { ])("should render the $presence presence", ({ presence, label }) => { mocked(useRoomAvatarViewModel).mockReturnValue({ ...defaultValue, - hasDecoration: true, + badgeDecoration: AvatarBadgeDecoration.Presence, presence, }); const { asFragment } = render(); diff --git a/test/unit-tests/components/views/avatars/__snapshots__/RoomAvatarView-test.tsx.snap b/test/unit-tests/components/views/avatars/__snapshots__/RoomAvatarView-test.tsx.snap index 1a4263a0c9..9aeb54ab0f 100644 --- a/test/unit-tests/components/views/avatars/__snapshots__/RoomAvatarView-test.tsx.snap +++ b/test/unit-tests/components/views/avatars/__snapshots__/RoomAvatarView-test.tsx.snap @@ -24,6 +24,48 @@ exports[` should not render a decoration 1`] = ` `; +exports[` should render a low priority room decoration 1`] = ` + +
    + + + + + + +
    +
    +`; + exports[` should render a public room decoration 1`] = `
    should render the AWAY presence 1`] = ` > should render the AWAY presence 1`] = ` width="32px" /> - - - should render the BUSY presence 1`] = ` > should render the BUSY presence 1`] = ` width="32px" /> - - - should render the OFFLINE presence 1`] = ` > should render the OFFLINE presence 1`] = ` width="32px" /> - - - should render the ONLINE presence 1`] = ` > should render the ONLINE presence 1`] = ` width="32px" /> - - - { render(); }; - const enterRecoveryKey = (): void => { - act(() => { + const enterRecoveryKey = async (valueToEnter: string = recoveryKey): Promise => { + await act(async () => { fireEvent.change(screen.getByRole("textbox"), { target: { - value: recoveryKey, + value: valueToEnter, }, }); - // wait for debounce - jest.advanceTimersByTime(250); + // wait for debounce, and then give `checkPrivateKey` a chance to complete + await jest.advanceTimersByTimeAsync(250); }); }; @@ -116,4 +116,22 @@ describe("AccessSecretStorageDialog", () => { await expect(screen.findByText("The recovery key you entered is not correct.")).resolves.toBeInTheDocument(); expect(screen.getByText("Continue")).toHaveAttribute("aria-disabled", "true"); }); + + it("Clears the 'invalid recovery key' notice when the input is cleared", async function () { + renderComponent({ onFinished: () => {}, checkPrivateKey: () => false }); + + jest.spyOn(mockClient.secretStorage, "checkKey").mockRejectedValue(new Error("invalid key")); + + // First, enter the wrong recovery key + await enterRecoveryKey(); + expect(screen.getByText("The recovery key you entered is not correct.")).toBeInTheDocument(); + + // Now, clear the input: the notice should be cleared. + await enterRecoveryKey(""); + expect(screen.queryByText("The recovery key you entered is not correct.")).not.toBeInTheDocument(); + expect( + screen.getByText("If you have a security key or security phrase, this will work too."), + ).toBeInTheDocument(); + expect(screen.getByText("Continue")).toHaveAttribute("aria-disabled", "true"); + }); }); diff --git a/test/unit-tests/components/views/dialogs/BaseDialog-test.tsx b/test/unit-tests/components/views/dialogs/BaseDialog-test.tsx new file mode 100644 index 0000000000..e8737fe88d --- /dev/null +++ b/test/unit-tests/components/views/dialogs/BaseDialog-test.tsx @@ -0,0 +1,21 @@ +/* +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 from "react"; +import { render } from "jest-matrix-react"; +import userEvent from "@testing-library/user-event"; + +import BaseDialog from "../../../../../src/components/views/dialogs/BaseDialog.tsx"; + +describe("BaseDialog", () => { + it("calls onFinished when Escape is pressed", async () => { + const onFinished = jest.fn(); + render(); + await userEvent.keyboard("{Escape}"); + expect(onFinished).toHaveBeenCalled(); + }); +}); diff --git a/test/unit-tests/components/views/dialogs/ManualDeviceKeyVerificationDialog-test.tsx b/test/unit-tests/components/views/dialogs/ManualDeviceKeyVerificationDialog-test.tsx new file mode 100644 index 0000000000..3193b41e66 --- /dev/null +++ b/test/unit-tests/components/views/dialogs/ManualDeviceKeyVerificationDialog-test.tsx @@ -0,0 +1,186 @@ +/* + * Copyright 2024-2025 New Vector Ltd. + * Copyright 2023 The Matrix.org Foundation C.I.C. + * + * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only + * Please see LICENSE files in the repository root for full details. + */ + +import React from "react"; +import { act, fireEvent, render, screen, waitFor } from "jest-matrix-react"; +import { type MatrixClient } from "matrix-js-sdk/src/matrix"; +import { DeviceVerificationStatus } from "matrix-js-sdk/src/crypto-api"; + +import { stubClient } from "../../../../test-utils"; +import { ManualDeviceKeyVerificationDialog } from "../../../../../src/components/views/dialogs/ManualDeviceKeyVerificationDialog"; + +describe("ManualDeviceKeyVerificationDialog", () => { + let mockClient: MatrixClient; + + function renderDialog(onFinished: (confirm: boolean) => void) { + return render(); + } + + beforeEach(() => { + mockClient = stubClient(); + mockExistingDevices(); + }); + + it("should render correctly", () => { + // When we render a dialog populated with data + const { dialog } = populateDialog("XYZ", "ABCDEFGH"); + + // Then the dialog looks as expected + expect(dialog.asFragment()).toMatchSnapshot(); + }); + + it("should call onFinished and crossSignDevice if we click Verify", async () => { + // Given a dialog populated with correct data + const { dialog, onFinished } = populateDialog("DEVICEID", "FINGERPRINT"); + + // When we click Verify session + dialog.getByRole("button", { name: "Verify session" }).click(); + + // Then crossSignDevice is called + await waitFor(async () => { + expect(onFinished).toHaveBeenCalledWith(true); + expect(mockClient.getCrypto()?.crossSignDevice).toHaveBeenCalledWith("DEVICEID"); + }); + }); + + it("should not call crossSignDevice if fingerprint is wrong", async () => { + // Given a dialog populated with incorrect fingerprint + const { dialog, onFinished } = populateDialog("DEVICEID", "WRONG_FINGERPRINT"); + + // When we click Verify session + act(() => dialog.getByRole("button", { name: "Verify session" }).click()); + + // Then crossSignDevice is not called + await waitFor(async () => { + expect(onFinished).toHaveBeenCalledWith(true); + expect(mockClient.getCrypto()?.crossSignDevice).not.toHaveBeenCalled(); + }); + + // And an error is displayed + expect( + screen.getByText( + "the supplied fingerprint 'WRONG_FINGERPRINT' does not match the device fingerprint, 'FINGERPRINT'", + { exact: false }, + ), + ).toBeVisible(); + }); + + it("should not call crossSignDevice if device is already verified", async () => { + // Given a dialog populated with a correct fingerprint for a verified device + const { dialog, onFinished } = populateDialog("VERIFIED_DEVICEID", "VERIFIED_FINGERPRINT"); + + // When we click Verify session + act(() => dialog.getByRole("button", { name: "Verify session" }).click()); + + // Then crossSignDevice is not called + await waitFor(async () => { + expect(onFinished).toHaveBeenCalledWith(true); + expect(mockClient.getCrypto()?.crossSignDevice).not.toHaveBeenCalled(); + }); + + // And an error is displayed + expect(screen.getByText("Failed to verify 'VERIFIED_DEVICEID': This device is already verified")).toBeVisible(); + }); + + it("should not call crossSignDevice if device is already verified and fingerprint is wrong", async () => { + // Given a dialog populated with an incorrect fingerprint for a verified device + const { dialog, onFinished } = populateDialog("VERIFIED_DEVICEID", "WRONG_FINGERPRINT"); + + // When we click Verify session + act(() => dialog.getByRole("button", { name: "Verify session" }).click()); + + // Then crossSignDevice is not called + await waitFor(async () => { + expect(onFinished).toHaveBeenCalledWith(true); + expect(mockClient.getCrypto()?.crossSignDevice).not.toHaveBeenCalled(); + }); + + // And an error is displayed + expect( + screen.getByText("The supplied fingerprint does not match, but the device is already verified!", { + exact: false, + }), + ).toBeVisible(); + }); + + it("should not call crossSignDevice if device is not found", async () => { + // Given a dialog populated with incorrect device ID + const { dialog, onFinished } = populateDialog("WRONG_DEVICE_ID", "FINGERPRINT"); + + // When we click Verify session + act(() => dialog.getByRole("button", { name: "Verify session" }).click()); + + // Then crossSignDevice is not called + await waitFor(async () => { + expect(onFinished).toHaveBeenCalledWith(true); + expect(mockClient.getCrypto()?.crossSignDevice).not.toHaveBeenCalled(); + }); + + // And an error is displayed + expect(screen.getByText("device 'WRONG_DEVICE_ID' was not found", { exact: false })).toBeVisible(); + }); + + it("should call onFinished but not crossSignDevice if we click Cancel", () => { + // Given a dialog populated with correct data + const { dialog, onFinished } = populateDialog("DEVICEID", "FINGERPRINT"); + + // When we click cancel + dialog.getByRole("button", { name: "Cancel" }).click(); + + // Then only onFinished is called + expect(onFinished).toHaveBeenCalledWith(false); + expect(mockClient.getCrypto()?.crossSignDevice).not.toHaveBeenCalled(); + }); + + function unverifiedDevice(): DeviceVerificationStatus { + return new DeviceVerificationStatus({}); + } + + function verifiedDevice(): DeviceVerificationStatus { + return new DeviceVerificationStatus({ + signedByOwner: true, + crossSigningVerified: true, + tofu: true, + localVerified: true, + trustCrossSignedDevices: true, + }); + } + + /** + * Set up two devices: DEVICEID, which is unverified, and VERIFIED_DEVICEID, which is verified. + */ + function mockExistingDevices() { + mockClient.getCrypto()!.getDeviceVerificationStatus = jest + .fn() + .mockImplementation(async (_userId, deviceId) => + deviceId === "DEVICEID" ? unverifiedDevice() : verifiedDevice(), + ); + + mockClient.getCrypto()!.getUserDeviceInfo = jest.fn().mockImplementation(async (userIds) => { + const userDevices = new Map(); + userDevices.set("DEVICEID", { getFingerprint: jest.fn().mockReturnValue("FINGERPRINT") }); + userDevices.set("VERIFIED_DEVICEID", { getFingerprint: jest.fn().mockReturnValue("VERIFIED_FINGERPRINT") }); + + const deviceMap = new Map(); + for (const userId of userIds) { + deviceMap.set(userId, userDevices); + } + return deviceMap; + }); + } + + function populateDialog(deviceId: string, fingerprint: string) { + const onFinished = jest.fn(); + const dialog = renderDialog(onFinished); + const deviceIdBox = dialog.getByRole("textbox", { name: "Device ID" }); + const fingerprintBox = dialog.getByRole("textbox", { name: "Fingerprint (session key)" }); + fireEvent.change(deviceIdBox, { target: { value: deviceId } }); + fireEvent.change(fingerprintBox, { target: { value: fingerprint } }); + return { dialog, onFinished }; + } +}); diff --git a/test/unit-tests/components/views/dialogs/__snapshots__/ManualDeviceKeyVerificationDialog-test.tsx.snap b/test/unit-tests/components/views/dialogs/__snapshots__/ManualDeviceKeyVerificationDialog-test.tsx.snap new file mode 100644 index 0000000000..75c2870126 --- /dev/null +++ b/test/unit-tests/components/views/dialogs/__snapshots__/ManualDeviceKeyVerificationDialog-test.tsx.snap @@ -0,0 +1,107 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ManualDeviceKeyVerificationDialog should render correctly 1`] = ` + +
    + @@ -452,7 +452,7 @@ exports[` space menu should display the space menu 1`] = ` xmlns="http://www.w3.org/2000/svg" >
    @@ -547,7 +547,7 @@ exports[` space menu should not display the space menu 1`] xmlns="http://www.w3.org/2000/svg" >
    diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListOptionsMenu-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListOptionsMenu-test.tsx.snap index aafcede2ad..6b75053753 100644 --- a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListOptionsMenu-test.tsx.snap +++ b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListOptionsMenu-test.tsx.snap @@ -29,7 +29,7 @@ exports[` should match snapshot 1`] = ` xmlns="http://www.w3.org/2000/svg" > diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListPanel-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListPanel-test.tsx.snap deleted file mode 100644 index 1cfb170829..0000000000 --- a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListPanel-test.tsx.snap +++ /dev/null @@ -1,433 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[` should not render the RoomListSearch component when UIComponent.FilterContainer is at false 1`] = ` - -
    -
    -
    -

    - Home -

    -
    -
    - - -
    -
    -
      -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    -
    -
    -
    -`; - -exports[` should render the RoomListSearch component when UIComponent.FilterContainer is at true 1`] = ` - -
    - -
    -
    -

    - Home -

    -
    -
    - - -
    -
    -
      -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    -
    -
    -
    -`; diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListPrimaryFilters-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListPrimaryFilters-test.tsx.snap index 6c705944b8..ee68963b2f 100644 --- a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListPrimaryFilters-test.tsx.snap +++ b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListPrimaryFilters-test.tsx.snap @@ -1,39 +1,62 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` should render primary filters 1`] = ` +exports[` should renders all filters correctly 1`] = ` -
      -
    • - -
    • -
    • - +
    • +
    • - Rooms - -
    • -
    + + +
  • + +
  • + +
    `; diff --git a/test/unit-tests/components/views/settings/tabs/user/InviteRulesAccountSetting-test.tsx b/test/unit-tests/components/views/settings/tabs/user/InviteRulesAccountSetting-test.tsx new file mode 100644 index 0000000000..2753bd39c9 --- /dev/null +++ b/test/unit-tests/components/views/settings/tabs/user/InviteRulesAccountSetting-test.tsx @@ -0,0 +1,76 @@ +/* +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 from "react"; +import { render } from "jest-matrix-react"; +import userEvent from "@testing-library/user-event"; + +import { InviteRulesAccountSetting } from "../../../../../../../src/components/views/settings/tabs/user/InviteRulesAccountSettings"; +import SettingsStore from "../../../../../../../src/settings/SettingsStore"; +import { type ComputedInviteConfig } from "../../../../../../../src/@types/invite-rules"; +import { SettingLevel } from "../../../../../../../src/settings/SettingLevel"; + +function mockSetting(mediaPreviews: ComputedInviteConfig, supported = true) { + jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName) => { + if (settingName === "inviteRules") { + return mediaPreviews; + } + throw Error(`Unexpected setting ${settingName}`); + }); + jest.spyOn(SettingsStore, "disabledMessage").mockImplementation((settingName) => { + if (settingName === "inviteRules") { + return supported ? undefined : "test-not-supported"; + } + throw Error(`Unexpected setting ${settingName}`); + }); +} + +describe("InviteRulesAccountSetting", () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + + it("does not render if not supported", async () => { + mockSetting({ allBlocked: false }, false); + const { findByText, findByLabelText } = render(); + const input = await findByLabelText("Allow users to invite you to rooms"); + await userEvent.hover(input); + const result = await findByText("test-not-supported"); + expect(result).toBeInTheDocument(); + }); + it("renders correct state when invites are not blocked", async () => { + mockSetting({ allBlocked: false }, true); + const { findByLabelText } = render(); + const result = await findByLabelText("Allow users to invite you to rooms"); + expect(result).toBeChecked(); + }); + it("renders correct state when invites are blocked", async () => { + mockSetting({ allBlocked: true }, true); + const { findByLabelText } = render(); + const result = await findByLabelText("Allow users to invite you to rooms"); + expect(result).not.toBeChecked(); + }); + it("handles disabling all invites", async () => { + mockSetting({ allBlocked: false }, true); + jest.spyOn(SettingsStore, "setValue").mockImplementation(); + const { findByLabelText } = render(); + const result = await findByLabelText("Allow users to invite you to rooms"); + await userEvent.click(result); + expect(SettingsStore.setValue).toHaveBeenCalledWith("inviteRules", null, SettingLevel.ACCOUNT, { + allBlocked: true, + }); + }); + it("handles enabling all invites", async () => { + mockSetting({ allBlocked: true }, true); + jest.spyOn(SettingsStore, "setValue").mockImplementation(); + const { findByLabelText } = render(); + const result = await findByLabelText("Allow users to invite you to rooms"); + await userEvent.click(result); + expect(SettingsStore.setValue).toHaveBeenCalledWith("inviteRules", null, SettingLevel.ACCOUNT, { + allBlocked: false, + }); + }); +}); diff --git a/test/unit-tests/components/views/settings/tabs/user/__snapshots__/PreferencesUserSettingsTab-test.tsx.snap b/test/unit-tests/components/views/settings/tabs/user/__snapshots__/PreferencesUserSettingsTab-test.tsx.snap index 3e3662bd68..7fe1f1d49b 100644 --- a/test/unit-tests/components/views/settings/tabs/user/__snapshots__/PreferencesUserSettingsTab-test.tsx.snap +++ b/test/unit-tests/components/views/settings/tabs/user/__snapshots__/PreferencesUserSettingsTab-test.tsx.snap @@ -1297,6 +1297,36 @@ exports[`PreferencesUserSettingsTab should render 1`] = ` +
    +
    + +
    + Allow users to invite you to rooms +
    +
    +
    +
    +
    +
    +
    { + afterEach(() => { + jest.restoreAllMocks(); + }); + + it("gets the default settings when none are specified.", () => { + const controller = new InviteRulesConfigController(); + + MatrixClientBackedController.matrixClient = getMockClientWithEventEmitter({ + ...mockClientMethodsServer(), + getAccountData: jest.fn().mockReturnValue(null), + }); + + const value = controller.getValueOverride(SettingLevel.ACCOUNT); + expect(value).toEqual(InviteRulesConfigController.default); + }); + + it("gets the default settings when the setting is empty.", () => { + const controller = new InviteRulesConfigController(); + + MatrixClientBackedController.matrixClient = getMockClientWithEventEmitter({ + ...mockClientMethodsServer(), + getAccountData: jest + .fn() + .mockReturnValue(new MatrixEvent({ type: INVITE_RULES_ACCOUNT_DATA_TYPE, content: {} })), + }); + + const value = controller.getValueOverride(SettingLevel.ACCOUNT); + expect(value).toEqual(InviteRulesConfigController.default); + }); + + it.each([{ blocked_users: ["foo_bar"] }, { blocked_users: [] }, {}])( + "calculates blockAll to be false", + (content: InviteConfigAccountData) => { + const controller = new InviteRulesConfigController(); + + MatrixClientBackedController.matrixClient = getMockClientWithEventEmitter({ + ...mockClientMethodsServer(), + getAccountData: jest.fn().mockReturnValue( + new MatrixEvent({ + type: INVITE_RULES_ACCOUNT_DATA_TYPE, + content, + }), + ), + }); + + const globalValue = controller.getValueOverride(SettingLevel.ACCOUNT); + expect(globalValue.allBlocked).toEqual(false); + }, + ); + + it.each([ + { blocked_users: ["*"] }, + { blocked_users: ["*", "bob"] }, + { allowed_users: ["*"], blocked_users: ["*"] }, + ])("calculates blockAll to be true", (content: InviteConfigAccountData) => { + const controller = new InviteRulesConfigController(); + + MatrixClientBackedController.matrixClient = getMockClientWithEventEmitter({ + ...mockClientMethodsServer(), + getAccountData: jest.fn().mockReturnValue( + new MatrixEvent({ + type: INVITE_RULES_ACCOUNT_DATA_TYPE, + content, + }), + ), + }); + + const globalValue = controller.getValueOverride(SettingLevel.ACCOUNT); + expect(globalValue.allBlocked).toEqual(true); + }); + + it("sets the account data correctly for blockAll = true", async () => { + const controller = new InviteRulesConfigController(); + const client = (MatrixClientBackedController.matrixClient = getMockClientWithEventEmitter({ + ...mockClientMethodsServer(), + getAccountData: jest.fn().mockReturnValue( + new MatrixEvent({ + type: INVITE_RULES_ACCOUNT_DATA_TYPE, + content: { + existing_content: {}, + allowed_servers: ["*"], + }, + }), + ), + setAccountData: jest.fn(), + })); + + expect(await controller.beforeChange(SettingLevel.ACCOUNT, null, { allBlocked: true })).toBe(true); + expect(client.setAccountData).toHaveBeenCalledWith(INVITE_RULES_ACCOUNT_DATA_TYPE, { + existing_content: {}, + allowed_servers: ["*"], + blocked_users: ["*"], + }); + }); + + it("sets the account data correctly for blockAll = false", async () => { + const controller = new InviteRulesConfigController(); + const client = (MatrixClientBackedController.matrixClient = getMockClientWithEventEmitter({ + ...mockClientMethodsServer(), + getAccountData: jest.fn().mockReturnValue( + new MatrixEvent({ + type: INVITE_RULES_ACCOUNT_DATA_TYPE, + content: { + existing_content: {}, + allowed_servers: ["*"], + blocked_users: ["extra_user", "*"], + }, + }), + ), + setAccountData: jest.fn(), + })); + + expect(await controller.beforeChange(SettingLevel.ACCOUNT, null, { allBlocked: false })).toBe(true); + expect(client.setAccountData).toHaveBeenCalledWith(INVITE_RULES_ACCOUNT_DATA_TYPE, { + existing_content: {}, + allowed_servers: ["*"], + blocked_users: ["extra_user"], + }); + }); + it.each([true, false])("ignores a no-op when allBlocked = %s", async (allBlocked) => { + const controller = new InviteRulesConfigController(); + const client = (MatrixClientBackedController.matrixClient = getMockClientWithEventEmitter({ + ...mockClientMethodsServer(), + getAccountData: jest.fn().mockReturnValue( + new MatrixEvent({ + type: INVITE_RULES_ACCOUNT_DATA_TYPE, + content: { + existing_content: {}, + allowed_servers: ["*"], + blocked_users: allBlocked ? ["*"] : [], + }, + }), + ), + setAccountData: jest.fn(), + })); + + expect(await controller.beforeChange(SettingLevel.ACCOUNT, null, { allBlocked })).toBe(false); + expect(client.setAccountData).not.toHaveBeenCalled(); + }); +}); diff --git a/test/unit-tests/stores/room-list-v3/RoomListStoreV3-test.ts b/test/unit-tests/stores/room-list-v3/RoomListStoreV3-test.ts index 0c0cdfc091..47b48d437b 100644 --- a/test/unit-tests/stores/room-list-v3/RoomListStoreV3-test.ts +++ b/test/unit-tests/stores/room-list-v3/RoomListStoreV3-test.ts @@ -798,4 +798,53 @@ describe("RoomListStoreV3", () => { expect(store.getSortedRooms()[34]).toEqual(unmutedRoom); }); }); + + describe("Low priority rooms", () => { + async function getRoomListStoreWithRooms() { + const client = stubClient(); + const rooms = getMockedRooms(client); + + // Let's say that rooms 34, 84, 64, 14, 57 are low priority + const lowPriorityIndices = [34, 84, 64, 14, 57]; + const lowPriorityRooms = lowPriorityIndices.map((i) => rooms[i]); + for (const room of lowPriorityRooms) { + room.tags[DefaultTagID.LowPriority] = {}; + } + + // Let's say that rooms 14, 57, 65, 78, 82, 5, 36 are muted + const mutedIndices = [14, 57, 65, 78, 82, 5, 36]; + const mutedRooms = mutedIndices.map((i) => rooms[i]); + jest.spyOn(RoomNotificationStateStore.instance, "getRoomState").mockImplementation((room) => { + const state = { + muted: mutedRooms.includes(room), + } as unknown as RoomNotificationState; + return state; + }); + + client.getVisibleRooms = jest.fn().mockReturnValue(rooms); + jest.spyOn(AsyncStoreWithClient.prototype, "matrixClient", "get").mockReturnValue(client); + const store = new RoomListStoreV3Class(dispatcher); + await store.start(); + + // We expect the following order: Low Priority -> Low Priority & Muted -> Muted + const expectedRoomIds = [84, 64, 34, 57, 14, 82, 78, 65, 36, 5].map((i) => rooms[i].roomId); + + return { + client, + rooms, + expectedRoomIds, + store, + dispatcher, + }; + } + + it("Low priority rooms are pushed to the bottom of the list just before muted rooms", async () => { + const { store, expectedRoomIds } = await getRoomListStoreWithRooms(); + const result = store + .getSortedRooms() + .slice(90) + .map((r) => r.roomId); + expect(result).toEqual(expectedRoomIds); + }); + }); }); diff --git a/test/unit-tests/utils/exportUtils/HTMLExport-test.ts b/test/unit-tests/utils/exportUtils/HTMLExport-test.ts index 6415efaf1d..91c4c3febd 100644 --- a/test/unit-tests/utils/exportUtils/HTMLExport-test.ts +++ b/test/unit-tests/utils/exportUtils/HTMLExport-test.ts @@ -104,8 +104,8 @@ describe("HTMLExport", () => { const chunk = events.slice(from, limit); return Promise.resolve({ chunk, - from: from.toString(), - to: (from + limit).toString(), + start: from.toString(), + end: (from + limit).toString(), }); }); } @@ -419,6 +419,36 @@ describe("HTMLExport", () => { expect(text).toBe(attachmentBody); }); + it("should handle attachments with identical names and dates", async () => { + mockMessages(EVENT_MESSAGE, EVENT_ATTACHMENT, EVENT_ATTACHMENT); + + const exporter = new HTMLExporter( + room, + ExportType.LastNMessages, + { + attachmentsIncluded: true, + maxSize: 1_024 * 1_024, + numberOfMessages: 40, + }, + () => {}, + ); + + await exporter.export(); + + const files = getFiles(exporter); + + // There should be 5 files: 2 attachments, 1 html file, 1 css file and 1 js file + expect(Object.keys(files)).toHaveLength(5); + + // Ensure that the attachment is present + const file = files[Object.keys(files).find((k) => k.endsWith(".txt"))!]; + expect(file).not.toBeUndefined(); + + // Ensure that the duplicate attachment has been uniquely named + const duplicateFile = files[Object.keys(files).find((k) => k.endsWith("(1).txt"))!]; + expect(duplicateFile).not.toBeUndefined(); + }); + it("should handle when attachment cannot be fetched", async () => { mockMessages(EVENT_MESSAGE, EVENT_ATTACHMENT_MALFORMED, EVENT_ATTACHMENT); const attachmentBody = "Lorem ipsum dolor sit amet"; diff --git a/test/unit-tests/vector/platform/ElectronPlatform-test.ts b/test/unit-tests/vector/platform/ElectronPlatform-test.ts index c4fda4a20f..40168231ac 100644 --- a/test/unit-tests/vector/platform/ElectronPlatform-test.ts +++ b/test/unit-tests/vector/platform/ElectronPlatform-test.ts @@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details. import { logger } from "matrix-js-sdk/src/logger"; import { MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; -import { mocked } from "jest-mock"; +import { mocked, type MockedObject } from "jest-mock"; import { UpdateCheckStatus } from "../../../../src/BasePlatform"; import { Action } from "../../../../src/dispatcher/actions"; @@ -19,6 +19,7 @@ import Modal from "../../../../src/Modal"; import DesktopCapturerSourcePicker from "../../../../src/components/views/elements/DesktopCapturerSourcePicker"; import ElectronPlatform from "../../../../src/vector/platform/ElectronPlatform"; import { setupLanguageMock } from "../../../setup/setupLanguage"; +import { stubClient } from "../../../test-utils"; jest.mock("../../../../src/rageshake/rageshake", () => ({ flush: jest.fn(), @@ -35,8 +36,11 @@ describe("ElectronPlatform", () => { protocol: "io.element.desktop", sessionId: "session-id", config: { _config: true }, + supportedSettings: { setting1: false, setting2: true }, }), - }; + setSettingValue: jest.fn().mockResolvedValue(undefined), + getSettingValue: jest.fn().mockResolvedValue(undefined), + } as unknown as MockedObject; const dispatchSpy = jest.spyOn(dispatcher, "dispatch"); const dispatchFireSpy = jest.spyOn(dispatcher, "fire"); @@ -318,4 +322,87 @@ describe("ElectronPlatform", () => { ); }); }); + + describe("authenticated media", () => { + it("should respond to relevant ipc requests", async () => { + const cli = stubClient(); + mocked(cli.getAccessToken).mockReturnValue("access_token"); + mocked(cli.getHomeserverUrl).mockReturnValue("homeserver_url"); + mocked(cli.getVersions).mockResolvedValue({ + versions: ["v1.1"], + unstable_features: {}, + }); + + new ElectronPlatform(); + + const userAccessTokenCall = mockElectron.on.mock.calls.find((call) => call[0] === "userAccessToken"); + userAccessTokenCall![1]({} as any); + const userAccessTokenResponse = mockElectron.send.mock.calls.find((call) => call[0] === "userAccessToken"); + expect(userAccessTokenResponse![1]).toBe("access_token"); + + const homeserverUrlCall = mockElectron.on.mock.calls.find((call) => call[0] === "homeserverUrl"); + homeserverUrlCall![1]({} as any); + const homeserverUrlResponse = mockElectron.send.mock.calls.find((call) => call[0] === "homeserverUrl"); + expect(homeserverUrlResponse![1]).toBe("homeserver_url"); + + const serverSupportedVersionsCall = mockElectron.on.mock.calls.find( + (call) => call[0] === "serverSupportedVersions", + ); + await (serverSupportedVersionsCall![1]({} as any) as unknown as Promise); + const serverSupportedVersionsResponse = mockElectron.send.mock.calls.find( + (call) => call[0] === "serverSupportedVersions", + ); + expect(serverSupportedVersionsResponse![1]).toEqual({ versions: ["v1.1"], unstable_features: {} }); + }); + }); + + describe("settings", () => { + let platform: ElectronPlatform; + beforeAll(async () => { + window.electron = mockElectron; + platform = new ElectronPlatform(); + await platform.getConfig(); // await init + }); + + it("supportsSetting should return true for the platform", () => { + expect(platform.supportsSetting()).toBe(true); + }); + + it("supportsSetting should return true for available settings", () => { + expect(platform.supportsSetting("setting2")).toBe(true); + }); + + it("supportsSetting should return false for unavailable settings", () => { + expect(platform.supportsSetting("setting1")).toBe(false); + }); + + it("should read setting value over ipc", async () => { + mockElectron.getSettingValue.mockResolvedValue("value"); + await expect(platform.getSettingValue("setting2")).resolves.toEqual("value"); + expect(mockElectron.getSettingValue).toHaveBeenCalledWith("setting2"); + }); + + it("should write setting value over ipc", async () => { + await platform.setSettingValue("setting2", "newValue"); + expect(mockElectron.setSettingValue).toHaveBeenCalledWith("setting2", "newValue"); + }); + }); + + it("should forward call_state dispatcher events via ipc", async () => { + new ElectronPlatform(); + + dispatcher.dispatch( + { + action: "call_state", + state: "connected", + }, + true, + ); + + const ipcMessage = mockElectron.send.mock.calls.find((call) => call[0] === "app_onAction"); + expect(ipcMessage![1]).toEqual({ + action: "call_state", + state: "connected", + }); + }); }); diff --git a/yarn.lock b/yarn.lock index fa591161dc..4a9c8c9fd5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -28,20 +28,20 @@ "@jridgewell/trace-mapping" "^0.3.24" "@axe-core/playwright@^4.10.1": - version "4.10.1" - resolved "https://registry.yarnpkg.com/@axe-core/playwright/-/playwright-4.10.1.tgz#c811ba8bfa244833cce422c4131e0043828c42cc" - integrity sha512-EV5t39VV68kuAfMKqb/RL+YjYKhfuGim9rgIaQ6Vntb2HgaCaau0h98Y3WEUqW1+PbdzxDtDNjFAipbtZuBmEA== + version "4.10.2" + resolved "https://registry.yarnpkg.com/@axe-core/playwright/-/playwright-4.10.2.tgz#f2d364986ce2ce58ef845c5def62ae7444c1d3aa" + integrity sha512-6/b5BJjG6hDaRNtgzLIfKr5DfwyiLHO4+ByTLB0cJgWSM8Ll7KqtdblIS6bEkwSF642/Ex91vNqIl3GLXGlceg== dependencies: - axe-core "~4.10.2" + axe-core "~4.10.3" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.26.2": - version "7.26.2" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" - integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" + integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== dependencies: - "@babel/helper-validator-identifier" "^7.25.9" + "@babel/helper-validator-identifier" "^7.27.1" js-tokens "^4.0.0" - picocolors "^1.0.0" + picocolors "^1.1.1" "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.25.7": version "7.25.7" @@ -51,34 +51,34 @@ "@babel/highlight" "^7.25.7" picocolors "^1.0.0" -"@babel/code-frame@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" - integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== +"@babel/code-frame@^7.26.2": + version "7.26.2" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" + integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== dependencies: - "@babel/helper-validator-identifier" "^7.27.1" + "@babel/helper-validator-identifier" "^7.25.9" js-tokens "^4.0.0" - picocolors "^1.1.1" + picocolors "^1.0.0" "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.27.2": - version "7.27.3" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.27.3.tgz#cc49c2ac222d69b889bf34c795f537c0c6311111" - integrity sha512-V42wFfx1ymFte+ecf6iXghnnP8kWTO+ZLXIyZq+1LAXHHvTZdVxicn4yiVYdYMGaCO3tmqub11AorKkv+iodqw== + version "7.27.5" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.27.5.tgz#7d0658ec1a8420fc866d1df1b03bea0e79934c82" + integrity sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg== "@babel/core@^7.0.0", "@babel/core@^7.11.6", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.18.5", "@babel/core@^7.21.3", "@babel/core@^7.23.9", "@babel/core@^7.24.4": - version "7.27.3" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.27.3.tgz#d7d05502bccede3cab36373ed142e6a1df554c2f" - integrity sha512-hyrN8ivxfvJ4i0fIJuV4EOlV0WDMz5Ui4StRTgVaAvWeiRCilXgwVvxJKtFQ3TKtHgJscB2YiXKGNJuVwhQMtA== + version "7.27.4" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.27.4.tgz#cc1fc55d0ce140a1828d1dd2a2eba285adbfb3ce" + integrity sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g== dependencies: "@ampproject/remapping" "^2.2.0" "@babel/code-frame" "^7.27.1" "@babel/generator" "^7.27.3" "@babel/helper-compilation-targets" "^7.27.2" "@babel/helper-module-transforms" "^7.27.3" - "@babel/helpers" "^7.27.3" - "@babel/parser" "^7.27.3" + "@babel/helpers" "^7.27.4" + "@babel/parser" "^7.27.4" "@babel/template" "^7.27.2" - "@babel/traverse" "^7.27.3" + "@babel/traverse" "^7.27.4" "@babel/types" "^7.27.3" convert-source-map "^2.0.0" debug "^4.1.0" @@ -87,9 +87,9 @@ semver "^6.3.1" "@babel/eslint-parser@^7.12.10": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.27.1.tgz#e146fb2facef62c6c5d1a6fd07cfd79ee9f7b0f1" - integrity sha512-q8rjOuadH0V6Zo4XLMkJ3RMQ9MSBqwaDByyYB0izsYdaIWGNLmEblbCOf1vyFHICcg16CD7Fsi51vcQnYxmt6Q== + version "7.27.5" + resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.27.5.tgz#56577afa9d820e9936e986d3a3b79c422223dfc6" + integrity sha512-HLkYQfRICudzcOtjGwkPvGc5nF1b4ljLZh1IRDj50lRZ718NAKVgQpIAUX8bfg6u/yuSKY3L7E0YzIV+OxrB8Q== dependencies: "@nicolo-ribaudo/eslint-scope-5-internals" "5.1.1-v1" eslint-visitor-keys "^2.1.0" @@ -124,11 +124,11 @@ jsesc "^3.0.2" "@babel/generator@^7.27.3": - version "7.27.3" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.27.3.tgz#ef1c0f7cfe3b5fc8cbb9f6cc69f93441a68edefc" - integrity sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q== + version "7.27.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.27.5.tgz#3eb01866b345ba261b04911020cbe22dd4be8c8c" + integrity sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw== dependencies: - "@babel/parser" "^7.27.3" + "@babel/parser" "^7.27.5" "@babel/types" "^7.27.3" "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.25" @@ -341,13 +341,13 @@ "@babel/traverse" "^7.27.1" "@babel/types" "^7.27.1" -"@babel/helpers@^7.27.3": - version "7.27.3" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.27.3.tgz#387d65d279290e22fe7a47a8ffcd2d0c0184edd0" - integrity sha512-h/eKy9agOya1IGuLaZ9tEUgz+uIRXcbtOhRtUyyMf8JFmn1iT13vnl/IGVWSkdOCG/pC57U4S1jnAabAavTMwg== +"@babel/helpers@^7.27.4": + version "7.27.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.27.6.tgz#6456fed15b2cb669d2d1fabe84b66b34991d812c" + integrity sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug== dependencies: "@babel/template" "^7.27.2" - "@babel/types" "^7.27.3" + "@babel/types" "^7.27.6" "@babel/highlight@^7.25.7": version "7.25.9" @@ -380,10 +380,10 @@ dependencies: "@babel/types" "^7.27.0" -"@babel/parser@^7.27.2", "@babel/parser@^7.27.3": - version "7.27.3" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.27.3.tgz#1b7533f0d908ad2ac545c4d05cbe2fb6dc8cfaaf" - integrity sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw== +"@babel/parser@^7.27.2", "@babel/parser@^7.27.4", "@babel/parser@^7.27.5": + version "7.27.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.27.5.tgz#ed22f871f110aa285a6fd934a0efed621d118826" + integrity sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg== dependencies: "@babel/types" "^7.27.3" @@ -992,9 +992,9 @@ "@babel/helper-plugin-utils" "^7.27.1" "@babel/plugin-transform-runtime@^7.12.10": - version "7.27.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.27.3.tgz#ad35f1eff5ba18a5e23f7270e939fb5a59d3ec0b" - integrity sha512-bA9ZL5PW90YwNgGfjg6U+7Qh/k3zCEQJ06BFgAGRp/yMjw9hP9UGbGPtx3KSOkHGljEPCCxaE+PH4fUR2h1sDw== + version "7.27.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.27.4.tgz#dee5c5db6543313d1ae1b4b1ec122ff1e77352b9" + integrity sha512-D68nR5zxU64EUzV8i7T3R5XP0Xhrou/amNnddsRQssx6GrTLdZl1rLxyjtVZBd+v/NVX4AbTPOB5aU8thAZV1A== dependencies: "@babel/helper-module-imports" "^7.27.1" "@babel/helper-plugin-utils" "^7.27.1" @@ -1189,9 +1189,9 @@ "@babel/plugin-transform-typescript" "^7.27.1" "@babel/runtime@^7.0.0", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.9", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": - version "7.27.3" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.3.tgz#10491113799fb8d77e1d9273384d5d68deeea8f6" - integrity sha512-7EYtGezsdiDMyY80+65EzwiGmcJqpmcZCojSXaRgdrBaGtWTgDZKq69cPIVped6MkIM78cTQ2GOiEYjwOlG4xw== + version "7.27.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.6.tgz#ec4070a04d76bae8ddbb10770ba55714a417b7c6" + integrity sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q== "@babel/template@^7.25.7", "@babel/template@^7.27.1", "@babel/template@^7.27.2", "@babel/template@^7.3.3": version "7.27.2" @@ -1250,14 +1250,14 @@ debug "^4.3.1" globals "^11.1.0" -"@babel/traverse@^7.27.1", "@babel/traverse@^7.27.3": - version "7.27.3" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.27.3.tgz#8b62a6c2d10f9d921ba7339c90074708509cffae" - integrity sha512-lId/IfN/Ye1CIu8xG7oKBHXd2iNb2aW1ilPszzGcJug6M8RCKfVNcYhpI5+bMvFYjK7lXIM0R+a+6r8xhHp2FQ== +"@babel/traverse@^7.27.1", "@babel/traverse@^7.27.3", "@babel/traverse@^7.27.4": + version "7.27.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.27.4.tgz#b0045ac7023c8472c3d35effd7cc9ebd638da6ea" + integrity sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA== dependencies: "@babel/code-frame" "^7.27.1" "@babel/generator" "^7.27.3" - "@babel/parser" "^7.27.3" + "@babel/parser" "^7.27.4" "@babel/template" "^7.27.2" "@babel/types" "^7.27.3" debug "^4.3.1" @@ -1296,10 +1296,10 @@ "@babel/helper-string-parser" "^7.25.9" "@babel/helper-validator-identifier" "^7.25.9" -"@babel/types@^7.27.1", "@babel/types@^7.27.3": - version "7.27.3" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.3.tgz#c0257bedf33aad6aad1f406d35c44758321eb3ec" - integrity sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw== +"@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.27.6": + version "7.27.6" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.6.tgz#a434ca7add514d4e646c80f7375c0aa2befc5535" + integrity sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q== dependencies: "@babel/helper-string-parser" "^7.27.1" "@babel/helper-validator-identifier" "^7.27.1" @@ -1354,19 +1354,19 @@ "@csstools/css-calc" "^2.1.1" "@csstools/css-parser-algorithms@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz#74426e93bd1c4dcab3e441f5cc7ba4fb35d94356" - integrity sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A== + version "3.0.5" + resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz#5755370a9a29abaec5515b43c8b3f2cf9c2e3076" + integrity sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ== "@csstools/css-tokenizer@^3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz#a5502c8539265fecbd873c1e395a890339f119c2" - integrity sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw== + version "3.0.4" + resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz#333fedabc3fd1a8e5d0100013731cf19e6a8c5d3" + integrity sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw== "@csstools/media-query-list-parser@^4.0.2": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.2.tgz#e80e17eba1693fceafb8d6f2cfc68c0e7a9ab78a" - integrity sha512-EUos465uvVvMJehckATTlNqGj4UJWkTmdWuDMjqvSUkjGpmOyFZBVwb4knxCm/k2GMTXY+c/5RkdndzFYWeX5A== + version "4.0.3" + resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.3.tgz#7aec77bcb89c2da80ef207e73f474ef9e1b3cdf1" + integrity sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ== "@csstools/postcss-cascade-layers@^5.0.1": version "5.0.1" @@ -1667,10 +1667,10 @@ resolved "https://registry.yarnpkg.com/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz#519c1549b0e147759e7825701ecffd25e5819f7b" integrity sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg== -"@element-hq/element-call-embedded@0.12.0": - version "0.12.0" - resolved "https://registry.yarnpkg.com/@element-hq/element-call-embedded/-/element-call-embedded-0.12.0.tgz#654da28610c758c6d1164bbfe61007a636357cca" - integrity sha512-p/ZymzOqrF5WDnt7ZIkpdtQwzUcwchykIGcU9quU1YkAwOCnGwqCwGmgG0YAvPEhqWAb4DVJ4qHL0ZZ+WqxAtQ== +"@element-hq/element-call-embedded@0.12.2": + version "0.12.2" + resolved "https://registry.yarnpkg.com/@element-hq/element-call-embedded/-/element-call-embedded-0.12.2.tgz#b6b6b7df69369b3088960b79591ce1bfd2b84a1a" + integrity sha512-2u5/bOARcjc5TFq4929x1R0tvsNbeVA58FBtiW05GlIJCapxzPSOeeGhbqEcJ1TW3/hLGpiKMcw0QwRBQVNzQA== "@element-hq/element-web-module-api@1.0.0": version "1.0.0" @@ -1678,9 +1678,9 @@ integrity sha512-FYId5tYgaKvpqAXRXqs0pY4+7/A09bEl1mCxFqlS9jlZOCjlMZVvZuv8spbY8ZN9HaMvuVmx9J00Fn2gCJd0TQ== "@element-hq/element-web-playwright-common@^1.1.5": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@element-hq/element-web-playwright-common/-/element-web-playwright-common-1.2.0.tgz#63808520825899701d9359001430e91b0ca6d1e0" - integrity sha512-I+W+0PThZkwwfP03xT19q6C0jrMqSimD+6QjsKV+DdwJyoJrFfhHD10jil8dWelDMwrhyfk+qLiu6Go7UDgaog== + version "1.3.0" + resolved "https://registry.yarnpkg.com/@element-hq/element-web-playwright-common/-/element-web-playwright-common-1.3.0.tgz#5f57eb2ee25e9733c92185ef57431db808deb603" + integrity sha512-tq3lN77f0KnTYtPVkRrwvsWpq/JAC6JqDkC2f6dxuvX3a+OmyjGnuoZh83kB2WNObfrrx7gxmsZpS7ov2mrw4Q== dependencies: "@axe-core/playwright" "^4.10.1" "@testcontainers/postgresql" "^11.0.0" @@ -1800,14 +1800,14 @@ integrity sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig== "@fontsource/inconsolata@^5": - version "5.2.5" - resolved "https://registry.yarnpkg.com/@fontsource/inconsolata/-/inconsolata-5.2.5.tgz#8f220d83567dc27b7b54fd583044cb2ebbbd2759" - integrity sha512-OvzkZY5qYghv/jEV6cfGZzFhdFTvSnU+ExPC7WcZ7w8PdRhtiu/SpcBWOBt+3LXgS0n9qyepgq4zZmxlDTlGGQ== + version "5.2.6" + resolved "https://registry.yarnpkg.com/@fontsource/inconsolata/-/inconsolata-5.2.6.tgz#441408df72dfe1a2408eb8bf676609043b869504" + integrity sha512-TRGh7bN+BN/oP8qD1IYe8REXM/0Uw3jbuERSncA/ZF6mqKFEOeTt6PR2T3xK7G+65N9pn2p0ablamdboee2nFQ== "@fontsource/inter@^5": - version "5.2.5" - resolved "https://registry.yarnpkg.com/@fontsource/inter/-/inter-5.2.5.tgz#69efffe6ccfae138cbe1806f41daa39cef986b59" - integrity sha512-kbsPKj0S4p44JdYRFiW78Td8Ge2sBVxi/PIBwmih+RpSXUdvS9nbs1HIiuUSPtRMi14CqLEZ/fbk7dj7vni1Sg== + version "5.2.6" + resolved "https://registry.yarnpkg.com/@fontsource/inter/-/inter-5.2.6.tgz#2c705ea57d7af283a05bb1ca59fb0f06fc5de3be" + integrity sha512-CZs9S1CrjD0jPwsNy9W6j0BhsmRSQrgwlTNkgQXTsAeDRM42LBRLo3eo9gCzfH4GvV7zpyf78Ozfl773826csw== "@formatjs/ecma402-abstract@2.3.4": version "2.3.4" @@ -2168,9 +2168,9 @@ thingies "^1.20.0" "@jsonjoy.com/util@^1.1.2", "@jsonjoy.com/util@^1.3.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@jsonjoy.com/util/-/util-1.5.0.tgz#6008e35b9d9d8ee27bc4bfaa70c8cbf33a537b4c" - integrity sha512-ojoNsrIuPI9g6o8UxhraZQSyF2ByJanAY4cTFbc8Mf2AXEF4aQRGY1dJxyJpuyav8r9FGflEt/Ff3u5Nt6YMPA== + version "1.6.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/util/-/util-1.6.0.tgz#23991b2fe12cb3a006573d9dc97c768d3ed2c9f1" + integrity sha512-sw/RMbehRhN68WRtcKCpQOPfnH6lLP4GJfqzi3iYej8tnzpZUDr6UkZYJjcjjC0FWEJOJbyM3PTIwxucUmDG2A== "@keyv/serialize@^1.0.3": version "1.0.3" @@ -2224,10 +2224,10 @@ resolved "https://registry.yarnpkg.com/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz#497c67a1cef50d1a2459ba60f315e448d2ad87fe" integrity sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q== -"@maplibre/maplibre-gl-style-spec@^23.2.2": - version "23.2.2" - resolved "https://registry.yarnpkg.com/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-23.2.2.tgz#9030560d2b92c33132a7cea1d537a154d7674580" - integrity sha512-kLcVlItPCULc20SM6pSVA7u8nST9xmQA8d7utc9j3KB0Tf/xhM4GgCn/QsZcmlbN/wW0ujyomDrvZ3/LbwvAmw== +"@maplibre/maplibre-gl-style-spec@^23.3.0": + version "23.3.0" + resolved "https://registry.yarnpkg.com/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-23.3.0.tgz#b69ab48cb3abead4e49213396c8f83492638b97c" + integrity sha512-IGJtuBbaGzOUgODdBRg66p8stnwj9iDXkgbYKoYcNiiQmaez5WVRfXm4b03MCDwmZyX93csbfHFWEJJYHnn5oA== dependencies: "@mapbox/jsonlint-lines-primitives" "~2.0.2" "@mapbox/unitbezier" "^0.0.1" @@ -2250,10 +2250,10 @@ emojibase "^15.3.1" emojibase-data "^15.3.1" -"@matrix-org/matrix-sdk-crypto-wasm@^14.0.1": - version "14.0.1" - resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-14.0.1.tgz#e258ef84bcc7889f0e7eb3a7dbecf0830a6dd606" - integrity sha512-CgLpHs6nTw5pjSsMBi9xbQnBXf2l8YhImQP9cv8nbGSCYdYjFI0FilMXffzjWV5HThpNHri/3pF20ahZtuS3VA== +"@matrix-org/matrix-sdk-crypto-wasm@^14.2.0": + version "14.2.0" + resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-14.2.0.tgz#1d6bddb2f777ac1674546467aab6f8584a7f2e71" + integrity sha512-xYbH1Yg8YwfXxGsCVDypiRvSVYPnCybsoRqlBDuAvIOs9tOfmdeeJqN+3VxvLWH28g3CtJs+9Afw8dYSHViTFg== "@matrix-org/olm@3.2.15": version "3.2.15" @@ -2272,10 +2272,10 @@ resolved "https://registry.yarnpkg.com/@matrix-org/spec/-/spec-1.14.0.tgz#cedc4f9cc86c9e623ccd4f643629bac97bc0fed2" integrity sha512-UI4kY6WXniRV++ZeMesmCvZMH8lANLc12rnca1/M+nkPgJgU9GQd5X9ezFoOLabG6tGdEUPPKr5/S2EP1FdGLA== -"@napi-rs/wasm-runtime@^0.2.9": - version "0.2.10" - resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.10.tgz#f3b7109419c6670000b2401e0c778b98afc25f84" - integrity sha512-bCsCyeZEwVErsGmyPNSzwfwFn4OdxBj0mmv6hOFucB/k81Ojdu68RbZdxYsRQUPc9l6SU5F/cG+bXgWs3oUgsQ== +"@napi-rs/wasm-runtime@^0.2.11": + version "0.2.11" + resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.11.tgz#192c1610e1625048089ab4e35bc0649ce478500e" + integrity sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA== dependencies: "@emnapi/core" "^1.4.3" "@emnapi/runtime" "^1.4.3" @@ -2309,72 +2309,72 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@oxc-resolver/binding-darwin-arm64@9.0.2": - version "9.0.2" - resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-9.0.2.tgz#345ff11258dbec11d333bf088a79b864f5f03ec5" - integrity sha512-MVyRgP2gzJJtAowjG/cHN3VQXwNLWnY+FpOEsyvDepJki1SdAX/8XDijM1yN6ESD1kr9uhBKjGelC6h3qtT+rA== +"@oxc-resolver/binding-darwin-arm64@11.2.0": + version "11.2.0" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.2.0.tgz#e10cb8530dfa948a2eee889346f459f6c65eebfd" + integrity sha512-ruKLkS+Dm/YIJaUhzEB7zPI+jh3EXxu0QnNV8I7t9jf0lpD2VnltuyRbhrbJEkksklZj//xCMyFFsILGjiU2Mg== -"@oxc-resolver/binding-darwin-x64@9.0.2": - version "9.0.2" - resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-9.0.2.tgz#cd19f263a31b601356002ebd2eb8dda193753704" - integrity sha512-7kV0EOFEZ3sk5Hjy4+bfA6XOQpCwbDiDkkHN4BHHyrBHsXxUR05EcEJPPL1WjItefg+9+8hrBmoK0xRoDs41+A== +"@oxc-resolver/binding-darwin-x64@11.2.0": + version "11.2.0" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.2.0.tgz#d91779b76820037055c9ead87bf3ee21a96e178d" + integrity sha512-0zhgNUm5bYezdSFOg3FYhtVP83bAq7FTV/3suGQDl/43MixfQG7+bl+hlrP4mz6WlD2SUb2u9BomnJWl1uey9w== -"@oxc-resolver/binding-freebsd-x64@9.0.2": - version "9.0.2" - resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-9.0.2.tgz#e6e0b9b9409cb4eb71307ca880f1d868cce88c94" - integrity sha512-6OvkEtRXrt8sJ4aVfxHRikjain9nV1clIsWtJ1J3J8NG1ZhjyJFgT00SCvqxbK+pzeWJq6XzHyTCN78ML+lY2w== +"@oxc-resolver/binding-freebsd-x64@11.2.0": + version "11.2.0" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.2.0.tgz#6149681bb3c3eaf0d92517428d38042e1c56faf2" + integrity sha512-SHOxfCcZV1axeIGfyeD1BkdLvfQgjmPy18tO0OUXGElcdScxD6MqU5rj/AVtiuBT+51GtFfOKlwl1+BdVwhD1A== -"@oxc-resolver/binding-linux-arm-gnueabihf@9.0.2": - version "9.0.2" - resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-9.0.2.tgz#e4ef8e0a831fa05101ca8fc0501cd7b536093e66" - integrity sha512-aYpNL6o5IRAUIdoweW21TyLt54Hy/ZS9tvzNzF6ya1ckOQ8DLaGVPjGpmzxdNja9j/bbV6aIzBH7lNcBtiOTkQ== +"@oxc-resolver/binding-linux-arm-gnueabihf@11.2.0": + version "11.2.0" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.2.0.tgz#7f375b0823374148368e65e4de8a2a3041e6d27c" + integrity sha512-mgEkYrJ+N90sgEDqEZ07zH+4I1D28WjqAhdzfW3aS2x2vynVpoY9jWfHuH8S62vZt3uATJrTKTRa8CjPWEsrdw== -"@oxc-resolver/binding-linux-arm64-gnu@9.0.2": - version "9.0.2" - resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-9.0.2.tgz#c95345cf91b9597a469a8d3028d0b182e2e22055" - integrity sha512-RGFW4vCfKMFEIzb9VCY0oWyyY9tR1/o+wDdNePhiUXZU4SVniRPQaZ1SJ0sUFI1k25pXZmzQmIP6cBmazi/Dew== +"@oxc-resolver/binding-linux-arm64-gnu@11.2.0": + version "11.2.0" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.2.0.tgz#aa70d17bdf6418d3fe309b11c1514e25e92bd7c7" + integrity sha512-BhEzNLjn4HjP8+Q18D3/jeIDBxW7OgoJYIjw2CaaysnYneoTlij8hPTKxHfyqq4IGM3fFs9TLR/k338M3zkQ7g== -"@oxc-resolver/binding-linux-arm64-musl@9.0.2": - version "9.0.2" - resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-9.0.2.tgz#d968aadd446a6c1d729764f3ddd0e1b66e3ec53f" - integrity sha512-lxx/PibBfzqYvut2Y8N2D0Ritg9H8pKO+7NUSJb9YjR/bfk2KRmP8iaUz3zB0JhPtf/W3REs65oKpWxgflGToA== +"@oxc-resolver/binding-linux-arm64-musl@11.2.0": + version "11.2.0" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.2.0.tgz#5065ba7151d316d45b209b55fc37f3ecfcb9f9bd" + integrity sha512-yxbMYUgRmN2V8x8XoxmD/Qq6aG7YIW3ToMDILfmcfeeRRVieEJ3DOWBT0JSE+YgrOy79OyFDH/1lO8VnqLmDQQ== -"@oxc-resolver/binding-linux-riscv64-gnu@9.0.2": - version "9.0.2" - resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-9.0.2.tgz#698f364bf15489e69c8441c842a09a468c7389ca" - integrity sha512-yD28ptS/OuNhwkpXRPNf+/FvrO7lwURLsEbRVcL1kIE0GxNJNMtKgIE4xQvtKDzkhk6ZRpLho5VSrkkF+3ARTQ== +"@oxc-resolver/binding-linux-riscv64-gnu@11.2.0": + version "11.2.0" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.2.0.tgz#63aaddff4d065d8a90425e1cc00e1d1410ea9b48" + integrity sha512-QG1UfgC2N2qhW1tOnDCgB/26vn1RCshR5sYPhMeaxO1gMQ3kEKbZ3QyBXxrG1IX5qsXYj5hPDJLDYNYUjRcOpg== -"@oxc-resolver/binding-linux-s390x-gnu@9.0.2": - version "9.0.2" - resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-9.0.2.tgz#dfcbf0531f0ade1197275f40e6f8900635af187c" - integrity sha512-WBwEJdspoga2w+aly6JVZeHnxuPVuztw3fPfWrei2P6rNM5hcKxBGWKKT6zO1fPMCB4sdDkFohGKkMHVV1eryQ== +"@oxc-resolver/binding-linux-s390x-gnu@11.2.0": + version "11.2.0" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.2.0.tgz#ab007ce62814b783f55a1ae7c204d841bd72e2af" + integrity sha512-uqTDsQdi6mrkSV1gvwbuT8jf/WFl6qVDVjNlx7IPSaAByrNiJfPrhTmH8b+Do58Dylz7QIRZgxQ8CHIZSyBUdg== -"@oxc-resolver/binding-linux-x64-gnu@9.0.2": - version "9.0.2" - resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-9.0.2.tgz#73f7157f3b052c44c206b672b970da060125c533" - integrity sha512-a2z3/cbOOTUq0UTBG8f3EO/usFcdwwXnCejfXv42HmV/G8GjrT4fp5+5mVDoMByH3Ce3iVPxj1LmS6OvItKMYQ== +"@oxc-resolver/binding-linux-x64-gnu@11.2.0": + version "11.2.0" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.2.0.tgz#71543385bcd557201a1fb27acb899c7d09ad3502" + integrity sha512-GZdHXhJ7p6GaQg9MjRqLebwBf8BLvGIagccI6z5yMj4fV3LU4QuDfwSEERG+R6oQ/Su9672MBqWwncvKcKT68w== -"@oxc-resolver/binding-linux-x64-musl@9.0.2": - version "9.0.2" - resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-9.0.2.tgz#2b047ce69cb7fa1900174d43e4a7b6027146a449" - integrity sha512-bHZF+WShYQWpuswB9fyxcgMIWVk4sZQT0wnwpnZgQuvGTZLkYJ1JTCXJMtaX5mIFHf69ngvawnwPIUA4Feil0g== +"@oxc-resolver/binding-linux-x64-musl@11.2.0": + version "11.2.0" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.2.0.tgz#28876735f3bd99d89363885ee6a35f36246bd0b2" + integrity sha512-YBAC3GOicYznReG2twE7oFPSeK9Z1f507z1EYWKg6HpGYRYRlJyszViu7PrhMT85r/MumDTs429zm+CNqpFWOA== -"@oxc-resolver/binding-wasm32-wasi@9.0.2": - version "9.0.2" - resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-9.0.2.tgz#df8eab1815ae0da0c70f0f9dda4bcd84c70d7024" - integrity sha512-I5cSgCCh5nFozGSHz+PjIOfrqW99eUszlxKLgoNNzQ1xQ2ou9ZJGzcZ94BHsM9SpyYHLtgHljmOZxCT9bgxYNA== +"@oxc-resolver/binding-wasm32-wasi@11.2.0": + version "11.2.0" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.2.0.tgz#68684ab71e727c870655fd3694ea5b8806b87516" + integrity sha512-+qlIg45CPVPy+Jn3vqU1zkxA/AAv6e/2Ax/ImX8usZa8Tr2JmQn/93bmSOOOnr9fXRV9d0n4JyqYzSWxWPYDEw== dependencies: - "@napi-rs/wasm-runtime" "^0.2.9" + "@napi-rs/wasm-runtime" "^0.2.11" -"@oxc-resolver/binding-win32-arm64-msvc@9.0.2": - version "9.0.2" - resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-9.0.2.tgz#9530c2e08ebb5d02870b004135ef0b4438a620b7" - integrity sha512-5IhoOpPr38YWDWRCA5kP30xlUxbIJyLAEsAK7EMyUgqygBHEYLkElaKGgS0X5jRXUQ6l5yNxuW73caogb2FYaw== +"@oxc-resolver/binding-win32-arm64-msvc@11.2.0": + version "11.2.0" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.2.0.tgz#ef9ced6045c60a77357fdd62205e76e0879d9fb0" + integrity sha512-AI4KIpS8Zf6vwfOPk0uQPSC0pQ1m5HU4hCbtrgL21JgJSlnJaeEu3/aoOBB45AXKiExBU9R+CDR7aSnW7uhc5A== -"@oxc-resolver/binding-win32-x64-msvc@9.0.2": - version "9.0.2" - resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-9.0.2.tgz#15dc9cecd2e89bbcad07247477fc04dd8c3ffbbe" - integrity sha512-Qc40GDkaad9rZksSQr2l/V9UubigIHsW69g94Gswc2sKYB3XfJXfIfyV8WTJ67u6ZMXsZ7BH1msSC6Aen75mCg== +"@oxc-resolver/binding-win32-x64-msvc@11.2.0": + version "11.2.0" + resolved "https://registry.yarnpkg.com/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.2.0.tgz#24af4f17b40b1e6c08e9fa3bdfcaaf5d64bfcd64" + integrity sha512-r19cQc7HaEJ76HFsMsbiKMTIV2YqFGSof8H5hB7e5Jkb/23Y8Isv1YrSzkDaGhcw02I/COsrPo+eEmjy35eFuA== "@peculiar/asn1-schema@^2.3.13", "@peculiar/asn1-schema@^2.3.8": version "2.3.13" @@ -2769,35 +2769,35 @@ resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8" integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g== -"@sentry-internal/browser-utils@9.23.0": - version "9.23.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-9.23.0.tgz#19cd82bc42654883190423126e666c6e5db66b43" - integrity sha512-hyN2Q6mh7ggw8sDVHeRyWz5LR6gjvf8zHSzQnMaF7QkeSyaeGM/SVSL4ODwqR9TRH7U2ku6nZFMbKhaBPV+Hfg== +"@sentry-internal/browser-utils@9.27.0": + version "9.27.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-9.27.0.tgz#1860a9925aadb700fc273d59d4d6e7083408f765" + integrity sha512-SJa7f6Ct1BzP8rWEomnshSGN1CmT+axNKvT+StrbFPD6AyHnYfFLJpKgc2iToIJHB/pmeuOI9dUwqtzVx+5nSw== dependencies: - "@sentry/core" "9.23.0" + "@sentry/core" "9.27.0" -"@sentry-internal/feedback@9.23.0": - version "9.23.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-9.23.0.tgz#088c52187cb1a9bb0b22c3df14a6d0b8fafec0e4" - integrity sha512-Xf+KqV69TBiPo1gk2EsU6O/dumuTMxWOF52uVWJddQYI3pQTU5DqSeoZ5AY76bIIhV9n6AEFDGqNPXmuj4Acrw== +"@sentry-internal/feedback@9.27.0": + version "9.27.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-9.27.0.tgz#ec9311033b401f4f89304c47ffde2de942a281ef" + integrity sha512-e7L8eG0y63RulN352lmafoCCfQGg4jLVT8YLx6096eWu/YKLkgmVpgi8livsT5WREnH+HB+iFSrejOwK7cRkhw== dependencies: - "@sentry/core" "9.23.0" + "@sentry/core" "9.27.0" -"@sentry-internal/replay-canvas@9.23.0": - version "9.23.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-9.23.0.tgz#7e384e8ab50d99d363c5e01cd255e04af1b9623b" - integrity sha512-cYlw5svJjyPequm0PJjFGLpee86L1NieONEHlujOXkIG6IEriiorMm+8bNpGsHRuyvg41B+4P/YmcQAGtEGxXg== +"@sentry-internal/replay-canvas@9.27.0": + version "9.27.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-9.27.0.tgz#d18e95ace5c6bd593935cacea1980b9e640b3afe" + integrity sha512-44rVSt3LCH6qePYRQrl4WUBwnkOk9dzinmnKmuwRksEdDOkVq5KBRhi/IDr7omwSpX8C+KrX5alfKhOx1cP0gQ== dependencies: - "@sentry-internal/replay" "9.23.0" - "@sentry/core" "9.23.0" + "@sentry-internal/replay" "9.27.0" + "@sentry/core" "9.27.0" -"@sentry-internal/replay@9.23.0": - version "9.23.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-9.23.0.tgz#6b1369a8712323666fd04a4f86d3ab8865ff4f37" - integrity sha512-0/q15tvSboaK7/05BFQhs71bqgHKejJoDJgXmH0lySqgsRh/S18867ZxQNiuYhuVt337h07u1QaCyjnNJKHfuA== +"@sentry-internal/replay@9.27.0": + version "9.27.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-9.27.0.tgz#e357d45a6fc2b80ac312798cc5b1b758c5ca9c66" + integrity sha512-n2kO1wOfCG7GxkMAqbYYkpgTqJM5tuVLdp0JuNCqTOLTXWvw6svWGaYKlYpKUgsK9X/GDzJYSXZmfe+Dbg+FJQ== dependencies: - "@sentry-internal/browser-utils" "9.23.0" - "@sentry/core" "9.23.0" + "@sentry-internal/browser-utils" "9.27.0" + "@sentry/core" "9.27.0" "@sentry/babel-plugin-component-annotate@3.5.0": version "3.5.0" @@ -2805,15 +2805,15 @@ integrity sha512-s2go8w03CDHbF9luFGtBHKJp4cSpsQzNVqgIa9Pfa4wnjipvrK6CxVT4icpLA3YO6kg5u622Yoa5GF3cJdippw== "@sentry/browser@^9.0.0": - version "9.23.0" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-9.23.0.tgz#8fdc4e393c8b893ca467d577bb7b11d038650641" - integrity sha512-QRkNxWys8e088260vByztoTsEVZ0W6v/XnZfKT6wkPPGn0aFeOrg/xjgxfI8D5huqZCxT28Cf23akOOly4FXjg== + version "9.27.0" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-9.27.0.tgz#5aa87a34600aa7ee19e29d2295266c18a32d7bc7" + integrity sha512-geR3lhRJOmUQqi1WgovLSYcD/f66zYnctdnDEa7j1BW2XIB1nlTJn0mpYyAHghXKkUN/pBpp1Z+Jk0XlVwFYVg== dependencies: - "@sentry-internal/browser-utils" "9.23.0" - "@sentry-internal/feedback" "9.23.0" - "@sentry-internal/replay" "9.23.0" - "@sentry-internal/replay-canvas" "9.23.0" - "@sentry/core" "9.23.0" + "@sentry-internal/browser-utils" "9.27.0" + "@sentry-internal/feedback" "9.27.0" + "@sentry-internal/replay" "9.27.0" + "@sentry-internal/replay-canvas" "9.27.0" + "@sentry/core" "9.27.0" "@sentry/bundler-plugin-core@3.5.0": version "3.5.0" @@ -2883,10 +2883,10 @@ "@sentry/cli-win32-i686" "2.42.2" "@sentry/cli-win32-x64" "2.42.2" -"@sentry/core@9.23.0": - version "9.23.0" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-9.23.0.tgz#96a514cc9d8ac043dea0a4648ed1205c25ff5ae5" - integrity sha512-9846pn/BvASGgl7WsnKY4xry98WreP9ToeLfCQTQOf+pNfD/qNPn1/0xPInGni3LVMAXRtfHHMPm2Ghz255N7A== +"@sentry/core@9.27.0": + version "9.27.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-9.27.0.tgz#e97d6957c243d95f9f05ccb553b937838bb500ea" + integrity sha512-Zb2SSAdWXQjTem+sVWrrAq9L6YYfxyoTwtapaE6C6qZBR5C8Uak0wcYww8StaCFH7dDA/PSW+VxOwjNXocrQHQ== "@sentry/webpack-plugin@^3.0.0": version "3.5.0" @@ -2917,9 +2917,9 @@ "@sinonjs/commons" "^3.0.0" "@stylistic/eslint-plugin@^4.0.0": - version "4.4.0" - resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin/-/eslint-plugin-4.4.0.tgz#e1a3c9fd7109411d32dc0bcb575d2b4066fbbc63" - integrity sha512-bIh/d9X+OQLCAMdhHtps+frvyjvAM4B1YlSJzcEEhl7wXLIqPar3ngn9DrHhkBOrTA/z9J0bUMtctAspe0dxdQ== + version "4.4.1" + resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin/-/eslint-plugin-4.4.1.tgz#410ac332887fb3d61cad1df4e6b55ae35d87c632" + integrity sha512-CEigAk7eOLyHvdgmpZsKFwtiqS2wFwI1fn4j09IU9GmD4euFM4jEBAViWeCqaNLlbX2k2+A/Fq9cje4HQBXuJQ== dependencies: "@typescript-eslint/utils" "^8.32.1" eslint-visitor-keys "^4.2.0" @@ -3034,11 +3034,11 @@ "@svgr/plugin-svgo" "8.1.0" "@testcontainers/postgresql@^11.0.0": - version "11.0.0" - resolved "https://registry.yarnpkg.com/@testcontainers/postgresql/-/postgresql-11.0.0.tgz#6590f2398fae2d9ad78a748e92b89053d2380f56" - integrity sha512-cw2vIz5MS1AjXkxcPmOQh0NRdh7p6frSLOkjsBFuR5lUo3qnV3LmvCfanyRzgXs5aHGJLwc0mlQNfXnKedBF6Q== + version "11.0.3" + resolved "https://registry.yarnpkg.com/@testcontainers/postgresql/-/postgresql-11.0.3.tgz#53387b2706ef519afdbe9064c025f51a62b4471d" + integrity sha512-nMq0lBLguZecp9JDV4J8nlPWwxOQ4XiXUIGkV5Lvj1CMdVqbqQJe4eVrNHVC3rIM/w8nadNp5ecEoxnwkhG33w== dependencies: - testcontainers "^11.0.0" + testcontainers "^11.0.3" "@testing-library/dom@^10.4.0": version "10.4.0" @@ -3155,9 +3155,9 @@ "@babel/types" "^7.20.7" "@types/body-parser@*": - version "1.19.5" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4" - integrity sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg== + version "1.19.6" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.6.tgz#1859bebb8fd7dac9918a45d54c1971ab8b5af474" + integrity sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g== dependencies: "@types/connect" "*" "@types/node" "*" @@ -3213,9 +3213,9 @@ "@types/ssh2" "*" "@types/dockerode@^3.3.39": - version "3.3.39" - resolved "https://registry.yarnpkg.com/@types/dockerode/-/dockerode-3.3.39.tgz#629adef5db9b3bb955423db1ccd586ee2b36e2e7" - integrity sha512-uMPmxehH6ofeYjaslASPtjvyH8FRJdM9fZ+hjhGzL4Jq3bGjr9D7TKmp9soSwgFncNk0HOwmyBxjqOb3ikjjsA== + version "3.3.40" + resolved "https://registry.yarnpkg.com/@types/dockerode/-/dockerode-3.3.40.tgz#5b1055bf4fd5cf106c545b4478d611686fb1cc95" + integrity sha512-O1ckSFYbcYv/KcnAHMLCnKQYY8/5+6CRzpsOPcQIePHRX2jG4Gmz8uXPMCXIxTGN9OYkE5eox/L67l2sGY1UYg== dependencies: "@types/docker-modem" "*" "@types/node" "*" @@ -3273,18 +3273,18 @@ "@types/send" "*" "@types/express@*", "@types/express@^5.0.0": - version "5.0.2" - resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.2.tgz#7be9e337a5745d6b43ef5b0c352dad94a7f0c256" - integrity sha512-BtjL3ZwbCQriyb0DGw+Rt12qAXPiBTPs815lsUvtt1Grk0vLRMZNMUZ741d5rjk+UQOxfDiBZ3dxpX00vSkK3g== + version "5.0.3" + resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.3.tgz#6c4bc6acddc2e2a587142e1d8be0bce20757e956" + integrity sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw== dependencies: "@types/body-parser" "*" "@types/express-serve-static-core" "^5.0.0" "@types/serve-static" "*" "@types/express@^4.17.21": - version "4.17.21" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d" - integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ== + version "4.17.23" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.23.tgz#35af3193c640bfd4d7fe77191cd0ed411a433bef" + integrity sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ== dependencies: "@types/body-parser" "*" "@types/express-serve-static-core" "^4.17.33" @@ -3334,9 +3334,9 @@ integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg== "@types/http-errors@*": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f" - integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== + version "2.0.5" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.5.tgz#5b749ab2b16ba113423feb1a64a95dcd30398472" + integrity sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== "@types/http-proxy@^1.17.8": version "1.17.16" @@ -3456,16 +3456,16 @@ "@types/node" "*" "@types/node@*", "@types/node@>=13.7.0": - version "22.15.23" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.23.tgz#a0b7c03f951f1ffe381a6a345c68d80e48043dd0" - integrity sha512-7Ec1zaFPF4RJ0eXu1YT/xgiebqwqoJz8rYPDi/O2BcZ++Wpt0Kq9cl0eg6NN6bYbPnR67ZLo7St5Q3UK0SnARw== + version "24.0.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-24.0.0.tgz#14a278ce74dd33993f2c4e5dd614760728c0fba8" + integrity sha512-yZQa2zm87aRVcqDyH5+4Hv9KYgSdgwX1rFnGvpbzMaC7YAljmhBET93TPiTd3ObwTL+gSpIzPKg5BqVxdCvxKg== dependencies: - undici-types "~6.21.0" + undici-types "~7.8.0" "@types/node@18", "@types/node@^18.11.18": - version "18.19.105" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.105.tgz#44bae77ea9832da4357e1d35df37cb86927e1405" - integrity sha512-a+DrwD2VyzqQR2W0EVF8EaCh6Em4ilQAYLEPZnMNkQHXR7ziWW7RUhZMWZAgRpkDDAdUIcJOXSPJT/zBEwz3sA== + version "18.19.111" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.111.tgz#e95b89efc24cc625834b43bcd70bd5591a5dfba5" + integrity sha512-90sGdgA+QLJr1F9X79tQuEut0gEYIfkX9pydI4XGRgvFo9g2JWswefI+WUSUHPYVBHYSEfTEqBxA5hQvAZB3Mw== dependencies: undici-types "~5.26.4" @@ -3518,10 +3518,10 @@ dependencies: "@types/react" "*" -"@types/react-dom@19.1.5": - version "19.1.5" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.1.5.tgz#cdfe2c663742887372f54804b16e8dbc26bd794a" - integrity sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg== +"@types/react-dom@19.1.6": + version "19.1.6" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.1.6.tgz#4af629da0e9f9c0f506fc4d1caa610399c595d64" + integrity sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw== "@types/react-redux@^7.1.20": version "7.1.34" @@ -3581,9 +3581,9 @@ integrity sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA== "@types/send@*": - version "0.17.4" - resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a" - integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA== + version "0.17.5" + resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.5.tgz#d991d4f2b16f2b1ef497131f00a9114290791e74" + integrity sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w== dependencies: "@types/mime" "^1" "@types/node" "*" @@ -3596,9 +3596,9 @@ "@types/express" "*" "@types/serve-static@*", "@types/serve-static@^1.15.5": - version "1.15.7" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.7.tgz#22174bbd74fb97fe303109738e9b5c2f3064f714" - integrity sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw== + version "1.15.8" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.8.tgz#8180c3fbe4a70e8f00b9f70b9ba7f08f35987877" + integrity sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg== dependencies: "@types/http-errors" "*" "@types/node" "*" @@ -3685,38 +3685,38 @@ "@types/yargs-parser" "*" "@typescript-eslint/eslint-plugin@^8.19.0": - version "8.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.0.tgz#51ed03649575ba51bcee7efdbfd85283249b5447" - integrity sha512-CACyQuqSHt7ma3Ns601xykeBK/rDeZa3w6IS6UtMQbixO5DWy+8TilKkviGDH6jtWCo8FGRKEK5cLLkPvEammQ== + version "8.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.0.tgz#96c9f818782fe24cd5883a5d517ca1826d3fa9c2" + integrity sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w== dependencies: "@eslint-community/regexpp" "^4.10.0" - "@typescript-eslint/scope-manager" "8.33.0" - "@typescript-eslint/type-utils" "8.33.0" - "@typescript-eslint/utils" "8.33.0" - "@typescript-eslint/visitor-keys" "8.33.0" + "@typescript-eslint/scope-manager" "8.34.0" + "@typescript-eslint/type-utils" "8.34.0" + "@typescript-eslint/utils" "8.34.0" + "@typescript-eslint/visitor-keys" "8.34.0" graphemer "^1.4.0" ignore "^7.0.0" natural-compare "^1.4.0" ts-api-utils "^2.1.0" "@typescript-eslint/parser@^8.19.0": - version "8.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.33.0.tgz#8e523c2b447ad7cd6ac91b719d8b37449481784d" - integrity sha512-JaehZvf6m0yqYp34+RVnihBAChkqeH+tqqhS0GuX1qgPpwLvmTPheKEs6OeCK6hVJgXZHJ2vbjnC9j119auStQ== + version "8.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.34.0.tgz#703270426ac529304ae6988482f487c856d9c13f" + integrity sha512-vxXJV1hVFx3IXz/oy2sICsJukaBrtDEQSBiV48/YIV5KWjX1dO+bcIr/kCPrW6weKXvsaGKFNlwH0v2eYdRRbA== dependencies: - "@typescript-eslint/scope-manager" "8.33.0" - "@typescript-eslint/types" "8.33.0" - "@typescript-eslint/typescript-estree" "8.33.0" - "@typescript-eslint/visitor-keys" "8.33.0" + "@typescript-eslint/scope-manager" "8.34.0" + "@typescript-eslint/types" "8.34.0" + "@typescript-eslint/typescript-estree" "8.34.0" + "@typescript-eslint/visitor-keys" "8.34.0" debug "^4.3.4" -"@typescript-eslint/project-service@8.33.0": - version "8.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.33.0.tgz#71f37ef9010de47bf20963914743c5cbef851e08" - integrity sha512-d1hz0u9l6N+u/gcrk6s6gYdl7/+pp8yHheRTqP6X5hVDKALEaTn8WfGiit7G511yueBEL3OpOEpD+3/MBdoN+A== +"@typescript-eslint/project-service@8.34.0": + version "8.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.34.0.tgz#449119b72fe9fae185013a6bdbaf1ffbfee6bcaf" + integrity sha512-iEgDALRf970/B2YExmtPMPF54NenZUf4xpL3wsCRx/lgjz6ul/l13R81ozP/ZNuXfnLCS+oPmG7JIxfdNYKELw== dependencies: - "@typescript-eslint/tsconfig-utils" "^8.33.0" - "@typescript-eslint/types" "^8.33.0" + "@typescript-eslint/tsconfig-utils" "^8.34.0" + "@typescript-eslint/types" "^8.34.0" debug "^4.3.4" "@typescript-eslint/scope-manager@8.23.0": @@ -3727,26 +3727,26 @@ "@typescript-eslint/types" "8.23.0" "@typescript-eslint/visitor-keys" "8.23.0" -"@typescript-eslint/scope-manager@8.33.0": - version "8.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.33.0.tgz#459cf0c49d410800b1a023b973c62d699b09bf4c" - integrity sha512-LMi/oqrzpqxyO72ltP+dBSP6V0xiUb4saY7WLtxSfiNEBI8m321LLVFU9/QDJxjDQG9/tjSqKz/E3380TEqSTw== +"@typescript-eslint/scope-manager@8.34.0": + version "8.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.34.0.tgz#9fedaec02370cf79c018a656ab402eb00dc69e67" + integrity sha512-9Ac0X8WiLykl0aj1oYQNcLZjHgBojT6cW68yAgZ19letYu+Hxd0rE0veI1XznSSst1X5lwnxhPbVdwjDRIomRw== dependencies: - "@typescript-eslint/types" "8.33.0" - "@typescript-eslint/visitor-keys" "8.33.0" + "@typescript-eslint/types" "8.34.0" + "@typescript-eslint/visitor-keys" "8.34.0" -"@typescript-eslint/tsconfig-utils@8.33.0", "@typescript-eslint/tsconfig-utils@^8.33.0": - version "8.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.0.tgz#316adab038bbdc43e448781d5a816c2973eab73e" - integrity sha512-sTkETlbqhEoiFmGr1gsdq5HyVbSOF0145SYDJ/EQmXHtKViCaGvnyLqWFFHtEXoS0J1yU8Wyou2UGmgW88fEug== +"@typescript-eslint/tsconfig-utils@8.34.0", "@typescript-eslint/tsconfig-utils@^8.34.0": + version "8.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.0.tgz#97d0a24e89a355e9308cebc8e23f255669bf0979" + integrity sha512-+W9VYHKFIzA5cBeooqQxqNriAP0QeQ7xTiDuIOr71hzgffm3EL2hxwWBIIj4GuofIbKxGNarpKqIq6Q6YrShOA== -"@typescript-eslint/type-utils@8.33.0": - version "8.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.33.0.tgz#f06124b2d6db8a51b24990cb123c9543af93fef5" - integrity sha512-lScnHNCBqL1QayuSrWeqAL5GmqNdVUQAAMTaCwdYEdWfIrSrOGzyLGRCHXcCixa5NK6i5l0AfSO2oBSjCjf4XQ== +"@typescript-eslint/type-utils@8.34.0": + version "8.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.34.0.tgz#03e7eb3776129dfd751ba1cac0c6ea4b0fab5ec6" + integrity sha512-n7zSmOcUVhcRYC75W2pnPpbO1iwhJY3NLoHEtbJwJSNlVAZuwqu05zY3f3s2SDWWDSo9FdN5szqc73DCtDObAg== dependencies: - "@typescript-eslint/typescript-estree" "8.33.0" - "@typescript-eslint/utils" "8.33.0" + "@typescript-eslint/typescript-estree" "8.34.0" + "@typescript-eslint/utils" "8.34.0" debug "^4.3.4" ts-api-utils "^2.1.0" @@ -3755,10 +3755,10 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.23.0.tgz#3355f6bcc5ebab77ef6dcbbd1113ec0a683a234a" integrity sha512-1sK4ILJbCmZOTt9k4vkoulT6/y5CHJ1qUYxqpF1K/DBAd8+ZUL4LlSCxOssuH5m4rUaaN0uS0HlVPvd45zjduQ== -"@typescript-eslint/types@8.33.0", "@typescript-eslint/types@^8.33.0": - version "8.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.33.0.tgz#02a7dbba611a8abf1ad2a9e00f72f7b94b5ab0ee" - integrity sha512-DKuXOKpM5IDT1FA2g9x9x1Ug81YuKrzf4mYX8FAVSNu5Wo/LELHWQyM1pQaDkI42bX15PWl0vNPt1uGiIFUOpg== +"@typescript-eslint/types@8.34.0", "@typescript-eslint/types@^8.34.0": + version "8.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.34.0.tgz#18000f205c59c9aff7f371fc5426b764cf2890fb" + integrity sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA== "@typescript-eslint/typescript-estree@8.23.0": version "8.23.0" @@ -3774,15 +3774,15 @@ semver "^7.6.0" ts-api-utils "^2.0.1" -"@typescript-eslint/typescript-estree@8.33.0": - version "8.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.0.tgz#abcc1d3db75a8e9fd2e274ee8c4099fa2399abfd" - integrity sha512-vegY4FQoB6jL97Tu/lWRsAiUUp8qJTqzAmENH2k59SJhw0Th1oszb9Idq/FyyONLuNqT1OADJPXfyUNOR8SzAQ== +"@typescript-eslint/typescript-estree@8.34.0": + version "8.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.0.tgz#c9f3feec511339ef64e9e4884516c3e558f1b048" + integrity sha512-rOi4KZxI7E0+BMqG7emPSK1bB4RICCpF7QD3KCLXn9ZvWoESsOMlHyZPAHyG04ujVplPaHbmEvs34m+wjgtVtg== dependencies: - "@typescript-eslint/project-service" "8.33.0" - "@typescript-eslint/tsconfig-utils" "8.33.0" - "@typescript-eslint/types" "8.33.0" - "@typescript-eslint/visitor-keys" "8.33.0" + "@typescript-eslint/project-service" "8.34.0" + "@typescript-eslint/tsconfig-utils" "8.34.0" + "@typescript-eslint/types" "8.34.0" + "@typescript-eslint/visitor-keys" "8.34.0" debug "^4.3.4" fast-glob "^3.3.2" is-glob "^4.0.3" @@ -3790,15 +3790,15 @@ semver "^7.6.0" ts-api-utils "^2.1.0" -"@typescript-eslint/utils@8.33.0", "@typescript-eslint/utils@^8.32.1": - version "8.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.33.0.tgz#574ad5edee371077b9e28ca6fb804f2440f447c1" - integrity sha512-lPFuQaLA9aSNa7D5u2EpRiqdAUhzShwGg/nhpBlc4GR6kcTABttCuyjFs8BcEZ8VWrjCBof/bePhP3Q3fS+Yrw== +"@typescript-eslint/utils@8.34.0", "@typescript-eslint/utils@^8.32.1": + version "8.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.34.0.tgz#7844beebc1153b4d3ec34135c2da53a91e076f8d" + integrity sha512-8L4tWatGchV9A1cKbjaavS6mwYwp39jql8xUmIIKJdm+qiaeHy5KMKlBrf30akXAWBzn2SqKsNOtSENWUwg7XQ== dependencies: "@eslint-community/eslint-utils" "^4.7.0" - "@typescript-eslint/scope-manager" "8.33.0" - "@typescript-eslint/types" "8.33.0" - "@typescript-eslint/typescript-estree" "8.33.0" + "@typescript-eslint/scope-manager" "8.34.0" + "@typescript-eslint/types" "8.34.0" + "@typescript-eslint/typescript-estree" "8.34.0" "@typescript-eslint/utils@^6.0.0 || ^7.0.0 || ^8.0.0": version "8.23.0" @@ -3818,12 +3818,12 @@ "@typescript-eslint/types" "8.23.0" eslint-visitor-keys "^4.2.0" -"@typescript-eslint/visitor-keys@8.33.0": - version "8.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.0.tgz#fbae16fd3594531f8cad95d421125d634e9974fe" - integrity sha512-7RW7CMYoskiz5OOGAWjJFxgb7c5UNjTG292gYhWeOAcFmYCtVCSqjqSBj5zMhxbXo2JOW95YYrUWJfU0zrpaGQ== +"@typescript-eslint/visitor-keys@8.34.0": + version "8.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.0.tgz#c7a149407be31d755dba71980617d638a40ac099" + integrity sha512-qHV7pW7E85A0x6qyrFn+O+q1k1p3tQCsqIZ1KZ5ESLXY57aTvUd3/a4rdPTeXisvhXn2VQG0VSKUqs8KHF2zcA== dependencies: - "@typescript-eslint/types" "8.33.0" + "@typescript-eslint/types" "8.34.0" eslint-visitor-keys "^4.2.0" "@ungap/structured-clone@^1.2.0": @@ -4083,7 +4083,12 @@ acorn@^8.0.4, acorn@^8.1.0, acorn@^8.11.0, acorn@^8.9.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.13.0.tgz#2a30d670818ad16ddd6a35d3842dacec9e5d7ca3" integrity sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w== -acorn@^8.14.0, acorn@^8.4.1, acorn@^8.8.1, acorn@^8.8.2: +acorn@^8.14.0, acorn@^8.15.0: + version "8.15.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" + integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== + +acorn@^8.4.1, acorn@^8.8.1, acorn@^8.8.2: version "8.14.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.1.tgz#721d5dc10f7d5b5609a891773d47731796935dfb" integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg== @@ -4460,7 +4465,7 @@ axe-core@^4.10.0: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.10.2.tgz#85228e3e1d8b8532a27659b332e39b7fa0e022df" integrity sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w== -axe-core@~4.10.2: +axe-core@~4.10.3: version "4.10.3" resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.10.3.tgz#04145965ac7894faddbac30861e5d8f11bfd14fc" integrity sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg== @@ -4764,7 +4769,7 @@ braces@^3.0.3, braces@~3.0.2: dependencies: fill-range "^7.1.1" -browserslist@^4.0.0, browserslist@^4.23.1, browserslist@^4.23.2, browserslist@^4.23.3, browserslist@^4.24.0, browserslist@^4.24.3, browserslist@^4.24.4: +browserslist@^4.0.0, browserslist@^4.23.1, browserslist@^4.23.2, browserslist@^4.23.3, browserslist@^4.24.0, browserslist@^4.24.3, browserslist@^4.25.0: version "4.25.0" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.25.0.tgz#986aa9c6d87916885da2b50d8eb577ac8d133b2c" integrity sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA== @@ -4841,13 +4846,13 @@ bytes@3.1.2, bytes@^3.1.2: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== -cacheable@^1.8.9: - version "1.8.10" - resolved "https://registry.yarnpkg.com/cacheable/-/cacheable-1.8.10.tgz#c1b36260240d812912a6d3503da3a6e00166f75c" - integrity sha512-0ZnbicB/N2R6uziva8l6O6BieBklArWyiGx4GkwAhLKhSHyQtRfM9T1nx7HHuHDKkYB/efJQhz3QJ6x/YqoZzA== +cacheable@^1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/cacheable/-/cacheable-1.10.0.tgz#844dc3bc299344af373b728d9f4f0906ee67c1c9" + integrity sha512-SSgQTAnhd7WlJXnGlIi4jJJOiHzgnM5wRMEPaXAU4kECTAMpBoYKoZ9i5zHmclIEZbxcu3j7yY/CF8DTmwIsHg== dependencies: - hookified "^1.8.1" - keyv "^5.3.2" + hookified "^1.8.2" + keyv "^5.3.3" call-bind-apply-helpers@^1.0.0: version "1.0.1" @@ -4929,10 +4934,10 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@1.0.30001720, caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001718: - version "1.0.30001720" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001720.tgz#c138cb6026d362be9d8d7b0e4bcd0183a850edfd" - integrity sha512-Ec/2yV2nNPwb4DnTANEV99ZWwm3ZWfdlfkQbWSDDt+PsXEVYwlhPH8tdMaPunYTKKmz7AnHi2oNEi1GcmKCD8g== +caniuse-lite@1.0.30001721, caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001718: + version "1.0.30001721" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001721.tgz#36b90cd96901f8c98dd6698bf5c8af7d4c6872d7" + integrity sha512-cOuvmUVtKrtEaoKiO0rSc29jcjwMwX5tOHDy4MgVFEWiUXj4uBMJkwI8MDySkgXidpMiHUcviogAvFi4pA2hDQ== chalk@5.2.0: version "5.2.0" @@ -5289,11 +5294,11 @@ core-js-compat@^3.38.1: browserslist "^4.24.3" core-js-compat@^3.40.0: - version "3.42.0" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.42.0.tgz#ce19c29706ee5806e26d3cb3c542d4cfc0ed51bb" - integrity sha512-bQasjMfyDGyaeWKBIu33lHh9qlSR0MFE/Nmc6nMjf/iU9b3rSMdAYz1Baxrv4lPdGUsTqZudHA4jIGSJy0SWZQ== + version "3.43.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.43.0.tgz#055587369c458795ef316f65e0aabb808fb15840" + integrity sha512-2GML2ZsCc5LR7hZYz4AXmjQw8zuy2T//2QntwdnpuYI7jteT6GVYJL7F6C2C57R7gSYrcqVW3lAALefdbhBLDA== dependencies: - browserslist "^4.24.4" + browserslist "^4.25.0" core-js@^3.0.0, core-js@^3.38.1: version "3.42.0" @@ -5700,7 +5705,7 @@ debug@^4.3.2: dependencies: ms "^2.1.3" -debug@^4.3.7, debug@^4.4.0: +debug@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== @@ -5869,10 +5874,10 @@ docker-modem@^5.0.6: split-ca "^1.0.1" ssh2 "^1.15.0" -dockerode@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/dockerode/-/dockerode-4.0.6.tgz#e53978605346afaaed940a72e24d4e8490d52437" - integrity sha512-FbVf3Z8fY/kALB9s+P9epCpWhfi/r0N2DgYYcYpsAUlaTxPjdsitsFobnltb+lyCgAIvf9C+4PSWlTnHlJMf1w== +dockerode@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/dockerode/-/dockerode-4.0.7.tgz#8c68a80f4e057723a07a1e886416248945056621" + integrity sha512-R+rgrSRTRdU5mH14PZTCPZtW/zw3HDWNTS/1ZAQpL/5Upe/ye5K9WQkIysu4wBoiMwKynsz0a8qWuGsHgEvSAA== dependencies: "@balena/dockerignore" "^1.0.2" "@grpc/grpc-js" "^1.11.1" @@ -6033,9 +6038,9 @@ ejs@^3.1.8: jake "^10.8.5" electron-to-chromium@^1.5.160: - version "1.5.161" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.161.tgz#650376bd3be7ff8e581031409fc2d4f150620b12" - integrity sha512-hwtetwfKNZo/UlwHIVBlKZVdy7o8bIZxxKs0Mv/ROPiQQQmDgdm5a+KvKtBsxM8ZjFzTaCeLoodZ8jiBE3o9rA== + version "1.5.166" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.166.tgz#3fff386ed473cc2169dbe2d3ace9592262601114" + integrity sha512-QPWqHL0BglzPYyJJ1zSSmwFFL6MFXhbACOCcsCdUMCkzPdS9/OIBVxg516X/Ado2qwAq8k0nJJ7phQPCqiaFAw== emittery@^0.13.1: version "0.13.1" @@ -6490,10 +6495,10 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4 resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -eslint-visitor-keys@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" - integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== +eslint-visitor-keys@^4.2.0, eslint-visitor-keys@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1" + integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== eslint@8.57.1: version "8.57.1" @@ -6540,13 +6545,13 @@ eslint@8.57.1: text-table "^0.2.0" espree@^10.3.0: - version "10.3.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-10.3.0.tgz#29267cf5b0cb98735b65e64ba07e0ed49d1eed8a" - integrity sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg== + version "10.4.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.4.0.tgz#d54f4949d4629005a1fa168d937c3ff1f7e2a837" + integrity sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ== dependencies: - acorn "^8.14.0" + acorn "^8.15.0" acorn-jsx "^5.3.2" - eslint-visitor-keys "^4.2.0" + eslint-visitor-keys "^4.2.1" espree@^9.6.0, espree@^9.6.1: version "9.6.1" @@ -6796,12 +6801,12 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" -fd-package-json@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/fd-package-json/-/fd-package-json-1.2.0.tgz#4f218bb8ff65c21011d1f4f17cb3d0c9e72f8da7" - integrity sha512-45LSPmWf+gC5tdCQMNH4s9Sr00bIkiD9aN7dc5hqkrEw1geRYyDQS1v1oMHAW3ysfxfndqGsrDREHHjNNbKUfA== +fd-package-json@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fd-package-json/-/fd-package-json-2.0.0.tgz#03f53ce5a0af552c2f4faf703a24e526310a2411" + integrity sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ== dependencies: - walk-up-path "^3.0.1" + walk-up-path "^4.0.0" fdir@^6.4.0: version "6.4.2" @@ -6841,12 +6846,12 @@ fflate@^0.4.8: resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.4.8.tgz#f90b82aefbd8ac174213abb338bd7ef848f0f5ae" integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA== -file-entry-cache@^10.0.8: - version "10.0.8" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-10.0.8.tgz#2b7a32c40615c4a6b59c385fb059a2762faf9624" - integrity sha512-FGXHpfmI4XyzbLd3HQ8cbUcsFGohJpZtmQRHr8z8FxxtCe2PcpgIlVLwIgunqjvRmXypBETvwhV4ptJizA+Y1Q== +file-entry-cache@^10.1.0: + version "10.1.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-10.1.1.tgz#ca46f5c4eb22cc37e4ac30214452a59c297d2119" + integrity sha512-zcmsHjg2B2zjuBgjdnB+9q0+cWcgWfykIcsDkWDB4GTPtl1eXUA+gTI6sO0u01AqK3cliHryTU55/b2Ow1hfZg== dependencies: - flat-cache "^6.1.8" + flat-cache "^6.1.10" file-entry-cache@^6.0.1: version "6.0.1" @@ -6949,14 +6954,14 @@ flat-cache@^3.0.4: keyv "^4.5.3" rimraf "^3.0.2" -flat-cache@^6.1.8: - version "6.1.8" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-6.1.8.tgz#968fb89b19df488fe60f346857ffc54b8dd0ba14" - integrity sha512-R6MaD3nrJAtO7C3QOuS79ficm2pEAy++TgEUD8ii1LVlbcgZ9DtASLkt9B+RZSFCzm7QHDMlXPsqqB6W2Pfr1Q== +flat-cache@^6.1.10: + version "6.1.10" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-6.1.10.tgz#bf388abca92c213ac55086d678b08362867d6213" + integrity sha512-B6/v1f0NwjxzmeOhzfXPGWpKBVA207LS7lehaVKQnFrVktcFRfkzjZZ2gwj2i1TkEUMQht7ZMJbABUT5N+V1Nw== dependencies: - cacheable "^1.8.9" + cacheable "^1.10.0" flatted "^3.3.3" - hookified "^1.8.1" + hookified "^1.9.1" flat@^5.0.2: version "5.0.2" @@ -7006,21 +7011,22 @@ foreground-child@^3.1.0: signal-exit "^4.0.1" form-data@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.2.tgz#35cabbdd30c3ce73deb2c42d3c8d3ed9ca51794c" - integrity sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w== + version "4.0.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.3.tgz#608b1b3f3e28be0fccf5901fc85fb3641e5cf0ae" + integrity sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA== dependencies: asynckit "^0.4.0" combined-stream "^1.0.8" es-set-tostringtag "^2.1.0" + hasown "^2.0.2" mime-types "^2.1.12" -formatly@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/formatly/-/formatly-0.2.3.tgz#30c4d3605c4f66d97a97a7dafbd9bb4a2467b26f" - integrity sha512-WH01vbXEjh9L3bqn5V620xUAWs32CmK4IzWRRY6ep5zpa/mrisL4d9+pRVuETORVDTQw8OycSO1WC68PL51RaA== +formatly@^0.2.4: + version "0.2.4" + resolved "https://registry.yarnpkg.com/formatly/-/formatly-0.2.4.tgz#9f6281e14d2dc43a14061c0526b42c8bbb780c7a" + integrity sha512-lIN7GpcvX/l/i24r/L9bnJ0I8Qn01qijWpQpDDvTLL29nKqSaJJu4h20+7VJ6m2CAhQ2/En/GbxDiHCzq/0MyA== dependencies: - fd-package-json "^1.2.0" + fd-package-json "^2.0.0" forwarded@0.2.0: version "0.2.0" @@ -7434,10 +7440,10 @@ hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: dependencies: react-is "^16.7.0" -hookified@^1.8.1: - version "1.8.2" - resolved "https://registry.yarnpkg.com/hookified/-/hookified-1.8.2.tgz#b365a89dfce3da43e790673a6a97d3b896ae5fa7" - integrity sha512-5nZbBNP44sFCDjSoB//0N7m508APCgbQ4mGGo1KJGBYyCKNHfry1Pvd0JVHZIxjdnqn8nFRBAN/eFB6Rk/4w5w== +hookified@^1.8.2, hookified@^1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/hookified/-/hookified-1.9.1.tgz#d30cb77590672a05029b7ea9adf25b71c406121d" + integrity sha512-u3pxtGhKjcSXnGm1CX6aXS9xew535j3lkOCegbA6jdyh0BaAjTbXI4aslKstCr6zUNtoCxFGFKwjbSHdGrMB8g== hosted-git-info@^2.1.4: version "2.8.9" @@ -7575,9 +7581,9 @@ http-errors@~1.6.2: statuses ">= 1.4.0 < 2" http-parser-js@>=0.5.1: - version "0.5.9" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.9.tgz#b817b3ca0edea6236225000d795378707c169cec" - integrity sha512-n1XsPy3rXVxlqxVioEWdC+0+M+SQw0DpJynwtOPo1X+ZlvdzTLtDBIJJlDQTnwZIFJrZSzSGmIOUdP8tu+SgLw== + version "0.5.10" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.10.tgz#b3277bd6d7ed5588e20ea73bf724fcbe44609075" + integrity sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA== http-proxy-agent@^5.0.0: version "5.0.0" @@ -7588,7 +7594,7 @@ http-proxy-agent@^5.0.0: agent-base "6" debug "4" -http-proxy-middleware@^2.0.7: +http-proxy-middleware@^2.0.9: version "2.0.9" resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz#e9e63d68afaa4eee3d147f39149ab84c0c2815ef" integrity sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q== @@ -7660,10 +7666,10 @@ ignore@^5.2.0: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== -ignore@^7.0.0, ignore@^7.0.3: - version "7.0.4" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.4.tgz#a12c70d0f2607c5bf508fb65a40c75f037d7a078" - integrity sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A== +ignore@^7.0.0, ignore@^7.0.4: + version "7.0.5" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.5.tgz#4cb5f6cd7d4c7ab0365738c7aea888baa6d7efd9" + integrity sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg== immediate@~3.0.5: version "3.0.6" @@ -8828,10 +8834,10 @@ keyv@^4.5.3: dependencies: json-buffer "3.0.1" -keyv@^5.3.2: - version "5.3.3" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-5.3.3.tgz#ec2d723fbd7b908de5ee7f56b769d46dbbeaf8ba" - integrity sha512-Rwu4+nXI9fqcxiEHtbkvoes2X+QfkTRo1TMkPfwzipGsJlJO/z69vqB4FNl9xJ3xCpAcbkvmEabZfPzrwN3+gQ== +keyv@^5.3.3: + version "5.3.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-5.3.4.tgz#e0548d9449c51fc332abdd637c2b3bb2d24c9bc9" + integrity sha512-ypEvQvInNpUe+u+w8BIcPkQvEqXquyyibWE/1NB5T2BTzIpS5cGEV1LZskDzPSTvNAaT4+5FutvzlvnkxOSKlw== dependencies: "@keyv/serialize" "^1.0.3" @@ -8853,21 +8859,21 @@ kleur@^3.0.3: integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== knip@^5.36.2: - version "5.59.0" - resolved "https://registry.yarnpkg.com/knip/-/knip-5.59.0.tgz#f2f035d06f68b42fef7555c9ff800262c63572f7" - integrity sha512-fpkvLIcw2xqcisJpFZRt/BG2rol1znSwzUB5AR6uj+tTgurC1iUEudx8mSlaxsLaeWx5zvHmlW0dlfq7CfKWJQ== + version "5.60.2" + resolved "https://registry.yarnpkg.com/knip/-/knip-5.60.2.tgz#0deb8f5d72878f08a1dd8200d2655124204c5d56" + integrity sha512-TsYqEsoL3802RmhGL5MN7RLI6/03kocMYx/4BpMmwo3dSwEJxmzV7HqRxMVZr6c1llbd25+MqjgA86bv1IwsPA== dependencies: "@nodelib/fs.walk" "^1.2.3" fast-glob "^3.3.3" - formatly "^0.2.3" + formatly "^0.2.4" jiti "^2.4.2" js-yaml "^4.1.0" minimist "^1.2.8" - oxc-resolver "^9.0.2" - picocolors "^1.1.0" + oxc-resolver "^11.1.0" + picocolors "^1.1.1" picomatch "^4.0.1" - smol-toml "^1.3.1" - strip-json-comments "5.0.1" + smol-toml "^1.3.4" + strip-json-comments "5.0.2" zod "^3.22.4" zod-validation-error "^3.0.3" @@ -9131,9 +9137,9 @@ magic-string@0.30.8: "@jridgewell/sourcemap-codec" "^1.4.15" mailpit-api@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/mailpit-api/-/mailpit-api-1.4.0.tgz#881b473434b86c13ff85c3d92e9474daf4376135" - integrity sha512-A7hMRDQGo0jf1HZM06tw8dSgT2cYEev7lh1ehUXDWIBWXuK+Rq6HHCCQz624uAFYWEngmt4RGG1kMGGUC7iY6A== + version "1.5.0" + resolved "https://registry.yarnpkg.com/mailpit-api/-/mailpit-api-1.5.0.tgz#c7006c3650308f4eec1c36f51ac1a66c1e949e5e" + integrity sha512-csvr3se1TKHhiMJBrToVxX7W4O/9VZ74ivitHHOZezYVLp+nZthx7dWWXB8BwCJPmTLtfKHcJXWqMluK453ulA== dependencies: axios "^1.9.0" @@ -9157,9 +9163,9 @@ makeerror@1.0.12: tmpl "1.0.5" maplibre-gl@^5.0.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/maplibre-gl/-/maplibre-gl-5.5.0.tgz#0a295a634e745a5c31848ac7198893da417d09ae" - integrity sha512-p8AOPuzzqn1ZA9gcXxKw0IED715we/2Owa/YUr6PANmgMvNMe/JG+V/C1hRra43Wm62Biz+Aa8AgbOLJimA8tA== + version "5.6.0" + resolved "https://registry.yarnpkg.com/maplibre-gl/-/maplibre-gl-5.6.0.tgz#bb62bad5669ecd33b27cd84d8d2338f93553d919" + integrity sha512-7TuHMozUC4rlIp08bSsxCixFn18P24otrlZU/7UGCO5RufFTJadFzauTrvBHr9FB67MbJ6nvFXEftGd0bUl4Iw== dependencies: "@mapbox/geojson-rewind" "^0.5.2" "@mapbox/jsonlint-lines-primitives" "^2.0.2" @@ -9168,7 +9174,7 @@ maplibre-gl@^5.0.0: "@mapbox/unitbezier" "^0.0.1" "@mapbox/vector-tile" "^1.3.1" "@mapbox/whoots-js" "^3.1.0" - "@maplibre/maplibre-gl-style-spec" "^23.2.2" + "@maplibre/maplibre-gl-style-spec" "^23.3.0" "@types/geojson" "^7946.0.16" "@types/geojson-vt" "3.2.5" "@types/mapbox__point-geometry" "^0.1.4" @@ -9220,11 +9226,11 @@ matrix-events-sdk@0.0.1: integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA== "matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": - version "37.6.0" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/c35c7d1a3be073e0922cf28f13225805fc2a78f8" + version "37.7.0" + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/c387f30e5c23c897877dd418545a306e6b8cf0c9" dependencies: "@babel/runtime" "^7.12.5" - "@matrix-org/matrix-sdk-crypto-wasm" "^14.0.1" + "@matrix-org/matrix-sdk-crypto-wasm" "^14.2.0" "@matrix-org/olm" "3.2.15" another-json "^0.2.0" bs58 "^6.0.0" @@ -9294,9 +9300,9 @@ media-typer@^1.1.0: integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw== memfs@^4.6.0: - version "4.17.0" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-4.17.0.tgz#a3c4b5490b9b1e7df5d433adc163e08208ce7ca2" - integrity sha512-4eirfZ7thblFmqFjywlTmuWVSvccHAJbn1r8qQLzmTO11qcqpohOjmY2mFce6x7x7WtskzRqApPD0hv+Oa74jg== + version "4.17.2" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-4.17.2.tgz#1f71a6d85c8c53b4f1b388234ed981a690c7e227" + integrity sha512-NgYhCOWgovOXSzvYgUW0LQ7Qy72rWQMGGFJDoWg4G30RHd3z77VbYdtJ4fembJXBy8pMIUA31XNAupobOQlwdg== dependencies: "@jsonjoy.com/json-pack" "^1.0.3" "@jsonjoy.com/util" "^1.3.0" @@ -9538,16 +9544,16 @@ nano-spawn@^1.0.2: resolved "https://registry.yarnpkg.com/nano-spawn/-/nano-spawn-1.0.2.tgz#9853795681f0e96ef6f39104c2e4347b6ba79bf6" integrity sha512-21t+ozMQDAL/UGgQVBbZ/xXvNO10++ZPuTmKRO8k9V3AClVRht49ahtDjfY8l1q6nSHOrE5ASfthzH3ol6R/hg== +nanoid@^3.3.11, nanoid@^3.3.8: + version "3.3.11" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" + integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== + nanoid@^3.3.7: version "3.3.8" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== -nanoid@^3.3.8: - version "3.3.11" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" - integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== - natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -9762,9 +9768,9 @@ onetime@^7.0.0: mimic-function "^5.0.0" open@^10.0.3: - version "10.1.0" - resolved "https://registry.yarnpkg.com/open/-/open-10.1.0.tgz#a7795e6e5d519abe4286d9937bb24b51122598e1" - integrity sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw== + version "10.1.2" + resolved "https://registry.yarnpkg.com/open/-/open-10.1.2.tgz#d5df40984755c9a9c3c93df8156a12467e882925" + integrity sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw== dependencies: default-browser "^5.2.1" define-lazy-prop "^3.0.0" @@ -9815,24 +9821,24 @@ own-keys@^1.0.1: object-keys "^1.1.1" safe-push-apply "^1.0.0" -oxc-resolver@^9.0.2: - version "9.0.2" - resolved "https://registry.yarnpkg.com/oxc-resolver/-/oxc-resolver-9.0.2.tgz#0a86ee1e26f6c3f5f2af73dece276f8f588d4ef8" - integrity sha512-w838ygc1p7rF+7+h5vR9A+Y9Fc4imy6C3xPthCMkdFUgFvUWkmABeNB8RBDQ6+afk44Q60/UMMQ+gfDUW99fBA== +oxc-resolver@^11.1.0: + version "11.2.0" + resolved "https://registry.yarnpkg.com/oxc-resolver/-/oxc-resolver-11.2.0.tgz#1c996e5b07b5170cc7134b66f71d27f65d4a5f74" + integrity sha512-3iJYyIdDZMDoj0ZSVBrI1gUvPBMkDC4gxonBG+7uqUyK5EslG0mCwnf6qhxK8oEU7jLHjbRBNyzflPSd3uvH7Q== optionalDependencies: - "@oxc-resolver/binding-darwin-arm64" "9.0.2" - "@oxc-resolver/binding-darwin-x64" "9.0.2" - "@oxc-resolver/binding-freebsd-x64" "9.0.2" - "@oxc-resolver/binding-linux-arm-gnueabihf" "9.0.2" - "@oxc-resolver/binding-linux-arm64-gnu" "9.0.2" - "@oxc-resolver/binding-linux-arm64-musl" "9.0.2" - "@oxc-resolver/binding-linux-riscv64-gnu" "9.0.2" - "@oxc-resolver/binding-linux-s390x-gnu" "9.0.2" - "@oxc-resolver/binding-linux-x64-gnu" "9.0.2" - "@oxc-resolver/binding-linux-x64-musl" "9.0.2" - "@oxc-resolver/binding-wasm32-wasi" "9.0.2" - "@oxc-resolver/binding-win32-arm64-msvc" "9.0.2" - "@oxc-resolver/binding-win32-x64-msvc" "9.0.2" + "@oxc-resolver/binding-darwin-arm64" "11.2.0" + "@oxc-resolver/binding-darwin-x64" "11.2.0" + "@oxc-resolver/binding-freebsd-x64" "11.2.0" + "@oxc-resolver/binding-linux-arm-gnueabihf" "11.2.0" + "@oxc-resolver/binding-linux-arm64-gnu" "11.2.0" + "@oxc-resolver/binding-linux-arm64-musl" "11.2.0" + "@oxc-resolver/binding-linux-riscv64-gnu" "11.2.0" + "@oxc-resolver/binding-linux-s390x-gnu" "11.2.0" + "@oxc-resolver/binding-linux-x64-gnu" "11.2.0" + "@oxc-resolver/binding-linux-x64-musl" "11.2.0" + "@oxc-resolver/binding-wasm32-wasi" "11.2.0" + "@oxc-resolver/binding-win32-arm64-msvc" "11.2.0" + "@oxc-resolver/binding-win32-x64-msvc" "11.2.0" p-limit@^2.2.0: version "2.3.0" @@ -10726,7 +10732,7 @@ postcss@^8.3.11, postcss@^8.4.33: picocolors "^1.1.0" source-map-js "^1.2.1" -postcss@^8.4.40, postcss@^8.5.3: +postcss@^8.4.40: version "8.5.3" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.3.tgz#1463b6f1c7fb16fe258736cba29a2de35237eafb" integrity sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A== @@ -10735,10 +10741,19 @@ postcss@^8.4.40, postcss@^8.5.3: picocolors "^1.1.1" source-map-js "^1.2.1" -posthog-js@1.248.1: - version "1.248.1" - resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.248.1.tgz#7d45bbf8af1fd1092871ee0b0a062dc0a1907aec" - integrity sha512-2KBbvvLbiNqQdoEgjczZJ7im5QT8zi0Q29a8AmfXs0UGyaYxmgqPCrE1elQmGBuAm1S3T+Xw2D6Ff0VQPXE6Rw== +postcss@^8.5.3: + version "8.5.4" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.4.tgz#d61014ac00e11d5f58458ed7247d899bd65f99c0" + integrity sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w== + dependencies: + nanoid "^3.3.11" + picocolors "^1.1.1" + source-map-js "^1.2.1" + +posthog-js@1.249.4: + version "1.249.4" + resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.249.4.tgz#449a908e7363be13b86aff59c917943147c8a4e1" + integrity sha512-Qq4cxDZ1P9BkwguuoVNTiLGQiET9vrzwjYWLS3DduKhRXqEzERLl9tOq2X8ZNPbo+D207+FILdWg/dTKUItfDg== dependencies: core-js "^3.38.1" fflate "^0.4.8" @@ -10845,9 +10860,9 @@ properties-reader@^2.3.0: mkdirp "^1.0.4" protobufjs@^7.2.5, protobufjs@^7.3.2: - version "7.4.0" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.4.0.tgz#7efe324ce9b3b61c82aae5de810d287bc08a248a" - integrity sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw== + version "7.5.3" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.5.3.tgz#13f95a9e3c84669995ec3652db2ac2fb00b89363" + integrity sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw== dependencies: "@protobufjs/aspromise" "^1.1.2" "@protobufjs/base64" "^1.1.2" @@ -11598,17 +11613,7 @@ schema-utils@^3.0.0: ajv "^6.12.5" ajv-keywords "^3.5.2" -schema-utils@^4.0.0, schema-utils@^4.2.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.3.0.tgz#3b669f04f71ff2dfb5aba7ce2d5a9d79b35622c0" - integrity sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g== - dependencies: - "@types/json-schema" "^7.0.9" - ajv "^8.9.0" - ajv-formats "^2.1.1" - ajv-keywords "^5.1.0" - -schema-utils@^4.3.0, schema-utils@^4.3.2: +schema-utils@^4.0.0, schema-utils@^4.2.0, schema-utils@^4.3.0, schema-utils@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.3.2.tgz#0c10878bf4a73fd2b1dfd14b9462b26788c806ae" integrity sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ== @@ -11803,9 +11808,9 @@ shebang-regex@^3.0.0: integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== shell-quote@^1.8.1: - version "1.8.2" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.2.tgz#d2d83e057959d53ec261311e9e9b8f51dcb2934a" - integrity sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA== + version "1.8.3" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.3.tgz#55e40ef33cf5c689902353a3d8cd1a6725f08b4b" + integrity sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw== side-channel-list@^1.0.0: version "1.0.0" @@ -11906,7 +11911,7 @@ slice-ansi@^7.1.0: ansi-styles "^6.2.1" is-fullwidth-code-point "^5.0.0" -smol-toml@^1.3.1: +smol-toml@^1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/smol-toml/-/smol-toml-1.3.4.tgz#4ec76e0e709f586bc50ba30eb79024173c2b2221" integrity sha512-UOPtVuYkzYGee0Bd2Szz8d2G3RfMfJ2t3qVdZUAozZyAk+a0Sxa+QKix0YCwjL/A1RR0ar44nCxaoN9FxdJGwA== @@ -12063,9 +12068,9 @@ statuses@2.0.1, statuses@^2.0.1: integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== streamx@^2.15.0, streamx@^2.21.0: - version "2.22.0" - resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.22.0.tgz#cd7b5e57c95aaef0ff9b2aef7905afa62ec6e4a7" - integrity sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw== + version "2.22.1" + resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.22.1.tgz#c97cbb0ce18da4f4db5a971dc9ab68ff5dc7f5a5" + integrity sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA== dependencies: fast-fifo "^1.3.2" text-decoder "^1.1.0" @@ -12246,10 +12251,10 @@ strip-indent@^3.0.0: dependencies: min-indent "^1.0.0" -strip-json-comments@5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-5.0.1.tgz#0d8b7d01b23848ed7dbdf4baaaa31a8250d8cfa0" - integrity sha512-0fk9zBqO67Nq5M/m45qHCJxylV/DhBlIOVExqgOMiCCrzrhU6tCibRXNqE3jwJLftzE9SNuZtYbpzcO+i9FiKw== +strip-json-comments@5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-5.0.2.tgz#14a76abd63b84a6d2419d14f26a0281d0cf6ea46" + integrity sha512-4X2FR3UwhNUE9G49aIsJW5hRRR3GXGTBTZRMfv568O60ojM8HcWjV/VxAxCDW3SUND33O6ZY66ZuRcdkj73q2g== strip-json-comments@^3.1.1: version "3.1.1" @@ -12313,9 +12318,9 @@ stylelint-value-no-unknown-custom-properties@^6.0.1: resolve "^1.22.8" stylelint@^16.13.0: - version "16.19.1" - resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-16.19.1.tgz#486b95fa7518a3077ee2802bc6dda2174bc097bb" - integrity sha512-C1SlPZNMKl+d/C867ZdCRthrS+6KuZ3AoGW113RZCOL0M8xOGpgx7G70wq7lFvqvm4dcfdGFVLB/mNaLFChRKw== + version "16.20.0" + resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-16.20.0.tgz#ec9eeddd49c20bbc397473505e63ad22f1639228" + integrity sha512-B5Myu9WRxrgKuLs3YyUXLP2H0mrbejwNxPmyADlACWwFsrL8Bmor/nTSh4OMae5sHjOz6gkSeccQH34gM4/nAw== dependencies: "@csstools/css-parser-algorithms" "^3.0.4" "@csstools/css-tokenizer" "^3.0.3" @@ -12327,15 +12332,15 @@ stylelint@^16.13.0: cosmiconfig "^9.0.0" css-functions-list "^3.2.3" css-tree "^3.1.0" - debug "^4.3.7" + debug "^4.4.1" fast-glob "^3.3.3" fastest-levenshtein "^1.0.16" - file-entry-cache "^10.0.8" + file-entry-cache "^10.1.0" global-modules "^2.0.0" globby "^11.1.0" globjoin "^0.1.4" html-tags "^3.3.1" - ignore "^7.0.3" + ignore "^7.0.4" imurmurhash "^0.1.4" is-plain-object "^5.0.0" known-css-properties "^0.36.0" @@ -12554,10 +12559,10 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" -testcontainers@^11.0.0: - version "11.0.0" - resolved "https://registry.yarnpkg.com/testcontainers/-/testcontainers-11.0.0.tgz#248d8e59b922c993da378e6ef8bb018c0fc29330" - integrity sha512-8zY2V+eovC6aylgMqMR3A7H+un2gqpqepbvBCnjo7QP2fpI0pJZhSus+A5TckHpF2CR2d1Zj/IQ5rNPW/HjS6g== +testcontainers@^11.0.0, testcontainers@^11.0.3: + version "11.0.3" + resolved "https://registry.yarnpkg.com/testcontainers/-/testcontainers-11.0.3.tgz#2d444996d0eebed27d2caed6cdd7321210d5353e" + integrity sha512-Xu6ZAaE1FaLyHzFSYdCsd+xMPxUegUjkum0r6zgO8SinnFDHRX/PllIHMt1D+DVUmJqBvPQI6vge/J5jgE5vng== dependencies: "@balena/dockerignore" "^1.0.2" "@types/dockerode" "^3.3.39" @@ -12566,7 +12571,7 @@ testcontainers@^11.0.0: byline "^5.0.0" debug "^4.4.1" docker-compose "^1.2.0" - dockerode "^4.0.6" + dockerode "^4.0.7" get-port "^7.1.0" proper-lockfile "^4.1.2" properties-reader "^2.3.0" @@ -12692,9 +12697,9 @@ tr46@~0.0.3: integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== tree-dump@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/tree-dump/-/tree-dump-1.0.2.tgz#c460d5921caeb197bde71d0e9a7b479848c5b8ac" - integrity sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ== + version "1.0.3" + resolved "https://registry.yarnpkg.com/tree-dump/-/tree-dump-1.0.3.tgz#2f0e42e77354714418ed7ab44291e435ccdb0f80" + integrity sha512-il+Cv80yVHFBwokQSfd4bldvr1Md951DpgAGfmhydt04L+YzHgubm2tQ7zueWDcGENKHq0ZvGFR/hjvNXilHEg== tree-kill@^1.2.2: version "1.2.2" @@ -12886,10 +12891,10 @@ undici-types@~5.26.4: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== -undici-types@~6.21.0: - version "6.21.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" - integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== +undici-types@~7.8.0: + version "7.8.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.8.0.tgz#de00b85b710c54122e44fbfd911f8d70174cd294" + integrity sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw== undici@^7.10.0: version "7.10.0" @@ -13100,10 +13105,10 @@ w3c-xmlserializer@^4.0.0: dependencies: xml-name-validator "^4.0.0" -walk-up-path@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/walk-up-path/-/walk-up-path-3.0.1.tgz#c8d78d5375b4966c717eb17ada73dbd41490e886" - integrity sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA== +walk-up-path@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/walk-up-path/-/walk-up-path-4.0.0.tgz#590666dcf8146e2d72318164f1f2ac6ef51d4198" + integrity sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A== walk@^2.3.15: version "2.3.15" @@ -13220,9 +13225,9 @@ webpack-dev-middleware@^7.4.2: schema-utils "^4.0.0" webpack-dev-server@^5.0.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-5.2.1.tgz#049072d6e19cbda8cf600b9e364e6662d61218ba" - integrity sha512-ml/0HIj9NLpVKOMq+SuBPLHcmbG+TGIjXRHsYfZwocUBIqEvws8NnS/V9AFQ5FKP+tgn5adwVwRrTEpGL33QFQ== + version "5.2.2" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-5.2.2.tgz#96a143d50c58fef0c79107e61df911728d7ceb39" + integrity sha512-QcQ72gh8a+7JO63TAx/6XZf/CWhgMzu5m0QirvPfGvptOusAxG12w2+aua1Jkjr7hzaWDnJ2n6JFeexMHI+Zjg== dependencies: "@types/bonjour" "^3.5.13" "@types/connect-history-api-fallback" "^1.5.4" @@ -13240,7 +13245,7 @@ webpack-dev-server@^5.0.0: connect-history-api-fallback "^2.0.0" express "^4.21.2" graceful-fs "^4.2.6" - http-proxy-middleware "^2.0.7" + http-proxy-middleware "^2.0.9" ipaddr.js "^2.1.0" launch-editor "^2.6.1" open "^10.0.3" @@ -13523,9 +13528,9 @@ ws@^8.11.0: integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== ws@^8.18.0: - version "8.18.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.1.tgz#ea131d3784e1dfdff91adb0a4a116b127515e3cb" - integrity sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w== + version "8.18.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.2.tgz#42738b2be57ced85f46154320aabb51ab003705a" + integrity sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ== xml-name-validator@^4.0.0: version "4.0.0" @@ -13637,6 +13642,6 @@ zod-validation-error@^3.0.3: integrity sha512-1KP64yqDPQ3rupxNv7oXhf7KdhHHgaqbKuspVoiN93TT0xrBjql+Svjkdjq/Qh/7GSMmgQs3AfvBT0heE35thw== zod@^3.22.4: - version "3.25.36" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.36.tgz#e5ae0fb7803662b8a21d9a1e282e7d5b8ff57726" - integrity sha512-eRFS3i8T0IrpGdL8HQyqFAugGOn7jOjyGgGdtv5NY4Wkhi7lJDk732bNZ609YMIGFbLoaj6J69O1Mura23gfIw== + version "3.25.57" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.57.tgz#317c8a6eb8a8460bb4b58defb19e8b50c1200e51" + integrity sha512-6tgzLuwVST5oLUxXTmBqoinKMd3JeesgbgseXeFasKKj8Q1FCZrHnbqJOyiEvr4cVAlbug+CgIsmJ8cl/pU5FA==