;
- }
-
public render(): React.ReactNode {
let accountManagementSection: JSX.Element | undefined;
const isAccountManagedExternally = !!this.state.externalAccountManagementUrl;
@@ -218,7 +211,6 @@ export default class GeneralUserSettingsTab extends React.Component
{this.renderAccountSection()}
- {this.renderIntegrationManagerSection()}
{accountManagementSection}
);
diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx
index c636721201..3b0dfb2dfc 100644
--- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx
+++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx
@@ -44,6 +44,7 @@ import { SettingsSection } from "../../shared/SettingsSection";
import SettingsSubsection, { SettingsSubsectionText } from "../../shared/SettingsSubsection";
import { useOwnDevices } from "../../devices/useOwnDevices";
import DiscoverySettings from "../../discovery/DiscoverySettings";
+import SetIntegrationManager from "../../SetIntegrationManager";
interface IIgnoredUserProps {
userId: string;
@@ -376,6 +377,7 @@ export default class SecurityUserSettingsTab extends React.Component
{warning}
+
{secureBackup}
{eventIndex}
diff --git a/test/components/views/settings/SetIntegrationManager-test.tsx b/test/components/views/settings/SetIntegrationManager-test.tsx
new file mode 100644
index 0000000000..5b92e03940
--- /dev/null
+++ b/test/components/views/settings/SetIntegrationManager-test.tsx
@@ -0,0 +1,104 @@
+/*
+Copyright 2024 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from "react";
+import { fireEvent, render, screen, within } from "@testing-library/react";
+import { logger } from "matrix-js-sdk/src/logger";
+
+import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
+import { SDKContext, SdkContextClass } from "../../../../src/contexts/SDKContext";
+import SettingsStore from "../../../../src/settings/SettingsStore";
+import { UIFeature } from "../../../../src/settings/UIFeature";
+import {
+ getMockClientWithEventEmitter,
+ mockClientMethodsServer,
+ mockClientMethodsUser,
+ flushPromises,
+} from "../../../test-utils";
+import SetIntegrationManager from "../../../../src/components/views/settings/SetIntegrationManager";
+import { SettingLevel } from "../../../../src/settings/SettingLevel";
+
+describe("SetIntegrationManager", () => {
+ const userId = "@alice:server.org";
+
+ const mockClient = getMockClientWithEventEmitter({
+ ...mockClientMethodsUser(userId),
+ ...mockClientMethodsServer(),
+ getCapabilities: jest.fn(),
+ getThreePids: jest.fn(),
+ getIdentityServerUrl: jest.fn(),
+ deleteThreePid: jest.fn(),
+ });
+
+ let stores: SdkContextClass;
+
+ const getComponent = () => (
+
+
+
+
+
+ );
+
+ it("should not render manage integrations section when widgets feature is disabled", () => {
+ jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName) => settingName !== UIFeature.Widgets);
+ render(getComponent());
+
+ expect(screen.queryByTestId("mx_SetIntegrationManager")).not.toBeInTheDocument();
+ expect(SettingsStore.getValue).toHaveBeenCalledWith(UIFeature.Widgets);
+ });
+ it("should render manage integrations sections", () => {
+ jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName) => settingName === UIFeature.Widgets);
+
+ render(getComponent());
+
+ expect(screen.getByTestId("mx_SetIntegrationManager")).toMatchSnapshot();
+ });
+ it("should update integrations provisioning on toggle", () => {
+ jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName) => settingName === UIFeature.Widgets);
+ jest.spyOn(SettingsStore, "setValue").mockResolvedValue(undefined);
+
+ render(getComponent());
+
+ const integrationSection = screen.getByTestId("mx_SetIntegrationManager");
+ fireEvent.click(within(integrationSection).getByRole("switch"));
+
+ expect(SettingsStore.setValue).toHaveBeenCalledWith(
+ "integrationProvisioning",
+ null,
+ SettingLevel.ACCOUNT,
+ true,
+ );
+ expect(within(integrationSection).getByRole("switch")).toBeChecked();
+ });
+ it("handles error when updating setting fails", async () => {
+ jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName) => settingName === UIFeature.Widgets);
+ jest.spyOn(logger, "error").mockImplementation(() => {});
+
+ jest.spyOn(SettingsStore, "setValue").mockRejectedValue("oups");
+
+ render(getComponent());
+
+ const integrationSection = screen.getByTestId("mx_SetIntegrationManager");
+ fireEvent.click(within(integrationSection).getByRole("switch"));
+
+ await flushPromises();
+
+ expect(logger.error).toHaveBeenCalledWith("Error changing integration manager provisioning");
+ expect(logger.error).toHaveBeenCalledWith("oups");
+ expect(within(integrationSection).getByRole("switch")).not.toBeChecked();
+ });
+});
diff --git a/test/components/views/settings/__snapshots__/SetIntegrationManager-test.tsx.snap b/test/components/views/settings/__snapshots__/SetIntegrationManager-test.tsx.snap
new file mode 100644
index 0000000000..cde822f6ab
--- /dev/null
+++ b/test/components/views/settings/__snapshots__/SetIntegrationManager-test.tsx.snap
@@ -0,0 +1,56 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`SetIntegrationManager should render manage integrations sections 1`] = `
+
+`;
diff --git a/test/components/views/settings/tabs/user/GeneralUserSettingsTab-test.tsx b/test/components/views/settings/tabs/user/GeneralUserSettingsTab-test.tsx
index 17016e0df2..8627f41063 100644
--- a/test/components/views/settings/tabs/user/GeneralUserSettingsTab-test.tsx
+++ b/test/components/views/settings/tabs/user/GeneralUserSettingsTab-test.tsx
@@ -27,7 +27,6 @@ import {
flushPromises,
} from "../../../../../test-utils";
import { UIFeature } from "../../../../../../src/settings/UIFeature";
-import { SettingLevel } from "../../../../../../src/settings/SettingLevel";
import { OidcClientStore } from "../../../../../../src/stores/oidc/OidcClientStore";
import MatrixClientContext from "../../../../../../src/contexts/MatrixClientContext";
@@ -98,65 +97,6 @@ describe("", () => {
expect(manageAccountLink.getAttribute("href")).toMatch(accountManagementLink);
});
- describe("Manage integrations", () => {
- it("should not render manage integrations section when widgets feature is disabled", () => {
- jest.spyOn(SettingsStore, "getValue").mockImplementation(
- (settingName) => settingName !== UIFeature.Widgets,
- );
- render(getComponent());
-
- expect(screen.queryByTestId("mx_SetIntegrationManager")).not.toBeInTheDocument();
- expect(SettingsStore.getValue).toHaveBeenCalledWith(UIFeature.Widgets);
- });
- it("should render manage integrations sections", () => {
- jest.spyOn(SettingsStore, "getValue").mockImplementation(
- (settingName) => settingName === UIFeature.Widgets,
- );
-
- render(getComponent());
-
- expect(screen.getByTestId("mx_SetIntegrationManager")).toMatchSnapshot();
- });
- it("should update integrations provisioning on toggle", () => {
- jest.spyOn(SettingsStore, "getValue").mockImplementation(
- (settingName) => settingName === UIFeature.Widgets,
- );
- jest.spyOn(SettingsStore, "setValue").mockResolvedValue(undefined);
-
- render(getComponent());
-
- const integrationSection = screen.getByTestId("mx_SetIntegrationManager");
- fireEvent.click(within(integrationSection).getByRole("switch"));
-
- expect(SettingsStore.setValue).toHaveBeenCalledWith(
- "integrationProvisioning",
- null,
- SettingLevel.ACCOUNT,
- true,
- );
- expect(within(integrationSection).getByRole("switch")).toBeChecked();
- });
- it("handles error when updating setting fails", async () => {
- jest.spyOn(SettingsStore, "getValue").mockImplementation(
- (settingName) => settingName === UIFeature.Widgets,
- );
- jest.spyOn(logger, "error").mockImplementation(() => {});
-
- jest.spyOn(SettingsStore, "setValue").mockRejectedValue("oups");
-
- render(getComponent());
-
- const integrationSection = screen.getByTestId("mx_SetIntegrationManager");
- fireEvent.click(within(integrationSection).getByRole("switch"));
-
- await flushPromises();
-
- expect(logger.error).toHaveBeenCalledWith("Error changing integration manager provisioning");
- expect(logger.error).toHaveBeenCalledWith("oups");
- expect(within(integrationSection).getByRole("switch")).not.toBeChecked();
- });
- });
-
describe("deactive account", () => {
it("should not render section when account deactivation feature is disabled", () => {
jest.spyOn(SettingsStore, "getValue").mockImplementation(
diff --git a/test/components/views/settings/tabs/user/__snapshots__/GeneralUserSettingsTab-test.tsx.snap b/test/components/views/settings/tabs/user/__snapshots__/GeneralUserSettingsTab-test.tsx.snap
index 8d15bf8e7e..08cd795def 100644
--- a/test/components/views/settings/tabs/user/__snapshots__/GeneralUserSettingsTab-test.tsx.snap
+++ b/test/components/views/settings/tabs/user/__snapshots__/GeneralUserSettingsTab-test.tsx.snap
@@ -42,14 +42,14 @@ exports[` 3pids should display 3pid email addresses an
>
@@ -150,14 +150,14 @@ exports[` 3pids should display 3pid email addresses an
@@ -175,61 +175,6 @@ exports[` 3pids should display 3pid email addresses an
`;
-exports[` Manage integrations should render manage integrations sections 1`] = `
-
-`;
-
exports[` deactive account should render section when account deactivation feature is enabled 1`] = `
renders security section 1`] = `
+
From 2772a9b7677961a587faa5c46c31169cae366274 Mon Sep 17 00:00:00 2001
From: ElementRobot
Date: Thu, 11 Jul 2024 07:22:30 +0100
Subject: [PATCH 16/59] [create-pull-request] automated change (#12763)
Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com>
---
playwright/plugins/homeserver/synapse/index.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/playwright/plugins/homeserver/synapse/index.ts b/playwright/plugins/homeserver/synapse/index.ts
index 8562ec32ae..e5eb9384d0 100644
--- a/playwright/plugins/homeserver/synapse/index.ts
+++ b/playwright/plugins/homeserver/synapse/index.ts
@@ -28,7 +28,7 @@ import { randB64Bytes } from "../../utils/rand";
// Docker tag to use for `matrixdotorg/synapse` image.
// We target a specific digest as every now and then a Synapse update will break our CI.
// This digest is updated by the playwright-image-updates.yaml workflow periodically.
-const DOCKER_TAG = "develop@sha256:e5f6da1f643417a60f0d398546f9e8a28c51fe743f2562c132f8f99dcce0266d";
+const DOCKER_TAG = "develop@sha256:c1aa9714b9e4470fe17fb61ed05d70cf6d78d0cac58930cc7ae847e72acfa561";
async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise> {
const templateDir = path.join(__dirname, "templates", opts.template);
From 72e0d100ea73ad85ce28baa305fab9c76d43b0b4 Mon Sep 17 00:00:00 2001
From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
Date: Thu, 11 Jul 2024 15:06:45 +0100
Subject: [PATCH 17/59] Update wording shown when keys are withheld (#12761)
We can be a bit more helpful here.
---
src/i18n/strings/en_EN.json | 2 +-
.../messages/__snapshots__/DecryptionFailureBody-test.tsx.snap | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index eaf5784af9..b019346156 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -3254,7 +3254,7 @@
"creation_summary_dm": "%(creator)s created this DM.",
"creation_summary_room": "%(creator)s created and configured the room.",
"decryption_failure": {
- "blocked": "The sender has blocked you from receiving this message",
+ "blocked": "The sender has blocked you from receiving this message because your device is unverified",
"historical_event_no_key_backup": "Historical messages are not available on this device",
"historical_event_unverified_device": "You need to verify this device for access to historical messages",
"historical_event_user_not_joined": "You don't have access to this message",
diff --git a/test/components/views/messages/__snapshots__/DecryptionFailureBody-test.tsx.snap b/test/components/views/messages/__snapshots__/DecryptionFailureBody-test.tsx.snap
index c0096b6467..22e44fd16a 100644
--- a/test/components/views/messages/__snapshots__/DecryptionFailureBody-test.tsx.snap
+++ b/test/components/views/messages/__snapshots__/DecryptionFailureBody-test.tsx.snap
@@ -5,7 +5,7 @@ exports[`DecryptionFailureBody Should display "The sender has blocked you from r
- The sender has blocked you from receiving this message
+ The sender has blocked you from receiving this message because your device is unverified
`;
From b53baea1118bab445369ae8bd95f56c6d018ebc9 Mon Sep 17 00:00:00 2001
From: ElementRobot
Date: Fri, 12 Jul 2024 07:17:32 +0100
Subject: [PATCH 18/59] [create-pull-request] automated change (#12766)
Co-authored-by: github-merge-queue
---
src/i18n/strings/cs.json | 3 ---
src/i18n/strings/de_DE.json | 3 ---
src/i18n/strings/el.json | 5 +----
src/i18n/strings/eo.json | 5 +----
src/i18n/strings/es.json | 3 ---
src/i18n/strings/et.json | 3 ---
src/i18n/strings/fa.json | 5 +----
src/i18n/strings/fi.json | 5 +----
src/i18n/strings/fr.json | 3 ---
src/i18n/strings/gl.json | 5 +----
src/i18n/strings/he.json | 5 +----
src/i18n/strings/hu.json | 3 ---
src/i18n/strings/id.json | 3 ---
src/i18n/strings/is.json | 5 +----
src/i18n/strings/it.json | 3 ---
src/i18n/strings/ja.json | 5 +----
src/i18n/strings/lo.json | 5 +----
src/i18n/strings/lt.json | 5 +----
src/i18n/strings/nl.json | 5 +----
src/i18n/strings/pl.json | 26 +++++++++++++++++++-------
src/i18n/strings/pt_BR.json | 5 +----
src/i18n/strings/ru.json | 3 ---
src/i18n/strings/sk.json | 3 ---
src/i18n/strings/sq.json | 3 ---
src/i18n/strings/sv.json | 3 ---
src/i18n/strings/uk.json | 3 ---
src/i18n/strings/vi.json | 3 ---
src/i18n/strings/zh_Hans.json | 5 +----
src/i18n/strings/zh_Hant.json | 3 ---
29 files changed, 32 insertions(+), 104 deletions(-)
diff --git a/src/i18n/strings/cs.json b/src/i18n/strings/cs.json
index 5a4a8173a7..7b885d2ac9 100644
--- a/src/i18n/strings/cs.json
+++ b/src/i18n/strings/cs.json
@@ -1992,14 +1992,11 @@
"rejecting": "Odmítání pozvánky…",
"rejoin_button": "Znovu vstoupit",
"search": {
- "all_rooms": "Všechny místnosti",
"all_rooms_button": "Vyhledávat ve všech místnostech",
- "field_placeholder": "Hledat…",
"result_count": {
"other": "(~%(count)s výsledků)",
"one": "(~%(count)s výsledek)"
},
- "this_room": "Tato místnost",
"this_room_button": "Vyhledávat v této místnosti"
},
"show_labs_settings": "Zobrazit nastavení Experimentálních funkcí",
diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json
index 31585adb20..5982ae172f 100644
--- a/src/i18n/strings/de_DE.json
+++ b/src/i18n/strings/de_DE.json
@@ -1974,14 +1974,11 @@
"rejecting": "Lehne Einladung ab …",
"rejoin_button": "Erneut betreten",
"search": {
- "all_rooms": "In allen Räumen",
"all_rooms_button": "Alle Räume durchsuchen",
- "field_placeholder": "Suchen…",
"result_count": {
"one": "(~%(count)s Ergebnis)",
"other": "(~%(count)s Ergebnisse)"
},
- "this_room": "In diesem Raum",
"this_room_button": "Diesen Raum durchsuchen"
},
"show_labs_settings": "Zeige die \"Labor\" Einstellungen",
diff --git a/src/i18n/strings/el.json b/src/i18n/strings/el.json
index 592dbc4afe..78af9d60ea 100644
--- a/src/i18n/strings/el.json
+++ b/src/i18n/strings/el.json
@@ -1557,13 +1557,10 @@
"peek_join_prompt": "Κάνετε προεπισκόπηση στο %(roomName)s. Θέλετε να συμμετάσχετε;",
"rejoin_button": "Επανασύνδεση",
"search": {
- "all_rooms": "Όλα τα δωμάτια",
- "field_placeholder": "Αναζήτηση…",
"result_count": {
"one": "(~%(count)s αποτέλεσμα)",
"other": "(~%(count)s αποτελέσματα)"
- },
- "this_room": "Στο δωμάτιο"
+ }
},
"show_labs_settings": "Εμφάνιση ρυθμίσεων Labs",
"status_bar": {
diff --git a/src/i18n/strings/eo.json b/src/i18n/strings/eo.json
index c5c904adeb..45da8d4d92 100644
--- a/src/i18n/strings/eo.json
+++ b/src/i18n/strings/eo.json
@@ -1379,13 +1379,10 @@
"peek_join_prompt": "Vi antaŭrigardas ĉambron %(roomName)s. Ĉu vi volas aliĝi?",
"rejoin_button": "Re-aliĝi",
"search": {
- "all_rooms": "Ĉiuj ĉambroj",
- "field_placeholder": "Serĉi…",
"result_count": {
"other": "(~%(count)s rezultoj)",
"one": "(~%(count)s rezulto)"
- },
- "this_room": "Ĉi tiu ĉambro"
+ }
},
"status_bar": {
"delete_all": "Forigi ĉiujn",
diff --git a/src/i18n/strings/es.json b/src/i18n/strings/es.json
index da2b8ae1bd..3e7c7c9cb6 100644
--- a/src/i18n/strings/es.json
+++ b/src/i18n/strings/es.json
@@ -1811,14 +1811,11 @@
"rejecting": "Rechazar invitación…",
"rejoin_button": "Volver a entrar",
"search": {
- "all_rooms": "Todas las salas",
"all_rooms_button": "Buscar en todas las salas",
- "field_placeholder": "Buscar…",
"result_count": {
"other": "(~%(count)s resultados)",
"one": "(~%(count)s resultado)"
},
- "this_room": "Esta sala",
"this_room_button": "Buscar en esta sala"
},
"show_labs_settings": "Ver ajustes de los experimentos",
diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json
index f43f251026..5c2028d689 100644
--- a/src/i18n/strings/et.json
+++ b/src/i18n/strings/et.json
@@ -1964,14 +1964,11 @@
"rejecting": "Hülgan kutset…",
"rejoin_button": "Liitu uuesti",
"search": {
- "all_rooms": "Kõik jututoad",
"all_rooms_button": "Otsi kõikidest jututubadest",
- "field_placeholder": "Otsi…",
"result_count": {
"other": "(~%(count)s tulemust)",
"one": "(~%(count)s tulemus)"
},
- "this_room": "See jututuba",
"this_room_button": "Otsi sellest jututoast"
},
"show_labs_settings": "Näita seadistusi",
diff --git a/src/i18n/strings/fa.json b/src/i18n/strings/fa.json
index 4dd583e7ed..0a7a41e281 100644
--- a/src/i18n/strings/fa.json
+++ b/src/i18n/strings/fa.json
@@ -1254,13 +1254,10 @@
"peek_join_prompt": "شما در حال پیش نمایش %(roomName)s هستید. می خواهید به آن بپیوندید؟",
"rejoin_button": "دوباره بپیوندید",
"search": {
- "all_rooms": "همهی گپها",
- "field_placeholder": "جستجو…",
"result_count": {
"one": "(~%(count)s نتیجه)",
"other": "(~%(count)s نتیجه)"
- },
- "this_room": "این گپ"
+ }
},
"status_bar": {
"delete_all": "حذف همه",
diff --git a/src/i18n/strings/fi.json b/src/i18n/strings/fi.json
index 59dce3bdd8..84ae773585 100644
--- a/src/i18n/strings/fi.json
+++ b/src/i18n/strings/fi.json
@@ -1715,13 +1715,10 @@
"rejecting": "Hylätään kutsua…",
"rejoin_button": "Liity uudelleen",
"search": {
- "all_rooms": "Kaikki huoneet",
- "field_placeholder": "Haku…",
"result_count": {
"one": "(~%(count)s tulos)",
"other": "(~%(count)s tulosta)"
- },
- "this_room": "Tämä huone"
+ }
},
"show_labs_settings": "Näytä laboratorion asetukset",
"status_bar": {
diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json
index 2852d94b53..ba4183e2df 100644
--- a/src/i18n/strings/fr.json
+++ b/src/i18n/strings/fr.json
@@ -2023,14 +2023,11 @@
"rejecting": "Rejet de l’invitation…",
"rejoin_button": "Revenir",
"search": {
- "all_rooms": "Tous les salons",
"all_rooms_button": "Rechercher dans tous les salons",
- "field_placeholder": "Rechercher…",
"result_count": {
"one": "(~%(count)s résultat)",
"other": "(~%(count)s résultats)"
},
- "this_room": "Ce salon",
"this_room_button": "Rechercher dans ce salon"
},
"show_labs_settings": "Afficher les réglages des expérimentations",
diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json
index c08a3f5b75..894b221096 100644
--- a/src/i18n/strings/gl.json
+++ b/src/i18n/strings/gl.json
@@ -1666,13 +1666,10 @@
"read_topic": "Preme para ler o tema",
"rejoin_button": "Volta a unirte",
"search": {
- "all_rooms": "Todas as Salas",
- "field_placeholder": "Buscar…",
"result_count": {
"other": "(~%(count)s resultados)",
"one": "(~%(count)s resultado)"
- },
- "this_room": "Esta sala"
+ }
},
"show_labs_settings": "Mostrar axustes en Labs",
"status_bar": {
diff --git a/src/i18n/strings/he.json b/src/i18n/strings/he.json
index 7ddc10f807..360e0cd301 100644
--- a/src/i18n/strings/he.json
+++ b/src/i18n/strings/he.json
@@ -1321,13 +1321,10 @@
"peek_join_prompt": "אתם צופים ב־%(roomName)s. האם תרצו להצטרף?",
"rejoin_button": "הצטרפות מחדש",
"search": {
- "all_rooms": "כל החדרים",
- "field_placeholder": "חפש…",
"result_count": {
"one": "(תוצאת %(count)s)",
"other": "(תוצאת %(count)s)"
- },
- "this_room": "החדר הזה"
+ }
},
"show_labs_settings": "הצג את אופציית מעבדת הפיתוח",
"status_bar": {
diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json
index 055249be11..b9fd14d926 100644
--- a/src/i18n/strings/hu.json
+++ b/src/i18n/strings/hu.json
@@ -1933,14 +1933,11 @@
"rejecting": "Meghívó elutasítása…",
"rejoin_button": "Újra-csatlakozás",
"search": {
- "all_rooms": "Minden szobában",
"all_rooms_button": "Keresés az összes szobában",
- "field_placeholder": "Keresés…",
"result_count": {
"one": "(~%(count)s db eredmény)",
"other": "(~%(count)s db eredmény)"
},
- "this_room": "Ebben a szobában",
"this_room_button": "Keresés ebben a szobában"
},
"show_labs_settings": "Labor beállítások megjelenítése",
diff --git a/src/i18n/strings/id.json b/src/i18n/strings/id.json
index a62ba112db..688241ba5f 100644
--- a/src/i18n/strings/id.json
+++ b/src/i18n/strings/id.json
@@ -1952,14 +1952,11 @@
"rejecting": "Menolak undangan…",
"rejoin_button": "Bergabung Ulang",
"search": {
- "all_rooms": "Semua Ruangan",
"all_rooms_button": "Cari semua ruangan",
- "field_placeholder": "Cari…",
"result_count": {
"one": "(~%(count)s hasil)",
"other": "(~%(count)s hasil)"
},
- "this_room": "Ruangan ini",
"this_room_button": "Cari ruangan ini"
},
"show_labs_settings": "Tampilkan pengaturan Uji Coba",
diff --git a/src/i18n/strings/is.json b/src/i18n/strings/is.json
index 7f713698ea..16b5dd9abb 100644
--- a/src/i18n/strings/is.json
+++ b/src/i18n/strings/is.json
@@ -1605,13 +1605,10 @@
"read_topic": "Smelltu til að lesa umfjöllunarefni",
"rejoin_button": "Taka þátt aftur",
"search": {
- "all_rooms": "Allar spjallrásir",
- "field_placeholder": "Leita…",
"result_count": {
"one": "(~%(count)s niðurstaða)",
"other": "(~%(count)s niðurstöður)"
- },
- "this_room": "Þessi spjallrás"
+ }
},
"show_labs_settings": "Sýna tilraunastillingar",
"status_bar": {
diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json
index 66e78c3802..b2f99ba1b1 100644
--- a/src/i18n/strings/it.json
+++ b/src/i18n/strings/it.json
@@ -1989,14 +1989,11 @@
"rejecting": "Rifiuto dell'invito…",
"rejoin_button": "Rientra",
"search": {
- "all_rooms": "Tutte le stanze",
"all_rooms_button": "Cerca in tutte le stanze",
- "field_placeholder": "Cerca…",
"result_count": {
"other": "(~%(count)s risultati)",
"one": "(~%(count)s risultato)"
},
- "this_room": "Questa stanza",
"this_room_button": "Cerca in questa stanza"
},
"show_labs_settings": "Mostra impostazioni Laboratori",
diff --git a/src/i18n/strings/ja.json b/src/i18n/strings/ja.json
index 2df008ecfc..8f8a217cfd 100644
--- a/src/i18n/strings/ja.json
+++ b/src/i18n/strings/ja.json
@@ -1814,13 +1814,10 @@
"rejecting": "招待を拒否しています…",
"rejoin_button": "再参加",
"search": {
- "all_rooms": "全てのルーム",
- "field_placeholder": "検索…",
"result_count": {
"other": "(〜%(count)s件)",
"one": "(〜%(count)s件)"
- },
- "this_room": "このルーム"
+ }
},
"show_labs_settings": "ラボの設定を表示",
"status_bar": {
diff --git a/src/i18n/strings/lo.json b/src/i18n/strings/lo.json
index 0556731e7e..08ef6e739c 100644
--- a/src/i18n/strings/lo.json
+++ b/src/i18n/strings/lo.json
@@ -1584,13 +1584,10 @@
"read_topic": "ກົດເພື່ອອ່ານຫົວຂໍ້",
"rejoin_button": "ເຂົ້າຮ່ວມອີກຄັ້ງ",
"search": {
- "all_rooms": "ຫ້ອງທັງໝົດ",
- "field_placeholder": "ຊອກຫາ…",
"result_count": {
"one": "(~%(count)sຜົນຮັບ)",
"other": "(~%(count)sຜົນຮັບ)"
- },
- "this_room": "ຫ້ອງນີ້"
+ }
},
"show_labs_settings": "ໂຊການຕັ້ງຄ່າສູນທົດລອງ",
"status_bar": {
diff --git a/src/i18n/strings/lt.json b/src/i18n/strings/lt.json
index 4be6e81d39..e7bc062aa5 100644
--- a/src/i18n/strings/lt.json
+++ b/src/i18n/strings/lt.json
@@ -1194,13 +1194,10 @@
"peek_join_prompt": "Jūs peržiūrite %(roomName)s. Norite prie jo prisijungti?",
"rejoin_button": "Prisijungti iš naujo",
"search": {
- "all_rooms": "Visi pokalbių kambariai",
- "field_placeholder": "Paieška…",
"result_count": {
"other": "(~%(count)s rezultatų(-ai))",
"one": "(~%(count)s rezultatas)"
- },
- "this_room": "Šis pokalbių kambarys"
+ }
},
"show_labs_settings": "Rodyti laboratorijų nustatymus",
"status_bar": {
diff --git a/src/i18n/strings/nl.json b/src/i18n/strings/nl.json
index 9d62fb7213..3a25d10c2d 100644
--- a/src/i18n/strings/nl.json
+++ b/src/i18n/strings/nl.json
@@ -1655,13 +1655,10 @@
"read_topic": "Klik om het onderwerp te lezen",
"rejoin_button": "Opnieuw toetreden",
"search": {
- "all_rooms": "Alle kamers",
- "field_placeholder": "Zoeken…",
"result_count": {
"one": "(~%(count)s resultaat)",
"other": "(~%(count)s resultaten)"
- },
- "this_room": "Deze kamer"
+ }
},
"show_labs_settings": "Lab instellingen weergeven",
"status_bar": {
diff --git a/src/i18n/strings/pl.json b/src/i18n/strings/pl.json
index bd9f57f6fe..f2761b8c0f 100644
--- a/src/i18n/strings/pl.json
+++ b/src/i18n/strings/pl.json
@@ -1454,7 +1454,9 @@
"location_share_live_description": "Implementacja tymczasowa. Lokalizacje są zapisywane w historii pokoju.",
"mjolnir": "Nowe sposoby na ignorowanie osób",
"msc3531_hide_messages_pending_moderation": "Daj moderatorom ukrycie wiadomości które są sprawdzane.",
- "new_room_decoration_ui": "W trakcie aktywnego rozwoju, nowy nagłówek pokoju & interfejs szczegółów",
+ "new_room_decoration_ui": "Nowy nagłówek pokoju",
+ "new_room_decoration_ui_beta_caption": "Nowy wygląd dla Twoich pokoi dzięki prostszemu i łatwiejszemu w użyciu nagłówkowi.",
+ "new_room_decoration_ui_beta_title": "Nagłówek pokoju",
"notification_settings": "Ustawienia nowych powiadomień",
"notification_settings_beta_caption": "Przedstawiamy prostszy sposób zmiany ustawień powiadomień. Dostosuj %(brand)s wedle swojego upodobania.",
"notification_settings_beta_title": "Ustawienia powiadomień",
@@ -1833,6 +1835,7 @@
"edit_integrations": "Edytuj widżety, mostki i boty",
"export_chat_button": "Eksportuj czat",
"files_button": "Pliki",
+ "info": "Info",
"pinned_messages": {
"empty": "Nie przypięto tu jeszcze niczego",
"explainer": "Jeżeli masz uprawnienia, przejdź do menu dowolnej wiadomości i wybierz Przypnij, aby przyczepić ją tutaj.",
@@ -1958,7 +1961,7 @@
"video_call_ec_layout_spotlight": "Centrum uwagi",
"video_room_view_chat_button": "Wyświetl oś czasu czatu"
},
- "header_face_pile_tooltip": "Przełącz listę członków",
+ "header_face_pile_tooltip": "Osoby",
"header_untrusted_label": "Niezaufany",
"inaccessible": "Ten pokój lub przestrzeń nie jest dostępna w tym momencie.",
"inaccessible_name": "%(roomName)s nie jest dostępny w tym momencie.",
@@ -2033,14 +2036,17 @@
"rejecting": "Odrzucanie zaproszenia…",
"rejoin_button": "Dołącz ponownie",
"search": {
- "all_rooms": "Wszystkie pokoje",
"all_rooms_button": "Wyszukaj wszystkie pokoje",
- "field_placeholder": "Szukaj…",
+ "placeholder": "Szukaj wiadomości...",
"result_count": {
"one": "(~%(count)s wynik)",
"other": "(~%(count)s wyników)"
},
- "this_room": "Ten pokój",
+ "summary": {
+ "one": "Znaleziono 1 wynik dla \"\"",
+ "few": "Znaleziono %(count)s wyniki dla \"\"",
+ "many": "Znaleziono %(count)s wyników dla \"\""
+ },
"this_room_button": "Wyszukaj ten pokój"
},
"show_labs_settings": "Pokaż ustawienia laboratoriów",
@@ -2419,6 +2425,8 @@
"always_show_message_timestamps": "Zawsze pokazuj znaczniki czasu wiadomości",
"appearance": {
"bundled_emoji_font": "Użyj wbudowanej czcionki emoji",
+ "compact_layout": "Pokaż kompaktowy tekst i wiadomości",
+ "compact_layout_description": "Aby korzystać z tej funkcji, należy wybrać nowoczesny układ.",
"custom_font": "Użyj czcionki systemowej",
"custom_font_description": "Wybierz nazwę czcionki zainstalowanej w systemie, a %(brand)s spróbuje jej użyć.",
"custom_font_name": "Nazwa czcionki systemowej",
@@ -2464,6 +2472,9 @@
"add_msisdn_dialog_title": "Dodaj numer telefonu",
"add_msisdn_instructions": "Wiadomość tekstowa wysłana do %(msisdn)s. Wprowadź kod weryfikacyjny w niej zawarty.",
"add_msisdn_misconfigured": "Dodaj / binduj za pomocą MSISDN flow zostało nieprawidłowo skonfigurowane",
+ "allow_spellcheck": "Zezwól na sprawdzanie pisowni",
+ "application_language": "Język aplikacji",
+ "application_language_reload_hint": "Aplikacja włączy się ponownie po zmianie języka",
"avatar_remove_progress": "Usuwanie obrazu...",
"avatar_save_progress": "Przesyłanie obrazu...",
"avatar_upload_error_text": "Format pliku nie jest obsługiwany lub obraz jest większy niż %(size)s.",
@@ -2517,7 +2528,7 @@
"identity_server_no_token": "Nie znaleziono tokena dostępu tożsamości",
"identity_server_not_set": "Serwer tożsamości nie jest ustawiony",
"incorrect_msisdn_verification": "Nieprawidłowy kod weryfikujący",
- "language_section": "Język i region",
+ "language_section": "Język",
"msisdn_in_use": "Ten numer telefonu jest już zajęty",
"msisdn_label": "Numer telefonu",
"msisdn_verification_field_label": "Kod weryfikacyjny",
@@ -2529,6 +2540,7 @@
"password_change_success": "Twoje hasło zostało pomyślnie zmienione.",
"personal_info": "Dane osobowe",
"profile_subtitle": "Tak widzą Cię inni w aplikacji.",
+ "profile_subtitle_oidc": "Twoje konto jest zarządzane oddzielnie przez dostawcę tożsamości, dlatego nie można tutaj zmienić niektórych danych osobowych.",
"remove_email_prompt": "Usunąć %(email)s?",
"remove_msisdn_prompt": "Usunąć %(phone)s?",
"spell_check_locale_placeholder": "Wybierz język",
@@ -3875,7 +3887,7 @@
"disable_camera": "Wyłącz kamerę",
"disable_microphone": "Wycisz mikrofon",
"disabled_no_one_here": "Nie ma tu nikogo, do kogo można zadzwonić",
- "disabled_no_perms_start_video_call": "Nie posiadasz wymaganych uprawnień do rozpoczęcia rozmowy wideo",
+ "disabled_no_perms_start_video_call": "Nie posiadasz uprawnień do rozpoczęcia rozmowy wideo",
"disabled_no_perms_start_voice_call": "Nie posiadasz uprawnień do rozpoczęcia rozmowy głosowej",
"disabled_ongoing_call": "Rozmowa w toku",
"element_call": "Element Call",
diff --git a/src/i18n/strings/pt_BR.json b/src/i18n/strings/pt_BR.json
index 8f124a21ed..989ac0297e 100644
--- a/src/i18n/strings/pt_BR.json
+++ b/src/i18n/strings/pt_BR.json
@@ -1296,13 +1296,10 @@
"peek_join_prompt": "Você está visualizando %(roomName)s. Deseja participar?",
"rejoin_button": "Entrar novamente",
"search": {
- "all_rooms": "Todas as salas",
- "field_placeholder": "Buscar…",
"result_count": {
"one": "(~%(count)s resultado)",
"other": "(~%(count)s resultados)"
- },
- "this_room": "Esta sala"
+ }
},
"status_bar": {
"exceeded_resource_limit": "Sua mensagem não foi enviada porque este servidor local excedeu o limite de recursos. Por favor, entre em contato com o seu administrador de serviços para continuar usando o serviço.",
diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json
index b91bab9c0d..dbfde41d43 100644
--- a/src/i18n/strings/ru.json
+++ b/src/i18n/strings/ru.json
@@ -1972,14 +1972,11 @@
"rejecting": "Отклонение приглашения…",
"rejoin_button": "Пере-присоединение",
"search": {
- "all_rooms": "Везде",
"all_rooms_button": "Поиск по всем комнатам",
- "field_placeholder": "Поиск…",
"result_count": {
"other": "(~%(count)s результатов)",
"one": "(~%(count)s результат)"
},
- "this_room": "В этой комнате",
"this_room_button": "Поиск в этой комнате"
},
"show_labs_settings": "Показать настройки лаборатории",
diff --git a/src/i18n/strings/sk.json b/src/i18n/strings/sk.json
index 70479854ef..faf57a637e 100644
--- a/src/i18n/strings/sk.json
+++ b/src/i18n/strings/sk.json
@@ -1977,14 +1977,11 @@
"rejecting": "Odmietnutie pozvania …",
"rejoin_button": "Znovu sa pripojiť",
"search": {
- "all_rooms": "Vo všetkých miestnostiach",
"all_rooms_button": "Vyhľadávať vo všetkých miestnostiach",
- "field_placeholder": "Hľadať…",
"result_count": {
"other": "(~%(count)s výsledkov)",
"one": "(~%(count)s výsledok)"
},
- "this_room": "V tejto miestnosti",
"this_room_button": "Vyhľadávať v tejto miestnosti"
},
"show_labs_settings": "Zobraziť nastavenia laboratórií",
diff --git a/src/i18n/strings/sq.json b/src/i18n/strings/sq.json
index b7227bbb70..736288371c 100644
--- a/src/i18n/strings/sq.json
+++ b/src/i18n/strings/sq.json
@@ -1858,14 +1858,11 @@
"rejecting": "Po hidhet poshtë ftesa…",
"rejoin_button": "Rihyni",
"search": {
- "all_rooms": "Krejt Dhomat",
"all_rooms_button": "Kërko në krejt dhomat",
- "field_placeholder": "Kërkoni…",
"result_count": {
"other": "(~%(count)s përfundime)",
"one": "(~%(count)s përfundim)"
},
- "this_room": "Këtë Dhomë",
"this_room_button": "Kërko në këtë dhomë"
},
"show_labs_settings": "Shfaq rregullime për Labs",
diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json
index 05d0671014..834fa1c981 100644
--- a/src/i18n/strings/sv.json
+++ b/src/i18n/strings/sv.json
@@ -1989,14 +1989,11 @@
"rejecting": "Nekar inbjudan …",
"rejoin_button": "Gå med igen",
"search": {
- "all_rooms": "Alla rum",
"all_rooms_button": "Sök i alla rum",
- "field_placeholder": "Sök…",
"result_count": {
"other": "(~%(count)s resultat)",
"one": "(~%(count)s resultat)"
},
- "this_room": "Det här rummet",
"this_room_button": "Sök i det här rummet"
},
"show_labs_settings": "Visa experimentinställningar",
diff --git a/src/i18n/strings/uk.json b/src/i18n/strings/uk.json
index a2e820c8b6..811e0234f8 100644
--- a/src/i18n/strings/uk.json
+++ b/src/i18n/strings/uk.json
@@ -1924,14 +1924,11 @@
"rejecting": "Відхилення запрошення…",
"rejoin_button": "Перепід'єднатись",
"search": {
- "all_rooms": "Усі кімнати",
"all_rooms_button": "Вибрати всі кімнати",
- "field_placeholder": "Пошук…",
"result_count": {
"one": "(~%(count)s результат)",
"other": "(~%(count)s результатів)"
},
- "this_room": "Ця кімната",
"this_room_button": "Шукати цю кімнату"
},
"show_labs_settings": "Відкрити налаштування експериментальних функцій",
diff --git a/src/i18n/strings/vi.json b/src/i18n/strings/vi.json
index b017e23a33..976976637e 100644
--- a/src/i18n/strings/vi.json
+++ b/src/i18n/strings/vi.json
@@ -1735,14 +1735,11 @@
"rejecting": "Từ chối lời mời…",
"rejoin_button": "Tham gia lại",
"search": {
- "all_rooms": "Tất cả các phòng",
"all_rooms_button": "Tìm tất cả phòng",
- "field_placeholder": "Tìm kiếm…",
"result_count": {
"one": "(~%(count)s kết quả)",
"other": "(~%(count)s kết quả)"
},
- "this_room": "phòng này",
"this_room_button": "Tìm trong phòng này"
},
"show_labs_settings": "Hiện các cài đặt thử nghiệm",
diff --git a/src/i18n/strings/zh_Hans.json b/src/i18n/strings/zh_Hans.json
index 7e78474ae9..c7d96fd36b 100644
--- a/src/i18n/strings/zh_Hans.json
+++ b/src/i18n/strings/zh_Hans.json
@@ -1772,13 +1772,10 @@
"read_topic": "点击阅读话题",
"rejoin_button": "重新加入",
"search": {
- "all_rooms": "全部房间",
- "field_placeholder": "搜索…",
"result_count": {
"one": "(~%(count)s 个结果)",
"other": "(~%(count)s 个结果)"
- },
- "this_room": "此房间"
+ }
},
"show_labs_settings": "显示实验室设置",
"status_bar": {
diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json
index bef357343d..d8d4edc30a 100644
--- a/src/i18n/strings/zh_Hant.json
+++ b/src/i18n/strings/zh_Hant.json
@@ -1927,14 +1927,11 @@
"rejecting": "正在回絕邀請…",
"rejoin_button": "重新加入",
"search": {
- "all_rooms": "所有聊天室",
"all_rooms_button": "搜尋所有聊天室",
- "field_placeholder": "搜尋…",
"result_count": {
"one": "(~%(count)s 結果)",
"other": "(~%(count)s 結果)"
},
- "this_room": "這個聊天室",
"this_room_button": "搜尋此聊天室"
},
"show_labs_settings": "顯示「實驗室」設定",
From 0a622f11fca9b6c710e752a5fb9bdfcdf8f6a139 Mon Sep 17 00:00:00 2001
From: ElementRobot
Date: Fri, 12 Jul 2024 07:22:06 +0100
Subject: [PATCH 19/59] [create-pull-request] automated change (#12767)
Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com>
---
playwright/plugins/homeserver/synapse/index.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/playwright/plugins/homeserver/synapse/index.ts b/playwright/plugins/homeserver/synapse/index.ts
index e5eb9384d0..8f04358ade 100644
--- a/playwright/plugins/homeserver/synapse/index.ts
+++ b/playwright/plugins/homeserver/synapse/index.ts
@@ -28,7 +28,7 @@ import { randB64Bytes } from "../../utils/rand";
// Docker tag to use for `matrixdotorg/synapse` image.
// We target a specific digest as every now and then a Synapse update will break our CI.
// This digest is updated by the playwright-image-updates.yaml workflow periodically.
-const DOCKER_TAG = "develop@sha256:c1aa9714b9e4470fe17fb61ed05d70cf6d78d0cac58930cc7ae847e72acfa561";
+const DOCKER_TAG = "develop@sha256:f7f460134dcaa841d5cab38d43b3016124e9c341925709db7ffcc018e93b368b";
async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise> {
const templateDir = path.join(__dirname, "templates", opts.template);
From 08ee1b8d97e31c3c78bc76eb4d671819c9827e2a Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 12 Jul 2024 12:23:44 +0100
Subject: [PATCH 20/59] Use multiple workers in Playwright CI to make use of
multiple cores (#12769)
* Use multiple workers in Playwright CI to make use of multiple cores
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* Iterate
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* Iterate
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---------
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
playwright.config.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/playwright.config.ts b/playwright.config.ts
index 458cf98daf..0e96d62c30 100644
--- a/playwright.config.ts
+++ b/playwright.config.ts
@@ -37,7 +37,7 @@ export default defineConfig({
},
testDir: "playwright/e2e",
outputDir: "playwright/test-results",
- workers: 1,
+ workers: process.env.CI ? "50%" : 1,
retries: process.env.CI ? 2 : 0,
reporter: process.env.CI ? [["blob"], ["github"]] : [["html", { outputFolder: "playwright/html-report" }]],
snapshotDir: "playwright/snapshots",
From 1082d767d44f6861e448b37620469b8b19f7b6b2 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 12 Jul 2024 12:23:53 +0100
Subject: [PATCH 21/59] Hide tooltips for certain playwright screenshots to
avoid flakiness (#12770)
* Hide tooltips for certain playwright screenshots to avoid flakiness
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* Typo
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* tsc
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---------
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
playwright/e2e/timeline/timeline.spec.ts | 5 +++++
playwright/element-web-test.ts | 11 +++++++++++
2 files changed, 16 insertions(+)
diff --git a/playwright/e2e/timeline/timeline.spec.ts b/playwright/e2e/timeline/timeline.spec.ts
index 47ec61aecf..2508f167e2 100644
--- a/playwright/e2e/timeline/timeline.spec.ts
+++ b/playwright/e2e/timeline/timeline.spec.ts
@@ -410,6 +410,7 @@ test.describe("Timeline", () => {
{
// Exclude timestamp from snapshot of mx_MainSplit
mask: [page.locator(".mx_MessageTimestamp")],
+ hideTooltips: true,
},
);
@@ -427,6 +428,7 @@ test.describe("Timeline", () => {
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot("expanded-gels-and-messages-irc-layout.png", {
// Exclude timestamp from snapshot of mx_MainSplit
mask: [page.locator(".mx_MessageTimestamp")],
+ hideTooltips: true,
});
// 3. Alignment of expanded GELS and placeholder of deleted message
@@ -447,6 +449,7 @@ test.describe("Timeline", () => {
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot("expanded-gels-redaction-placeholder.png", {
// Exclude timestamp from snapshot of mx_MainSplit
mask: [page.locator(".mx_MessageTimestamp")],
+ hideTooltips: true,
});
// 4. Alignment of expanded GELS, placeholder of deleted message, and emote
@@ -469,6 +472,7 @@ test.describe("Timeline", () => {
await expect(page.locator(".mx_MainSplit")).toMatchScreenshot("expanded-gels-emote-irc-layout.png", {
// Exclude timestamp from snapshot of mx_MainSplit
mask: [page.locator(".mx_MessageTimestamp")],
+ hideTooltips: true,
});
});
@@ -481,6 +485,7 @@ test.describe("Timeline", () => {
display: none !important;
}
`,
+ hideTooltips: true,
};
await sendEvent(app.client, room.roomId);
diff --git a/playwright/element-web-test.ts b/playwright/element-web-test.ts
index 75235fe03d..3b12cba8cf 100644
--- a/playwright/element-web-test.ts
+++ b/playwright/element-web-test.ts
@@ -306,12 +306,22 @@ export const expect = baseExpect.extend({
options?: {
mask?: Array;
omitBackground?: boolean;
+ hideTooltips?: boolean;
timeout?: number;
css?: string;
},
) {
const page = "page" in receiver ? receiver.page() : receiver;
+ let hideTooltipsCss: string | undefined;
+ if (options?.hideTooltips) {
+ hideTooltipsCss = `
+ .mx_Tooltip_visible {
+ visibility: hidden !important;
+ }
+ `;
+ }
+
// We add a custom style tag before taking screenshots
const style = (await page.addStyleTag({
content: `
@@ -339,6 +349,7 @@ export const expect = baseExpect.extend({
.mx_MessageTimestamp {
font-family: Inconsolata !important;
}
+ ${hideTooltipsCss ?? ""}
${options?.css ?? ""}
`,
})) as ElementHandle;
From ba7cf60cd825f94225887758cd0f97117caeef7b Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Fri, 12 Jul 2024 15:27:51 +0000
Subject: [PATCH 22/59] Update dependency @vector-im/compound-web to v5.4.0
(#12773)
* Update dependency @vector-im/compound-web to v5.4.0
* Update snapshots
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---------
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
---
.../__snapshots__/ThreadPanel-test.tsx.snap | 4 +-
.../RoomSummaryCard-test.tsx.snap | 4 +-
.../VideoRoomChatButton-test.tsx.snap | 2 +-
.../__snapshots__/RoomHeader-test.tsx.snap | 8 +--
.../ThemeChoicePanel-test.tsx.snap | 4 +-
.../__snapshots__/SpacePanel-test.tsx.snap | 2 +-
.../ThreadsActivityCentre-test.tsx.snap | 4 +-
yarn.lock | 58 ++++++++++++-------
8 files changed, 51 insertions(+), 35 deletions(-)
diff --git a/test/components/structures/__snapshots__/ThreadPanel-test.tsx.snap b/test/components/structures/__snapshots__/ThreadPanel-test.tsx.snap
index 1ca0c698dc..09bf4ae39b 100644
--- a/test/components/structures/__snapshots__/ThreadPanel-test.tsx.snap
+++ b/test/components/structures/__snapshots__/ThreadPanel-test.tsx.snap
@@ -12,7 +12,7 @@ exports[`ThreadPanel Header expect that All filter for ThreadPanelHeader properl
-
-
-
- Encryption
-
-
-
-
-
-
-
-
-
-
- Individually verify each session used by a user to mark it as trusted, not trusting cross-signed devices.
-
-
-
From 348000100a19cba4a3fb094c3c48fc0afe501343 Mon Sep 17 00:00:00 2001
From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
Date: Sat, 13 Jul 2024 11:27:59 +0100
Subject: [PATCH 24/59] Cleanup tasks in SecurityManager/SetupEncryptionStore
(#12764)
* Remove call to no-op `checkOwnCrossSigningTrust`
this is a no-op on rust crypto
* inline `SecurityManager.isCachingAllowed`
Since https://github.com/matrix-org/matrix-react-sdk/pull/4789, this has just
been an obscure way to write a test of a local variable.
* Remove unused `CreateSecretStorageOpts.getKeyBackupPassphrase` parameter
This is unused on rust crypto (cf https://github.com/matrix-org/matrix-js-sdk/pull/4313)
---
src/SecurityManager.ts | 39 +++----------------
.../security/CreateSecretStorageDialog.tsx | 17 +-------
src/stores/SetupEncryptionStore.ts | 2 -
test/test-utils/test-utils.ts | 1 -
4 files changed, 6 insertions(+), 53 deletions(-)
diff --git a/src/SecurityManager.ts b/src/SecurityManager.ts
index 873aec08e2..c5958a6f48 100644
--- a/src/SecurityManager.ts
+++ b/src/SecurityManager.ts
@@ -25,7 +25,6 @@ import { MatrixClientPeg } from "./MatrixClientPeg";
import { _t } from "./languageHandler";
import { isSecureBackupRequired } from "./utils/WellKnownUtils";
import AccessSecretStorageDialog, { KeyParams } from "./components/views/dialogs/security/AccessSecretStorageDialog";
-import RestoreKeyBackupDialog from "./components/views/dialogs/security/RestoreKeyBackupDialog";
import SettingsStore from "./settings/SettingsStore";
import { ModuleRunner } from "./modules/ModuleRunner";
import QuestionDialog from "./components/views/dialogs/QuestionDialog";
@@ -45,10 +44,6 @@ let dehydrationCache: {
keyInfo?: SecretStorage.SecretStorageKeyDescription;
} = {};
-function isCachingAllowed(): boolean {
- return secretStorageBeingAccessed;
-}
-
/**
* This can be used by other components to check if secret storage access is in
* progress, so that we can e.g. avoid intermittently showing toasts during
@@ -118,7 +113,7 @@ async function getSecretStorageKey({
}
// Check the in-memory cache
- if (isCachingAllowed() && secretStorageKeys[keyId]) {
+ if (secretStorageBeingAccessed && secretStorageKeys[keyId]) {
return [keyId, secretStorageKeys[keyId]];
}
@@ -226,7 +221,7 @@ function cacheSecretStorageKey(
keyInfo: SecretStorage.SecretStorageKeyDescription,
key: Uint8Array,
): void {
- if (isCachingAllowed()) {
+ if (secretStorageBeingAccessed) {
secretStorageKeys[keyId] = key;
secretStorageKeyInfo[keyId] = keyInfo;
}
@@ -278,26 +273,6 @@ export const crossSigningCallbacks: ICryptoCallbacks = {
getDehydrationKey,
};
-export async function promptForBackupPassphrase(): Promise {
- let key!: Uint8Array;
-
- const { finished } = Modal.createDialog(
- RestoreKeyBackupDialog,
- {
- showSummary: false,
- keyCallback: (k: Uint8Array) => (key = k),
- },
- undefined,
- /* priority = */ false,
- /* static = */ true,
- );
-
- const success = await finished;
- if (!success) throw new Error("Key backup prompt cancelled");
-
- return key;
-}
-
/**
* Carry out an operation that may require multiple accesses to secret storage, caching the key.
*
@@ -313,10 +288,8 @@ export async function withSecretStorageKeyCache(func: () => Promise): Prom
} finally {
// Clear secret storage key cache now that work is complete
secretStorageBeingAccessed = false;
- if (!isCachingAllowed()) {
- secretStorageKeys = {};
- secretStorageKeyInfo = {};
- }
+ secretStorageKeys = {};
+ secretStorageKeyInfo = {};
}
}
@@ -395,9 +368,7 @@ async function doAccessSecretStorage(func: () => Promise, forceReset: bool
}
},
});
- await crypto.bootstrapSecretStorage({
- getKeyBackupPassphrase: promptForBackupPassphrase,
- });
+ await crypto.bootstrapSecretStorage({});
const keyId = Object.keys(secretStorageKeys)[0];
if (keyId && SettingsStore.getValue("feature_dehydration")) {
diff --git a/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx
index 0316c43994..97469177d9 100644
--- a/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx
+++ b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx
@@ -26,7 +26,6 @@ import { BackupTrustInfo, GeneratedSecretStorageKey, KeyBackupInfo } from "matri
import { MatrixClientPeg } from "../../../../MatrixClientPeg";
import { _t, _td } from "../../../../languageHandler";
import Modal from "../../../../Modal";
-import { promptForBackupPassphrase } from "../../../../SecurityManager";
import { copyNode } from "../../../../utils/strings";
import { SSOAuthEntry } from "../../../../components/views/auth/InteractiveAuthEntryComponents";
import PassphraseField from "../../../../components/views/auth/PassphraseField";
@@ -123,7 +122,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent();
private passphraseField = createRef();
@@ -384,15 +382,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent this.recoveryKey!,
keyBackupInfo: this.state.backupInfo!,
setupNewKeyBackup: !this.state.backupInfo,
- getKeyBackupPassphrase: async (): Promise => {
- // We may already have the backup key if we earlier went
- // through the restore backup path, so pass it along
- // rather than prompting again.
- if (this.backupKey) {
- return this.backupKey;
- }
- return promptForBackupPassphrase();
- },
});
}
await initialiseDehydration(true);
@@ -424,11 +413,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent => {
- // It's possible we'll need the backup key later on for bootstrapping,
- // so let's stash it here, rather than prompting for it twice.
- const keyCallback = (k: Uint8Array): void => {
- this.backupKey = k;
- };
+ const keyCallback = (k: Uint8Array): void => {};
const { finished } = Modal.createDialog(
RestoreKeyBackupDialog,
diff --git a/src/stores/SetupEncryptionStore.ts b/src/stores/SetupEncryptionStore.ts
index 640ef2c7a4..dce523875e 100644
--- a/src/stores/SetupEncryptionStore.ts
+++ b/src/stores/SetupEncryptionStore.ts
@@ -152,8 +152,6 @@ export class SetupEncryptionStore extends EventEmitter {
// in the background.
await new Promise((resolve: (value?: unknown) => void, reject: (reason?: any) => void) => {
accessSecretStorage(async (): Promise => {
- await cli.checkOwnCrossSigningTrust();
-
// The remaining tasks (device dehydration and restoring
// key backup) may take some time due to processing many
// to-device messages in the case of device dehydration, or
diff --git a/test/test-utils/test-utils.ts b/test/test-utils/test-utils.ts
index 9b28a3077a..5370f72b18 100644
--- a/test/test-utils/test-utils.ts
+++ b/test/test-utils/test-utils.ts
@@ -116,7 +116,6 @@ export function createTestClient(): MatrixClient {
bootstrapCrossSigning: jest.fn(),
hasSecretStorageKey: jest.fn(),
getKeyBackupVersion: jest.fn(),
- checkOwnCrossSigningTrust: jest.fn(),
secretStorage: {
get: jest.fn(),
From 52c32f37c3bd0f0423e7c885a42c2c029a682c7a Mon Sep 17 00:00:00 2001
From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
Date: Sat, 13 Jul 2024 13:36:45 +0100
Subject: [PATCH 25/59] Add logging to encryption setup (#12765)
* Add logging to `getSecretStorageKey`
* Replace call to deprecated MatrixClient.hasSecretStorageKey
* Add/improve logging in `accessSecretStorage`
* Add/improve logging in SetupEncryptionStore.usePassPhrase
---
src/SecurityManager.ts | 46 ++++++++++++++++++++++--------
src/stores/SetupEncryptionStore.ts | 36 ++++++++++++-----------
test/SecurityManager-test.ts | 4 +--
3 files changed, 56 insertions(+), 30 deletions(-)
diff --git a/src/SecurityManager.ts b/src/SecurityManager.ts
index c5958a6f48..c1c4e7a72b 100644
--- a/src/SecurityManager.ts
+++ b/src/SecurityManager.ts
@@ -111,14 +111,17 @@ async function getSecretStorageKey({
}
[keyId, keyInfo] = keyInfoEntries[0];
}
+ logger.debug(`getSecretStorageKey: request for 4S keys [${Object.keys(keyInfos)}]: looking for key ${keyId}`);
// Check the in-memory cache
if (secretStorageBeingAccessed && secretStorageKeys[keyId]) {
+ logger.debug(`getSecretStorageKey: returning key ${keyId} from cache`);
return [keyId, secretStorageKeys[keyId]];
}
if (dehydrationCache.key) {
if (await MatrixClientPeg.safeGet().checkSecretStorageKey(dehydrationCache.key, keyInfo)) {
+ logger.debug("getSecretStorageKey: returning key from dehydration cache");
cacheSecretStorageKey(keyId, keyInfo, dehydrationCache.key);
return [keyId, dehydrationCache.key];
}
@@ -126,11 +129,12 @@ async function getSecretStorageKey({
const keyFromCustomisations = ModuleRunner.instance.extensions.cryptoSetup.getSecretStorageKey();
if (keyFromCustomisations) {
- logger.log("CryptoSetupExtension: Using key from extension (secret storage)");
+ logger.log("getSecretStorageKey: Using secret storage key from CryptoSetupExtension");
cacheSecretStorageKey(keyId, keyInfo, keyFromCustomisations);
return [keyId, keyFromCustomisations];
}
+ logger.debug("getSecretStorageKey: prompting user for key");
const inputToKey = makeInputToKey(keyInfo);
const { finished } = Modal.createDialog(
AccessSecretStorageDialog,
@@ -158,6 +162,7 @@ async function getSecretStorageKey({
if (!keyParams) {
throw new AccessCancelledError();
}
+ logger.debug("getSecretStorageKey: got key from user");
const key = await inputToKey(keyParams);
// Save to cache to avoid future prompts in the current session
@@ -282,11 +287,13 @@ export const crossSigningCallbacks: ICryptoCallbacks = {
* @param func - The operation to be wrapped.
*/
export async function withSecretStorageKeyCache(func: () => Promise): Promise {
+ logger.debug("SecurityManager: enabling 4S key cache");
secretStorageBeingAccessed = true;
try {
return await func();
} finally {
// Clear secret storage key cache now that work is complete
+ logger.debug("SecurityManager: disabling 4S key cache");
secretStorageBeingAccessed = false;
secretStorageKeys = {};
secretStorageKeyInfo = {};
@@ -322,7 +329,21 @@ export async function accessSecretStorage(func = async (): Promise => {},
async function doAccessSecretStorage(func: () => Promise, forceReset: boolean): Promise {
try {
const cli = MatrixClientPeg.safeGet();
- if (!(await cli.hasSecretStorageKey()) || forceReset) {
+ const crypto = cli.getCrypto();
+ if (!crypto) {
+ throw new Error("End-to-end encryption is disabled - unable to access secret storage.");
+ }
+
+ let createNew = false;
+ if (forceReset) {
+ logger.debug("accessSecretStorage: resetting 4S");
+ createNew = true;
+ } else if (!(await cli.secretStorage.hasKey())) {
+ logger.debug("accessSecretStorage: no 4S key configured, creating a new one");
+ createNew = true;
+ }
+
+ if (createNew) {
// This dialog calls bootstrap itself after guiding the user through
// passphrase creation.
const { finished } = Modal.createDialogAsync(
@@ -350,13 +371,10 @@ async function doAccessSecretStorage(func: () => Promise, forceReset: bool
throw new Error("Secret storage creation canceled");
}
} else {
- const crypto = cli.getCrypto();
- if (!crypto) {
- throw new Error("End-to-end encryption is disabled - unable to access secret storage.");
- }
-
+ logger.debug("accessSecretStorage: bootstrapCrossSigning");
await crypto.bootstrapCrossSigning({
authUploadDeviceSigningKeys: async (makeRequest): Promise => {
+ logger.debug("accessSecretStorage: performing UIA to upload cross-signing keys");
const { finished } = Modal.createDialog(InteractiveAuthDialog, {
title: _t("encryption|bootstrap_title"),
matrixClient: cli,
@@ -366,8 +384,10 @@ async function doAccessSecretStorage(func: () => Promise, forceReset: bool
if (!confirmed) {
throw new Error("Cross-signing key upload auth canceled");
}
+ logger.debug("accessSecretStorage: Cross-signing key upload successful");
},
});
+ logger.debug("accessSecretStorage: bootstrapSecretStorage");
await crypto.bootstrapSecretStorage({});
const keyId = Object.keys(secretStorageKeys)[0];
@@ -376,21 +396,23 @@ async function doAccessSecretStorage(func: () => Promise, forceReset: bool
if (secretStorageKeyInfo[keyId] && secretStorageKeyInfo[keyId].passphrase) {
dehydrationKeyInfo = { passphrase: secretStorageKeyInfo[keyId].passphrase };
}
- logger.log("Setting dehydration key");
+ logger.log("accessSecretStorage: Setting dehydration key");
await cli.setDehydrationKey(secretStorageKeys[keyId], dehydrationKeyInfo, "Backup device");
} else if (!keyId) {
- logger.warn("Not setting dehydration key: no SSSS key found");
+ logger.warn("accessSecretStorage: Not setting dehydration key: no SSSS key found");
} else {
- logger.log("Not setting dehydration key: feature disabled");
+ logger.log("accessSecretStorage: Not setting dehydration key: feature disabled");
}
}
+ logger.debug("accessSecretStorage: 4S now ready");
// `return await` needed here to ensure `finally` block runs after the
// inner operation completes.
- return await func();
+ await func();
+ logger.debug("accessSecretStorage: operation complete");
} catch (e) {
ModuleRunner.instance.extensions.cryptoSetup.catchAccessSecretStorageError(e as Error);
- logger.error(e);
+ logger.error("accessSecretStorage: error during operation", e);
// Re-throw so that higher level logic can abort as needed
throw e;
}
diff --git a/src/stores/SetupEncryptionStore.ts b/src/stores/SetupEncryptionStore.ts
index dce523875e..62a3412d81 100644
--- a/src/stores/SetupEncryptionStore.ts
+++ b/src/stores/SetupEncryptionStore.ts
@@ -135,6 +135,7 @@ export class SetupEncryptionStore extends EventEmitter {
}
public async usePassPhrase(): Promise {
+ logger.debug("SetupEncryptionStore.usePassphrase");
this.phase = Phase.Busy;
this.emit("update");
try {
@@ -142,21 +143,21 @@ export class SetupEncryptionStore extends EventEmitter {
const backupInfo = await cli.getKeyBackupVersion();
this.backupInfo = backupInfo;
this.emit("update");
- // The control flow is fairly twisted here...
- // For the purposes of completing security, we only wait on getting
- // as far as the trust check and then show a green shield.
- // We also begin the key backup restore as well, which we're
- // awaiting inside `accessSecretStorage` only so that it keeps your
- // passphase cached for that work. This dialog itself will only wait
- // on the first trust check, and the key backup restore will happen
- // in the background.
+
await new Promise((resolve: (value?: unknown) => void, reject: (reason?: any) => void) => {
accessSecretStorage(async (): Promise => {
- // The remaining tasks (device dehydration and restoring
- // key backup) may take some time due to processing many
- // to-device messages in the case of device dehydration, or
- // having many keys to restore in the case of key backups,
- // so we allow the dialog to advance before this.
+ // `accessSecretStorage` will call `boostrapCrossSigning` and `bootstrapSecretStorage`, so that
+ // should be enough to ensure that our device is correctly cross-signed.
+ //
+ // The remaining tasks (device dehydration and restoring key backup) may take some time due to
+ // processing many to-device messages in the case of device dehydration, or having many keys to
+ // restore in the case of key backups, so we allow the dialog to advance before this.
+ //
+ // However, we need to keep the 4S key cached, so we stay inside `accessSecretStorage`.
+ logger.debug(
+ "SetupEncryptionStore.usePassphrase: cross-signing and secret storage set up; checking " +
+ "dehydration and backup in the background",
+ );
resolve();
await initialiseDehydration();
@@ -168,14 +169,17 @@ export class SetupEncryptionStore extends EventEmitter {
});
if (await cli.getCrypto()?.getCrossSigningKeyId()) {
+ logger.debug("SetupEncryptionStore.usePassphrase: done");
this.phase = Phase.Done;
this.emit("update");
}
} catch (e) {
- if (!(e instanceof AccessCancelledError)) {
- logger.log(e);
+ if (e instanceof AccessCancelledError) {
+ logger.debug("SetupEncryptionStore.usePassphrase: user cancelled access to secret storage");
+ } else {
+ logger.log("SetupEncryptionStore.usePassphrase: error", e);
}
- // this will throw if the user hits cancel, so ignore
+
this.phase = Phase.Intro;
this.emit("update");
}
diff --git a/test/SecurityManager-test.ts b/test/SecurityManager-test.ts
index 15d1eb1dec..13d5f2f63f 100644
--- a/test/SecurityManager-test.ts
+++ b/test/SecurityManager-test.ts
@@ -31,7 +31,7 @@ describe("SecurityManager", () => {
bootstrapSecretStorage: () => {},
} as unknown as CryptoApi;
const client = stubClient();
- mocked(client.hasSecretStorageKey).mockResolvedValue(true);
+ client.secretStorage.hasKey = jest.fn().mockResolvedValue(true);
mocked(client.getCrypto).mockReturnValue(crypto);
// When I run accessSecretStorage
@@ -48,7 +48,7 @@ describe("SecurityManager", () => {
it("throws if crypto is unavailable", async () => {
// Given a client with no crypto
const client = stubClient();
- mocked(client.hasSecretStorageKey).mockResolvedValue(true);
+ client.secretStorage.hasKey = jest.fn().mockResolvedValue(true);
mocked(client.getCrypto).mockReturnValue(undefined);
// When I run accessSecretStorage
From 44454618d80dafe8bb4d9722e8c80c8bb8ec8fc8 Mon Sep 17 00:00:00 2001
From: ElementRobot
Date: Mon, 15 Jul 2024 07:16:24 +0100
Subject: [PATCH 26/59] [create-pull-request] automated change (#12776)
Co-authored-by: github-merge-queue
---
src/i18n/strings/cs.json | 2 --
src/i18n/strings/de_DE.json | 2 --
src/i18n/strings/el.json | 2 --
src/i18n/strings/eo.json | 2 --
src/i18n/strings/es.json | 2 --
src/i18n/strings/et.json | 2 --
src/i18n/strings/fa.json | 2 --
src/i18n/strings/fi.json | 2 --
src/i18n/strings/fr.json | 2 --
src/i18n/strings/gl.json | 2 --
src/i18n/strings/he.json | 2 --
src/i18n/strings/hu.json | 2 --
src/i18n/strings/id.json | 2 --
src/i18n/strings/is.json | 2 --
src/i18n/strings/it.json | 2 --
src/i18n/strings/ja.json | 2 --
src/i18n/strings/lo.json | 2 --
src/i18n/strings/lt.json | 2 --
src/i18n/strings/nl.json | 2 --
src/i18n/strings/pl.json | 15 +++++++--------
src/i18n/strings/pt_BR.json | 2 --
src/i18n/strings/ru.json | 2 --
src/i18n/strings/sk.json | 2 --
src/i18n/strings/sq.json | 2 --
src/i18n/strings/sv.json | 2 --
src/i18n/strings/uk.json | 2 --
src/i18n/strings/vi.json | 2 --
src/i18n/strings/zh_Hans.json | 2 --
src/i18n/strings/zh_Hant.json | 2 --
29 files changed, 7 insertions(+), 64 deletions(-)
diff --git a/src/i18n/strings/cs.json b/src/i18n/strings/cs.json
index 7b885d2ac9..7da68c59d3 100644
--- a/src/i18n/strings/cs.json
+++ b/src/i18n/strings/cs.json
@@ -2642,7 +2642,6 @@
"delete_backup_confirm_description": "Opravdu? Pokud klíče nejsou správně zálohované můžete přijít o šifrované zprávy.",
"e2ee_default_disabled_warning": "Správce vašeho serveru vypnul ve výchozím nastavení koncové šifrování v soukromých místnostech a přímých zprávách.",
"enable_message_search": "Povolit vyhledávání v šifrovaných místnostech",
- "encryption_individual_verification_mode": "Individuálně ověřit každou uživatelovu relaci a označit jí za důvěryhodnou, bez důvěry v křížový podpis.",
"encryption_section": "Šifrování",
"error_loading_key_backup_status": "Nepovedlo se načíst stav zálohy",
"export_megolm_keys": "Exportovat šifrovací klíče místností",
@@ -2661,7 +2660,6 @@
"key_backup_inactive": "Tato relace nezálohuje vaše klíče, ale už máte zálohu ze které je můžete obnovit.",
"key_backup_inactive_warning": "Vaše klíče nejsou z této relace zálohovány.",
"key_backup_latest_version": "Nejnovější verze zálohy na serveru:",
- "manually_verify_all_sessions": "Ručně ověřit všechny relace",
"message_search_disable_warning": "Když je to zakázané, zprávy v šifrovaných místnostech se nebudou objevovat ve výsledcích vyhledávání.",
"message_search_disabled": "Bezpečně uchovávat zprávy na tomto zařízení aby se v nich dalo vyhledávat.",
"message_search_enabled": {
diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json
index 5982ae172f..26ebeb05d6 100644
--- a/src/i18n/strings/de_DE.json
+++ b/src/i18n/strings/de_DE.json
@@ -2621,7 +2621,6 @@
"delete_backup_confirm_description": "Bist du sicher? Du wirst alle deine verschlüsselten Nachrichten verlieren, wenn deine Schlüssel nicht gut gesichert sind.",
"e2ee_default_disabled_warning": "Deine Server-Administration hat die Ende-zu-Ende-Verschlüsselung für private Räume und Direktnachrichten standardmäßig deaktiviert.",
"enable_message_search": "Nachrichtensuche in verschlüsselten Räumen aktivieren",
- "encryption_individual_verification_mode": "Alle Sitzungen einzeln verifizieren, anstatt auch Sitzungen zu vertrauen, die durch Quersignierungen verifiziert sind.",
"encryption_section": "Verschlüsselung",
"error_loading_key_backup_status": "Konnte Status der Schlüsselsicherung nicht laden",
"export_megolm_keys": "E2E-Raumschlüssel exportieren",
@@ -2640,7 +2639,6 @@
"key_backup_inactive": "Diese Sitzung sichert deine Schlüssel nicht, aber du hast eine vorhandene Sicherung, die du wiederherstellen und in Zukunft hinzufügen kannst.",
"key_backup_inactive_warning": "Deine Schlüssel werden von dieser Sitzung nicht gesichert.",
"key_backup_latest_version": "Letzte Backup-Version auf diesen Server:",
- "manually_verify_all_sessions": "Indirekte Sitzungen manuell verifizieren",
"message_search_disable_warning": "Wenn deaktiviert, werden Nachrichten von verschlüsselten Räumen nicht in den Ergebnissen auftauchen.",
"message_search_disabled": "Speichere verschlüsselte Nachrichten lokal, sodass sie deinen Suchergebnissen erscheinen können.",
"message_search_enabled": {
diff --git a/src/i18n/strings/el.json b/src/i18n/strings/el.json
index 78af9d60ea..2cc7aeafee 100644
--- a/src/i18n/strings/el.json
+++ b/src/i18n/strings/el.json
@@ -2108,7 +2108,6 @@
"delete_backup_confirm_description": "Είσαι σίγουρος? Θα χάσετε τα κρυπτογραφημένα μηνύματά σας εάν δε δημιουργηθούν σωστά αντίγραφα ασφαλείας των κλειδιών σας.",
"e2ee_default_disabled_warning": "Ο διαχειριστής του διακομιστή σας έχει απενεργοποιήσει την κρυπτογράφηση από άκρο σε άκρο από προεπιλογή σε ιδιωτικά δωμάτια & άμεσα μηνύματα.",
"enable_message_search": "Ενεργοποίηση αναζήτησης μηνυμάτων σε κρυπτογραφημένα δωμάτια",
- "encryption_individual_verification_mode": "Επαληθεύστε μεμονωμένα κάθε συνεδρία που χρησιμοποιείται από έναν χρήστη για να την επισημάνετε ως αξιόπιστη, χωρίς να εμπιστεύεστε συσκευές με διασταυρούμενη υπογραφή.",
"encryption_section": "Κρυπτογράφηση",
"error_loading_key_backup_status": "Δεν είναι δυνατή η φόρτωση της κατάστασης του αντιγράφου ασφαλείας κλειδιού",
"export_megolm_keys": "Εξαγωγή κλειδιών κρυπτογράφησης για το δωμάτιο",
@@ -2122,7 +2121,6 @@
"key_backup_connect_prompt": "Συνδέστε αυτήν την συνεδρία με το αντίγραφο ασφαλείας κλειδιού πριν αποσυνδεθείτε για να αποφύγετε την απώλεια κλειδιών που μπορεί να υπάρχουν μόνο σε αυτήν την συνεδρία.",
"key_backup_inactive": "Αυτή η συνεδρία δεν δημιουργεί αντίγραφα ασφαλείας των κλειδιών σας, αλλά έχετε ένα υπάρχον αντίγραφο ασφαλείας από το οποίο μπορείτε να επαναφέρετε και να προσθέσετε στη συνέχεια.",
"key_backup_inactive_warning": "Δεν δημιουργούνται αντίγραφα ασφαλείας των κλειδιών σας από αυτήν την συνεδρία.",
- "manually_verify_all_sessions": "Επαληθεύστε χειροκίνητα όλες τις απομακρυσμένες συνεδρίες",
"message_search_disable_warning": "Εάν απενεργοποιηθεί, τα μηνύματα από κρυπτογραφημένα δωμάτια δε θα εμφανίζονται στα αποτελέσματα αναζήτησης.",
"message_search_disabled": "Αποθηκεύστε με ασφάλεια κρυπτογραφημένα μηνύματα τοπικά για να εμφανίζονται στα αποτελέσματα αναζήτησης.",
"message_search_enabled": {
diff --git a/src/i18n/strings/eo.json b/src/i18n/strings/eo.json
index 45da8d4d92..acf1dcebb0 100644
--- a/src/i18n/strings/eo.json
+++ b/src/i18n/strings/eo.json
@@ -1892,7 +1892,6 @@
"delete_backup_confirm_description": "Ĉu vi certas? Vi perdos ĉiujn viajn ĉifritajn mesaĝojn, se viaj ŝlosiloj ne estas savkopiitaj.",
"e2ee_default_disabled_warning": "La administranto de via servilo malŝaltis implicitan tutvojan ĉifradon en privataj kaj individuaj ĉambroj.",
"enable_message_search": "Ŝalti serĉon de mesaĝoj en ĉifritaj ĉambroj",
- "encryption_individual_verification_mode": "Unuope kontroli ĉiun salutaĵon de uzanto por marki ĝin fidata, ne fidante delege subskribitajn aparatojn.",
"encryption_section": "Ĉifrado",
"error_loading_key_backup_status": "Ne povas enlegi staton de ŝlosila savkopio",
"export_megolm_keys": "Elporti tutvoje ĉifrajn ŝlosilojn de la ĉambro",
@@ -1906,7 +1905,6 @@
"key_backup_connect_prompt": "Konektu ĉi tiun salutaĵon al savkopiado de ŝlosiloj antaŭ ol vi adiaŭos, por ne perdi ŝlosilojn, kiuj povus troviĝi nur en ĉi tiu salutaĵo.",
"key_backup_inactive": "Ĉi tiu salutaĵo ne savkopias viajn ŝlosilojn, sed vi jam havas savkopion, el kiu vi povas rehavi datumojn, kaj ilin kreskigi plue.",
"key_backup_inactive_warning": "Viaj ŝlosiloj ne estas savkopiataj el ĉi tiu salutaĵo.",
- "manually_verify_all_sessions": "Permane kontroli ĉiujn forajn salutaĵojn",
"message_search_disable_warning": "Post malŝalto, mesaĝoj el ĉifritaj ĉambroj ne aperos en serĉorezultoj.",
"message_search_disabled": "Sekure kaŝmemori ĉifritajn mesaĝojn loke, por aperigi ilin en serĉrezultoj.",
"message_search_enabled": {
diff --git a/src/i18n/strings/es.json b/src/i18n/strings/es.json
index 3e7c7c9cb6..c3ff20c5a6 100644
--- a/src/i18n/strings/es.json
+++ b/src/i18n/strings/es.json
@@ -2402,7 +2402,6 @@
"delete_backup_confirm_description": "¿Estás seguro? Perderás tus mensajes cifrados si las claves no se copian adecuadamente.",
"e2ee_default_disabled_warning": "El administrador de tu servidor base ha desactivado el cifrado de extremo a extremo en salas privadas y mensajes directos.",
"enable_message_search": "Activar la búsqueda de mensajes en salas cifradas",
- "encryption_individual_verification_mode": "Verificar individualmente cada sesión utilizada por un usuario para marcarla como de confianza, no confiando en dispositivos de firma cruzada.",
"encryption_section": "Cifrado",
"error_loading_key_backup_status": "No se pudo cargar el estado de la copia de la clave",
"export_megolm_keys": "Exportar claves de salas con cifrado de extremo a extremo",
@@ -2416,7 +2415,6 @@
"key_backup_connect_prompt": "Conecte esta sesión a la copia de seguridad de las claves antes de firmar y así evitar perder las claves que sólo existen en esta sesión.",
"key_backup_inactive": "Esta sesión no ha creado una copia de seguridad de tus llaves, pero tienes una copia de seguridad existente de la que puedes restaurar y añadir para proceder.",
"key_backup_inactive_warning": "No se está haciendo una copia de seguridad de tus claves en esta sesión.",
- "manually_verify_all_sessions": "Verificar manualmente todas las sesiones remotas",
"message_search_disable_warning": "Si está desactivado, los mensajes de las salas cifradas no aparecerán en los resultados de búsqueda.",
"message_search_disabled": "Almacenar localmente, de manera segura, a los mensajes cifrados localmente para que aparezcan en los resultados de búsqueda.",
"message_search_enabled": {
diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json
index 5c2028d689..f2cc8b6922 100644
--- a/src/i18n/strings/et.json
+++ b/src/i18n/strings/et.json
@@ -2602,7 +2602,6 @@
"delete_backup_confirm_description": "Kas sa oled kindel? Kui sul muud varundust pole, siis kaotad ligipääsu oma krüptitud sõnumitele.",
"e2ee_default_disabled_warning": "Sinu serveri haldur on lülitanud läbiva krüptimise omavahelistes jututubades ja otsesõnumites välja.",
"enable_message_search": "Võta kasutusele sõnumite otsing krüptitud jututubades",
- "encryption_individual_verification_mode": "Ära usalda risttunnustamist ning verifitseeri kasutaja iga sessioon eraldi.",
"encryption_section": "Krüptimine",
"error_loading_key_backup_status": "Võtmete varunduse oleku laadimine ei õnnestunud",
"export_megolm_keys": "Ekspordi jututubade läbiva krüptimise võtmed",
@@ -2618,7 +2617,6 @@
"key_backup_in_progress": "Varundan %(sessionsRemaining)s krüptovõtmeid…",
"key_backup_inactive": "See sessioon ei varunda sinu krüptovõtmeid, aga sul on olemas varundus, millest saad taastada ning millele saad võtmeid lisada.",
"key_backup_inactive_warning": "Sinu selle sessiooni krüptovõtmeid ei varundata.",
- "manually_verify_all_sessions": "Verifitseeri käsitsi kõik välised sessioonid",
"message_search_disable_warning": "Kui see seadistus pole kasutusel, siis krüptitud jututubade sõnumeid otsing ei vaata.",
"message_search_disabled": "Turvaliselt puhverda krüptitud sõnumid kohalikku arvutisse ja võimalda kasutada neid otsingus.",
"message_search_enabled": {
diff --git a/src/i18n/strings/fa.json b/src/i18n/strings/fa.json
index 0a7a41e281..0120a7e76d 100644
--- a/src/i18n/strings/fa.json
+++ b/src/i18n/strings/fa.json
@@ -1672,7 +1672,6 @@
"delete_backup_confirm_description": "آیا اطمینان دارید؟ در صورتی که از کلیدهای شما به درستی پشتیبانگیری نشده باشد، تمام پیامهای رمزشدهی خود را از دست خواهید داد.",
"e2ee_default_disabled_warning": "مدیر سرور شما قابلیت رمزنگاری سرتاسر برای اتاقها و گفتگوهای خصوصی را به صورت پیشفرض غیرفعال کردهاست.",
"enable_message_search": "فعالسازی قابلیت جستجو در اتاقهای رمزشده",
- "encryption_individual_verification_mode": "به صورت جداگانه هر نشستی که با بقیهی کاربران دارید را تائید کنید تا به عنوان نشست قابل اعتماد نشانهگذاری شود، با این کار میتوانید به دستگاههای امضاء متقابل اعتماد نکنید.",
"encryption_section": "رمزنگاری",
"error_loading_key_backup_status": "امکان بارگیری و نمایش وضعیت کلید پشتیبان وجود ندارد",
"export_megolm_keys": "استخراج (Export) کلیدهای رمزنگاری اتاقها",
@@ -1686,7 +1685,6 @@
"key_backup_connect_prompt": "پیش از خروج از حساب کاربری، این نشست را به کلید پشتیبانگیر متصل نمائید. با این کار مانع از گمشدن کلیدهای که فقط بر روی این نشست وجود دارند میشوید.",
"key_backup_inactive": "این نشست از کلیدهای شما پشتیبانگیری نمیکند، با این حال شما یک نسخهی پشتیبان موجود دارید که میتوانید آن را بازیابی کنید.",
"key_backup_inactive_warning": "کلیدهای شما از این نشست پشتیبانگیری نمیشود.",
- "manually_verify_all_sessions": "به صورت دستی همهی نشستها را تائید نمائید",
"message_search_disable_warning": "اگر غیر فعال شود، پیامهای اتاقهای رمزشده در نتایج جستجوها نمایش داده نمیشوند.",
"message_search_disabled": "پیامهای رمزشده را به صورتی محلی و امن ذخیره کرده تا در نتایج جستجو ظاهر شوند.",
"message_search_enabled": {
diff --git a/src/i18n/strings/fi.json b/src/i18n/strings/fi.json
index 84ae773585..e1976e1bff 100644
--- a/src/i18n/strings/fi.json
+++ b/src/i18n/strings/fi.json
@@ -2278,7 +2278,6 @@
"delete_backup_confirm_description": "Oletko varma? Et voi lukea salattuja viestejäsi, mikäli avaimesi eivät ole kunnolla varmuuskopioituna.",
"e2ee_default_disabled_warning": "Palvelimesi ylläpitäjä on poistanut päästä päähän -salauksen oletuksena käytöstä yksityisissä huoneissa ja yksityisviesteissä.",
"enable_message_search": "Ota viestihaku salausta käyttävissä huoneissa käyttöön",
- "encryption_individual_verification_mode": "Varmenna jokainen istunto erikseen, äläkä luota ristiinvarmennettuihin laitteisiin.",
"encryption_section": "Salaus",
"error_loading_key_backup_status": "Avainten varmuuskopionnin tilan lukeminen epäonnistui",
"export_megolm_keys": "Tallenna osapuolten välisen salauksen huoneavaimet",
@@ -2292,7 +2291,6 @@
"key_backup_connect_prompt": "Yhdistä tämä istunto avainten varmuuskopiointiin ennen uloskirjautumista, jotta et menetä avaimia, jotka ovat vain tässä istunnossa.",
"key_backup_inactive": "Tämä istunto ei varmuuskopioi avaimiasi, mutta sillä on olemassaoleva varmuuskopio, jonka voit palauttaa ja lisätä jatkaaksesi.",
"key_backup_inactive_warning": "Avaimiasi ei varmuuskopioida tästä istunnosta.",
- "manually_verify_all_sessions": "Varmenna kaikki etäistunnot käsin",
"message_search_disable_warning": "Jos ei ole käytössä, salattujen huoneiden viestejä ei näytetä hakutuloksissa.",
"message_search_disabled": "Pidä salatut viestit turvallisessa välimuistissa, jotta ne näkyvät hakutuloksissa.",
"message_search_enabled": {
diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json
index ba4183e2df..9a35bd7f85 100644
--- a/src/i18n/strings/fr.json
+++ b/src/i18n/strings/fr.json
@@ -2672,7 +2672,6 @@
"delete_backup_confirm_description": "En êtes-vous sûr ? Vous perdrez vos messages chiffrés si vos clés ne sont pas sauvegardées correctement.",
"e2ee_default_disabled_warning": "L’administrateur de votre serveur a désactivé le chiffrement de bout en bout par défaut dans les salons privés et les conversations privées.",
"enable_message_search": "Activer la recherche de messages dans les salons chiffrés",
- "encryption_individual_verification_mode": "Vérifiez individuellement chaque session utilisée par un utilisateur pour la marquer comme fiable, sans faire confiance aux appareils signés avec la signature croisée.",
"encryption_section": "Chiffrement",
"error_loading_key_backup_status": "Impossible de charger l’état de sauvegarde des clés",
"export_megolm_keys": "Exporter les clés de chiffrement de salon",
@@ -2691,7 +2690,6 @@
"key_backup_inactive": "Cette session ne sauvegarde pas vos clés, mais vous n’avez pas de sauvegarde existante que vous pouvez restaurer ou compléter à l’avenir.",
"key_backup_inactive_warning": "Vos clés ne sont pas sauvegardées sur cette session.",
"key_backup_latest_version": "Dernière version de la sauvegarde sur le serveur :",
- "manually_verify_all_sessions": "Vérifier manuellement toutes les sessions à distance",
"message_search_disable_warning": "Si l’option est désactivée, les messages des salons chiffrés n’apparaîtront pas dans les résultats de recherche.",
"message_search_disabled": "Mettre en cache les messages chiffrés localement et de manière sécurisée pour qu’ils apparaissent dans les résultats de recherche.",
"message_search_enabled": {
diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json
index 894b221096..1c799134b9 100644
--- a/src/i18n/strings/gl.json
+++ b/src/i18n/strings/gl.json
@@ -2228,7 +2228,6 @@
"delete_backup_confirm_description": "Estás seguro? Perderás as mensaxes cifradas se non tes unha copia de apoio das chaves de cifrado.",
"e2ee_default_disabled_warning": "A administración do servidor desactivou por defecto o cifrado extremo-a-extremo en salas privadas e Mensaxes Directas.",
"enable_message_search": "Activar a busca de mensaxes en salas cifradas",
- "encryption_individual_verification_mode": "Verificar individualmente cada sesión utilizada pola usuaria para marcala como confiable, non confiando en dispositivos con sinatura cruzada.",
"encryption_section": "Cifrado",
"error_loading_key_backup_status": "Non se puido cargar o estado das chaves de apoio",
"export_megolm_keys": "Exportar chaves E2E da sala",
@@ -2242,7 +2241,6 @@
"key_backup_connect_prompt": "Conecta esta sesión ao gardado das chaves antes de desconectarte para evitar perder calquera chave que só puidese estar nesta sesión.",
"key_backup_inactive": "Esta sesión non está facendo copia das chaves, pero tes unha copia de apoio existente que podes restablecer e engadir para seguir adiante.",
"key_backup_inactive_warning": "As túas chaves non están a ser copiadas desde esta sesión.",
- "manually_verify_all_sessions": "Verificar manualmente todas as sesións remotas",
"message_search_disable_warning": "Se está desactivado, as mensaxes das salas cifradas non aparecerán nos resultados das buscas.",
"message_search_disabled": "Gardar de xeito seguro mensaxes cifradas na caché local para que aparezan nos resultados de buscas.",
"message_search_enabled": {
diff --git a/src/i18n/strings/he.json b/src/i18n/strings/he.json
index 360e0cd301..4cb91bc93e 100644
--- a/src/i18n/strings/he.json
+++ b/src/i18n/strings/he.json
@@ -1810,7 +1810,6 @@
"delete_backup_confirm_description": "האם אתה בטוח? תאבד את ההודעות המוצפנות שלך אם המפתחות שלך לא מגובים כראוי.",
"e2ee_default_disabled_warning": "מנהל השרת שלך השבית הצפנה מקצה לקצה כברירת מחדל בחדרים פרטיים ובהודעות ישירות.",
"enable_message_search": "אפשר חיפוש הודעות בחדרים מוצפנים",
- "encryption_individual_verification_mode": "אמת בנפרד כל מושב שמשתמש בו כדי לסמן אותו כאמצעי מהימן, ולא אמון על מכשירים חתומים צולבים.",
"encryption_section": "הצפנה",
"error_loading_key_backup_status": "לא ניתן לטעון את מצב גיבוי המפתח",
"export_megolm_keys": "ייצא מפתחות חדר E2E",
@@ -1824,7 +1823,6 @@
"key_backup_connect_prompt": "חבר את ההפעלה הזו לגיבוי המפתח לפני היציאה, כדי למנוע אובדן מפתחות שיכולים להיות רק בפגישה זו.",
"key_backup_inactive": "הפעלה זו אינה מגבה את המפתחות שלך , אך יש לך גיבוי קיים ממנו תוכל לשחזר ולהוסיף להמשך.",
"key_backup_inactive_warning": "המפתחות שלך אינם מגובים מהתחברות זו .",
- "manually_verify_all_sessions": "אמת באופן ידני את כל ההתחברויות",
"message_search_disable_warning": "אם מושבת, הודעות מחדרים מוצפנים לא יופיעו בתוצאות החיפוש.",
"message_search_disabled": "שמור באופן מאובטח הודעות מוצפנות באופן מקומי כדי שיופיעו בתוצאות החיפוש.",
"message_search_enabled": {
diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json
index b9fd14d926..8c0b1fcbd2 100644
--- a/src/i18n/strings/hu.json
+++ b/src/i18n/strings/hu.json
@@ -2563,7 +2563,6 @@
"delete_backup_confirm_description": "Biztos benne? Ha a kulcsai nincsenek megfelelően mentve, akkor elveszíti a titkosított üzeneteit.",
"e2ee_default_disabled_warning": "A kiszolgáló rendszergazdája alapértelmezetten kikapcsolta a végpontok közötti titkosítást a privát szobákban és a közvetlen beszélgetésekben.",
"enable_message_search": "Üzenetek keresésének bekapcsolása a titkosított szobákban",
- "encryption_individual_verification_mode": "A felhasználó által használt munkamenetek ellenőrzése egyenként, nem bízva az eszközök közti aláírással rendelkező eszközökben.",
"encryption_section": "Titkosítás",
"error_loading_key_backup_status": "A mentett kulcsok állapotát nem lehet betölteni",
"export_megolm_keys": "E2E szobakulcsok exportálása",
@@ -2582,7 +2581,6 @@
"key_backup_inactive": "Ez az munkamenet nem menti el a kulcsait, de van létező mentése, amelyből helyre tudja állítani, és amelyhez hozzá tudja adni a továbbiakban.",
"key_backup_inactive_warning": "A kulcsai nem kerülnek mentésre ebből a munkamenetből.",
"key_backup_latest_version": "A legújabb biztonsági mentés verziója a kiszolgálón:",
- "manually_verify_all_sessions": "Az összes távoli munkamenet kézi ellenőrzése",
"message_search_disable_warning": "Ha nincs engedélyezve akkor a titkosított szobák üzenetei nem jelennek meg a keresések között.",
"message_search_disabled": "A titkosított üzenetek biztonságos helyi gyorsítótárazása, hogy megjelenhessenek a keresési találatok között.",
"message_search_enabled": {
diff --git a/src/i18n/strings/id.json b/src/i18n/strings/id.json
index 688241ba5f..746f175216 100644
--- a/src/i18n/strings/id.json
+++ b/src/i18n/strings/id.json
@@ -2596,7 +2596,6 @@
"delete_backup_confirm_description": "Apakah Anda yakin? Anda akan kehilangan pesan terenkripsi jika kunci Anda tidak dicadangkan dengan benar.",
"e2ee_default_disabled_warning": "Admin server Anda telah menonaktifkan enkripsi ujung ke ujung secara bawaan di ruangan privat & Pesan Langsung.",
"enable_message_search": "Aktifkan pencarian pesan di ruangan terenkripsi",
- "encryption_individual_verification_mode": "Verifikasi setiap sesi yang digunakan oleh pengguna satu per satu untuk menandainya sebagai tepercaya, dan tidak memercayai perangkat yang ditandatangani silang.",
"encryption_section": "Enkripsi",
"error_loading_key_backup_status": "Tidak dapat memuat status pencadangan kunci",
"export_megolm_keys": "Ekspor kunci ruangan enkripsi ujung ke ujung",
@@ -2615,7 +2614,6 @@
"key_backup_inactive": "Sesi ini tidak mencadangkan kunci Anda, tetapi Anda memiliki cadangan yang ada yang dapat Anda pulihkan dan tambahkan untuk selanjutnya.",
"key_backup_inactive_warning": "Kunci Anda tidak dicadangan dari sesi ini.",
"key_backup_latest_version": "Versi cadangan terbaru di server:",
- "manually_verify_all_sessions": "Verifikasi semua sesi jarak jauh secara manual",
"message_search_disable_warning": "Jika dinonaktifkan, pesan dari ruangan terenkripsi tidak akan muncul di hasil pencarian.",
"message_search_disabled": "Simpan pesan terenkripsi secara lokal dengan aman agar muncul di hasil pencarian.",
"message_search_enabled": {
diff --git a/src/i18n/strings/is.json b/src/i18n/strings/is.json
index 16b5dd9abb..af7c28014e 100644
--- a/src/i18n/strings/is.json
+++ b/src/i18n/strings/is.json
@@ -2111,7 +2111,6 @@
"delete_backup_confirm_description": "Ertu viss? Þú munt tapa dulrituðu skilaboðunum þínum ef dulritunarlyklarnir þínir eru ekki rétt öryggisafritaðir.",
"e2ee_default_disabled_warning": "Kerfisstjóri netþjónsins þíns hefur lokað á sjálfvirka dulritun í einkaspjallrásum og beinum skilaboðum.",
"enable_message_search": "Virka skilaboðleit í dulrituðum spjallrásum",
- "encryption_individual_verification_mode": "Sannreyndu hverja setu sem notandinn notar til að merkja hana sem treysta, án þess að treyta kross-undirrituðum tækjum.",
"encryption_section": "Dulritun",
"error_loading_key_backup_status": "Tókst ekki að hlaða inn stöðu öryggisafritunar dulritunarlykla",
"export_megolm_keys": "Flytja út E2E dulritunarlykla spjallrásar",
@@ -2124,7 +2123,6 @@
"key_backup_connect": "Tengja þessa setu við öryggisafrit af lykli",
"key_backup_inactive": "Þessi seta er ekki að öryggisafrita dulritunarlyklana þína, en þú ert með fyrirliggjandi öryggisafrit sem þú getur endurheimt úr og notað til að halda áfram.",
"key_backup_inactive_warning": "Dulritunarlyklarnir þínir eru ekki öryggisafritaðir úr þessari setu.",
- "manually_verify_all_sessions": "Sannreyna handvirkt allar fjartengdar setur",
"message_search_disable_warning": "Ef þetta er óvirkt, munu skilaboð frá dulrituðum spjallrásum ekki birtast í leitarniðurstöðum.",
"message_search_disabled": "Setja dulrituð skilaboð leynilega í skyndiminni á tækinu svo þau birtist í leitarniðurstöðum.",
"message_search_enabled": {
diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json
index b2f99ba1b1..a5960e2d3a 100644
--- a/src/i18n/strings/it.json
+++ b/src/i18n/strings/it.json
@@ -2638,7 +2638,6 @@
"delete_backup_confirm_description": "Sei sicuro? Perderai i tuoi messaggi cifrati se non hai salvato adeguatamente le tue chiavi.",
"e2ee_default_disabled_warning": "L'amministratore del server ha disattivato la crittografia end-to-end in modo predefinito nelle stanze private e nei messaggi diretti.",
"enable_message_search": "Attiva la ricerca messaggi nelle stanze cifrate",
- "encryption_individual_verification_mode": "Verifica individualmente ogni sessione usata da un utente per segnarla come fidata, senza fidarsi dei dispositivi a firma incrociata.",
"encryption_section": "Crittografia",
"error_loading_key_backup_status": "Impossibile caricare lo stato del backup delle chiavi",
"export_megolm_keys": "Esporta chiavi E2E della stanza",
@@ -2657,7 +2656,6 @@
"key_backup_inactive": "Questa sessione non sta facendo il backup delle tue chiavi, ma hai un backup esistente dal quale puoi ripristinare e che puoi usare da ora in poi.",
"key_backup_inactive_warning": "Il backup chiavi non viene fatto per questa sessione.",
"key_backup_latest_version": "Ultima versione del backup sul server:",
- "manually_verify_all_sessions": "Verifica manualmente tutte le sessioni remote",
"message_search_disable_warning": "Se disattivato, i messaggi delle stanze cifrate non appariranno nei risultati di ricerca.",
"message_search_disabled": "Tieni in cache localmente i messaggi cifrati in modo sicuro affinché appaiano nei risultati di ricerca.",
"message_search_enabled": {
diff --git a/src/i18n/strings/ja.json b/src/i18n/strings/ja.json
index 8f8a217cfd..788e305fbb 100644
--- a/src/i18n/strings/ja.json
+++ b/src/i18n/strings/ja.json
@@ -2396,7 +2396,6 @@
"delete_backup_confirm_description": "本当によろしいですか? もし鍵が正常にバックアップされていない場合、暗号化されたメッセージにアクセスできなくなります。",
"e2ee_default_disabled_warning": "サーバー管理者は、非公開のルームとダイレクトメッセージで既定でエンドツーエンド暗号化を無効にしています。",
"enable_message_search": "暗号化されたルームでメッセージの検索を有効にする",
- "encryption_individual_verification_mode": "クロス署名された端末を信頼せず、ユーザーが使用する各セッションを個別に認証し、信頼済に設定。",
"encryption_section": "暗号化",
"error_loading_key_backup_status": "鍵のバックアップの状態を読み込めません",
"export_megolm_keys": "ルームのエンドツーエンド暗号鍵をエクスポート",
@@ -2412,7 +2411,6 @@
"key_backup_in_progress": "%(sessionsRemaining)s個の鍵をバックアップしています…",
"key_backup_inactive": "このセッションでは鍵をバックアップしていませんが、復元に使用したり、今後鍵を追加したりできるバックアップがあります。",
"key_backup_inactive_warning": "鍵はこのセッションからバックアップされていません。",
- "manually_verify_all_sessions": "全てのリモートセッションを手動で認証",
"message_search_disable_warning": "無効にすると、暗号化されたルームのメッセージは検索結果に表示されません。",
"message_search_disabled": "検索結果の表示用に、暗号化されたメッセージをローカルに安全にキャッシュしています。",
"message_search_enabled": {
diff --git a/src/i18n/strings/lo.json b/src/i18n/strings/lo.json
index 08ef6e739c..5cecf688f5 100644
--- a/src/i18n/strings/lo.json
+++ b/src/i18n/strings/lo.json
@@ -2136,7 +2136,6 @@
"delete_backup_confirm_description": "ທ່ານແນ່ໃຈບໍ່? ທ່ານຈະສູນເສຍຂໍ້ຄວາມທີ່ເຂົ້າລະຫັດໄວ້ຫາກກະແຈຂອງທ່ານບໍ່ຖືກສຳຮອງຂໍ້ມູນຢ່າງຖືກຕ້ອງ.",
"e2ee_default_disabled_warning": "ຜູ້ຄຸມເຊີບເວີຂອງທ່ານໄດ້ປິດການນຳໃຊ້ການເຂົ້າລະຫັດແບບຕົ້ນທາງຮອດປາຍທາງໂດຍຄ່າເລີ່ມຕົ້ນໃນຫ້ອງສ່ວນຕົວ ແລະ ຂໍ້ຄວາມໂດຍກົງ.",
"enable_message_search": "ເປີດໃຊ້ການຊອກຫາຂໍ້ຄວາມຢູ່ໃນຫ້ອງທີ່ຖືກເຂົ້າລະຫັດ",
- "encryption_individual_verification_mode": "ຢືນຢັນແຕ່ລະລະບົບທີ່ໃຊ້ໂດຍຜູ້ໃຊ້ເພື່ອໝາຍວ່າເປັນທີ່ໜ້າເຊື່ອຖືໄດ້, ບໍ່ໄວ້ໃຈອຸປະກອນທີ່ cross-signed.",
"encryption_section": "ການເຂົ້າລະຫັດ",
"error_loading_key_backup_status": "ບໍ່ສາມາດໂຫຼດສະຖານະສຳຮອງລະຫັດໄດ້",
"export_megolm_keys": "ສົ່ງກະແຈຫ້ອງ E2E ອອກ",
@@ -2150,7 +2149,6 @@
"key_backup_connect_prompt": "ເຊື່ອມຕໍ່ລະບົບນີ້ກັບການສໍາຮອງກະແຈກ່ອນທີ່ຈະອອກຈາກລະບົບເພື່ອຫຼີກເວັ້ນການສູນເສຍກະແຈທີ່ອາດຢູ່ໃນລະບົບນີ້ເທົ່ານັ້ນ.",
"key_backup_inactive": "ລະບົບນີ້ແມ່ນ ບໍ່ໄດ້ສໍາຮອງລະຫັດຂອງທ່ານ, ແຕ່ທ່ານມີການສໍາຮອງຂໍ້ມູນທີ່ມີຢູ່ແລ້ວທີ່ທ່ານສາມາດກູ້ຄືນຈາກ ແລະເພີ່ມຕໍ່ໄປ.",
"key_backup_inactive_warning": "ກະແຈຂອງທ່ານ ບໍ່ຖືກສຳຮອງຂໍ້ມູນຈາກລະບົບນີ້.",
- "manually_verify_all_sessions": "ຢັ້ງຢືນທຸກລະບົບທາງໄກດ້ວຍຕົນເອງ",
"message_search_disable_warning": "ຖ້າປິດໃຊ້ງານ, ຂໍ້ຄວາມຈາກຫ້ອງທີ່ເຂົ້າລະຫັດຈະບໍ່ປາກົດຢູ່ໃນຜົນການຄົ້ນຫາ.",
"message_search_disabled": "ເກັບຮັກສາຂໍ້ຄວາມທີ່ຖືກເຂົ້າລະຫັດໄວ້ຢ່າງປອດໄພຢູ່ໃນເຄື່ອງເພື່ອໃຫ້ປາກົດໃນຜົນການຄົ້ນຫາ.",
"message_search_enabled": {
diff --git a/src/i18n/strings/lt.json b/src/i18n/strings/lt.json
index e7bc062aa5..cd092b5cbf 100644
--- a/src/i18n/strings/lt.json
+++ b/src/i18n/strings/lt.json
@@ -1703,7 +1703,6 @@
"delete_backup_confirm_description": "Ar tikrai? Jūs prarasite savo šifruotas žinutes, jei jūsų raktams nebus tinkamai sukurtos atsarginės kopijos.",
"e2ee_default_disabled_warning": "Serverio administratorius išjungė visapusį šifravimą, kaip numatytą, privačiuose kambariuose ir Tiesioginėse Žinutėse.",
"enable_message_search": "Įjungti žinučių paiešką užšifruotuose kambariuose",
- "encryption_individual_verification_mode": "Individualiai patikrinkite kiekvieną vartotojo naudojamą seansą, kad pažymėtumėte jį kaip patikimą, nepasitikint kryžminiu pasirašymu patvirtintais įrenginiais.",
"encryption_section": "Šifravimas",
"error_loading_key_backup_status": "Nepavyko įkelti atsarginės raktų kopijos būklės",
"export_megolm_keys": "Eksportuoti E2E (visapusio šifravimo) kambarių raktus",
@@ -1717,7 +1716,6 @@
"key_backup_connect_prompt": "Prieš atsijungdami prijunkite šį seansą prie atsarginės raktų kopijos, kad neprarastumėte raktų, kurie gali būti tik šiame seanse.",
"key_backup_inactive": "Šis seansas nekuria atsarginių raktų kopijų, bet jūs jau turite atsarginę kopiją iš kurios galite atkurti ir pridėti.",
"key_backup_inactive_warning": "Jūsų raktams nėra daromos atsarginės kopijos iš šio seanso.",
- "manually_verify_all_sessions": "Rankiniu būdu patvirtinti visus nuotolinius seansus",
"message_search_disabled": "Šifruotas žinutes saugiai talpinkite lokaliai, kad jos būtų rodomos paieškos rezultatuose.",
"message_search_enabled": {
"one": "Saugiai talpinkite užšifruotas žinutes vietoje, kad jos būtų rodomos paieškos rezultatuose, naudojant %(size)s žinutėms iš %(rooms)s kambario saugoti.",
diff --git a/src/i18n/strings/nl.json b/src/i18n/strings/nl.json
index 3a25d10c2d..e09544f451 100644
--- a/src/i18n/strings/nl.json
+++ b/src/i18n/strings/nl.json
@@ -2226,7 +2226,6 @@
"delete_backup_confirm_description": "Weet je het zeker? Je zal je versleutelde berichten verliezen als je sleutels niet correct geback-upt zijn.",
"e2ee_default_disabled_warning": "De beheerder van je server heeft eind-tot-eind-versleuteling standaard uitgeschakeld in alle privékamers en directe gesprekken.",
"enable_message_search": "Zoeken in versleutelde kamers inschakelen",
- "encryption_individual_verification_mode": "Verifieer elke sessie die door een persoon wordt gebruikt afzonderlijk. Dit markeert hen als vertrouwd zonder te vertrouwen op kruislings ondertekende apparaten.",
"encryption_section": "Versleuteling",
"error_loading_key_backup_status": "Kan sleutelback-upstatus niet laden",
"export_megolm_keys": "E2E-kamersleutels exporteren",
@@ -2240,7 +2239,6 @@
"key_backup_connect_prompt": "Verbind deze sessie met de sleutelback-up voordat je jezelf afmeldt. Dit voorkomt dat je sleutels verliest die alleen op deze sessie voorkomen.",
"key_backup_inactive": "Deze sessie maakt geen back-ups van je sleutels, maar je beschikt over een reeds bestaande back-up waaruit je kan herstellen en waaraan je nieuwe sleutels vanaf nu kunt toevoegen.",
"key_backup_inactive_warning": "Jouw sleutels worden niet geback-upt van deze sessie.",
- "manually_verify_all_sessions": "Handmatig alle externe sessies verifiëren",
"message_search_disable_warning": "Dit moet aan staan om te kunnen zoeken in versleutelde kamers.",
"message_search_disabled": "Sla versleutelde berichten veilig lokaal op om ze doorzoekbaar te maken.",
"message_search_enabled": {
diff --git a/src/i18n/strings/pl.json b/src/i18n/strings/pl.json
index f2761b8c0f..82e2a50f7f 100644
--- a/src/i18n/strings/pl.json
+++ b/src/i18n/strings/pl.json
@@ -1612,7 +1612,7 @@
"all_messages_description": "Otrzymuj powiadomienie każdej wiadomości",
"class_global": "Globalne",
"class_other": "Inne",
- "default": "Zwykły",
+ "default": "Domyślne",
"email_pusher_app_display_name": "Powiadomienia e-mail",
"enable_prompt_toast_description": "Włącz powiadomienia na pulpicie",
"enable_prompt_toast_title": "Powiadomienia",
@@ -1742,7 +1742,7 @@
"admin": "Administrator",
"custom": "Własny (%(level)s)",
"custom_level": "Własny poziom",
- "default": "Zwykły",
+ "default": "Domyślne",
"label": "Poziom uprawnień",
"mod": "Moderator",
"moderator": "Moderator",
@@ -1948,7 +1948,7 @@
"hide_widgets_button": "Ukryj widżety",
"n_people_asking_to_join": {
"one": "Prosi o dołączenie",
- "few": "%(count)s osób prosi o dołączenie",
+ "few": "%(count)s osoby proszą o dołączenie",
"many": "%(count)s osób prosi o dołączenie"
},
"room_is_public": "Ten pokój jest publiczny",
@@ -2440,7 +2440,7 @@
"font_size": "Rozmiar czcionki",
"font_size_default": "%(fontSize)s (domyślny)",
"high_contrast": "Wysoki kontrast",
- "image_size_default": "Zwykły",
+ "image_size_default": "Domyślne",
"image_size_large": "Duży",
"layout_bubbles": "Dymki wiadomości",
"layout_irc": "IRC (eksperymentalny)",
@@ -2732,7 +2732,6 @@
"dialog_title": "Ustawienia: Bezpieczeństwo i prywatność",
"e2ee_default_disabled_warning": "Twój administrator serwera wyłączył domyślne szyfrowanie end-to-end w pokojach i wiadomościach prywatnych.",
"enable_message_search": "Włącz wyszukiwanie wiadomości w szyfrowanych pokojach",
- "encryption_individual_verification_mode": "Indywidualnie weryfikuj każdą sesję używaną przez użytkownika, aby oznaczyć ją jako zaufaną, nie ufając urządzeniom weryfikowanym krzyżowo.",
"encryption_section": "Szyfrowanie",
"error_loading_key_backup_status": "Nie można załadować stanu kopii zapasowej klucza",
"export_megolm_keys": "Eksportuj klucze E2E pokojów",
@@ -2751,7 +2750,6 @@
"key_backup_inactive": "Ta sesja nie wykonuje kopii zapasowej twoich kluczy, ale masz istniejącą kopię którą możesz przywrócić i uzupełniać w przyszłości.",
"key_backup_inactive_warning": "Twoje klucze nie są zapisywanie na tej sesji.",
"key_backup_latest_version": "Najnowsza wersja kopii zapasowej na serwerze:",
- "manually_verify_all_sessions": "Ręcznie weryfikuj wszystkie zdalne sesje",
"message_search_disable_warning": "Jeśli wyłączone, wiadomości z szyfrowanych pokojów nie pojawią się w wynikach wyszukiwania.",
"message_search_disabled": "Bezpiecznie przechowuj lokalnie wiadomości szyfrowane, aby mogły się wyświetlać w wynikach wyszukiwania.",
"message_search_enabled": {
@@ -3258,7 +3256,7 @@
"creation_summary_dm": "%(creator)s utworzył tę wiadomość prywatną.",
"creation_summary_room": "%(creator)s stworzył i skonfigurował pokój.",
"decryption_failure": {
- "blocked": "Nadawca zablokował Ci możliwość otrzymania tej wiadomości",
+ "blocked": "Nadawca zablokował Ci możliwość otrzymania tej wiadomości ponieważ twoje urządzenie nie zostało zweryfikowane",
"historical_event_no_key_backup": "Historia wiadomości nie jest dostępna na tym urządzeniu",
"historical_event_unverified_device": "Musisz zweryfikować to urządzenie, aby wyświetlić historię wiadomości",
"historical_event_user_not_joined": "Nie masz dostępu do tej wiadomości",
@@ -3518,7 +3516,8 @@
},
"read_receipt_title": {
"one": "Odczytane przez %(count)s osobę",
- "other": "Odczytane przez %(count)s osób"
+ "few": "Odczytane przez %(count)s osoby",
+ "many": "Odczytane przez %(count)s osób"
},
"read_receipts_label": "Czytaj potwierdzenia",
"redacted": {
diff --git a/src/i18n/strings/pt_BR.json b/src/i18n/strings/pt_BR.json
index 989ac0297e..61c632dddf 100644
--- a/src/i18n/strings/pt_BR.json
+++ b/src/i18n/strings/pt_BR.json
@@ -1801,7 +1801,6 @@
"delete_backup_confirm_description": "Tem certeza? Você perderá suas mensagens criptografadas se não tiver feito o backup de suas chaves.",
"e2ee_default_disabled_warning": "O administrador do servidor desativou a criptografia de ponta a ponta por padrão em salas privadas e em conversas.",
"enable_message_search": "Ativar busca de mensagens em salas criptografadas",
- "encryption_individual_verification_mode": "Verifique individualmente cada sessão usada por um usuário para marcá-la como confiável, em vez de confirmar em aparelhos autoverificados.",
"encryption_section": "Criptografia",
"error_loading_key_backup_status": "Não foi possível carregar o status do backup da chave",
"export_megolm_keys": "Exportar chaves ponta-a-ponta da sala",
@@ -1815,7 +1814,6 @@
"key_backup_connect_prompt": "Autorize esta sessão a fazer o backup de chaves antes de se desconectar, para evitar perder chaves que possam estar apenas nesta sessão.",
"key_backup_inactive": "Esta sessão não está fazendo backup de suas chaves, mas você tem um backup existente que pode restaurar para continuar.",
"key_backup_inactive_warning": "Suas chaves não estão sendo copiadas desta sessão.",
- "manually_verify_all_sessions": "Verificar manualmente todas as sessões remotas",
"message_search_disable_warning": "Se desativado, as mensagens de salas criptografadas não aparecerão em resultados de buscas.",
"message_search_disabled": "Armazene mensagens criptografadas de forma segura localmente para que possam aparecer nos resultados das buscas.",
"message_search_enabled": {
diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json
index dbfde41d43..37beb7194a 100644
--- a/src/i18n/strings/ru.json
+++ b/src/i18n/strings/ru.json
@@ -2621,7 +2621,6 @@
"delete_backup_confirm_description": "Вы уверены? Зашифрованные сообщения будут безвозвратно утеряны при отсутствии соответствующего резервного копирования ваших ключей.",
"e2ee_default_disabled_warning": "Администратор вашего сервера отключил сквозное шифрование по умолчанию в приватных комнатах и диалогах.",
"enable_message_search": "Включить поиск сообщений в зашифрованных комнатах",
- "encryption_individual_verification_mode": "Отдельно подтверждать каждый сеанс пользователя как доверенный, не доверяя кросс-подписанным устройствам.",
"encryption_section": "Шифрование",
"error_loading_key_backup_status": "Не удалось получить статус резервного копирования для ключей шифрования",
"export_megolm_keys": "Экспорт ключей шифрования",
@@ -2640,7 +2639,6 @@
"key_backup_inactive": "Это сеанс не сохраняет ваши ключи, но у вас есть резервная копия, из которой вы можете их восстановить.",
"key_backup_inactive_warning": "Ваши ключи не резервируются с этом сеансе.",
"key_backup_latest_version": "Последняя версия резервной копии на сервере:",
- "manually_verify_all_sessions": "Подтверждать вручную все сеансы на других устройствах",
"message_search_disable_warning": "Если этот параметр отключен, сообщения из зашифрованных комнат не будут отображаться в результатах поиска.",
"message_search_disabled": "Безопасно кэшировать шифрованные сообщения локально, чтобы они появлялись в результатах поиска.",
"message_search_enabled": {
diff --git a/src/i18n/strings/sk.json b/src/i18n/strings/sk.json
index faf57a637e..a8afeae2d6 100644
--- a/src/i18n/strings/sk.json
+++ b/src/i18n/strings/sk.json
@@ -2625,7 +2625,6 @@
"delete_backup_confirm_description": "Ste si istí? Ak nemáte správne zálohované šifrovacie kľúče, prídete o históriu šifrovaných konverzácií.",
"e2ee_default_disabled_warning": "Správca vášho servera predvolene vypol end-to-end šifrovanie v súkromných miestnostiach a v priamych správach.",
"enable_message_search": "Povoliť vyhľadávanie správ v šifrovaných miestnostiach",
- "encryption_individual_verification_mode": "Individuálne overte každú používateľskú reláciu a označte ju za dôveryhodnú, bez dôvery krížovo podpísaných zariadení.",
"encryption_section": "Šifrovanie",
"error_loading_key_backup_status": "Nie je možné načítať stav zálohy kľúčov",
"export_megolm_keys": "Exportovať end-to-end šifrovacie kľúče miestnosti",
@@ -2644,7 +2643,6 @@
"key_backup_inactive": "Táto relácia nezálohuje vaše kľúče, ale máte jednu existujúcu zálohu, ktorú môžete obnoviť a pridať do budúcnosti.",
"key_backup_inactive_warning": "Vaše kľúče nie sú zálohované z tejto relácie.",
"key_backup_latest_version": "Najnovšia verzia zálohy na serveri:",
- "manually_verify_all_sessions": "Manuálne overiť všetky relácie",
"message_search_disable_warning": "Ak nie je povolené, správy zo zašifrovaných miestností sa nezobrazia vo výsledkoch vyhľadávania.",
"message_search_disabled": "Bezpečne lokálne ukladať zašifrované správy do vyrovnávacej pamäte, aby sa zobrazovali vo výsledkoch vyhľadávania.",
"message_search_enabled": {
diff --git a/src/i18n/strings/sq.json b/src/i18n/strings/sq.json
index 736288371c..ab55e38ce4 100644
--- a/src/i18n/strings/sq.json
+++ b/src/i18n/strings/sq.json
@@ -2459,7 +2459,6 @@
"delete_backup_confirm_description": "Jeni i sigurt? Do të humbni mesazhet tuaj të fshehtëzuar, nëse kopjeruajtja për kyçet tuaj nuk bëhet si duhet.",
"e2ee_default_disabled_warning": "Përgjegjësi i shërbyesit tuaj ka çaktivizuar fshehtëzimin skaj-më-skaj, si parazgjedhje, në dhoma private & Mesazhe të Drejtpërdrejtë.",
"enable_message_search": "Aktivizo kërkim mesazhesh në dhoma të fshehtëzuara",
- "encryption_individual_verification_mode": "Verifikoni individualisht çdo sesion të përdorur nga një përdorues, për t’i vënë shenjë si i besuar, duke mos besuar pajisje cross-signed.",
"encryption_section": "Fshehtëzim",
"error_loading_key_backup_status": "S’arrihet të ngarkohet gjendje kopjeruajtjeje kyçesh",
"export_megolm_keys": "Eksporto kyçe dhome E2E",
@@ -2475,7 +2474,6 @@
"key_backup_in_progress": "Po kopjeruhen kyçet për %(sessionsRemaining)s…",
"key_backup_inactive": "Ky sesion nuk po bën kopjeruajtje të kyçeve tuaja, por keni një kopjeruajtje ekzistuese që mund ta përdorni për rimarrje dhe ta shtoni më tej.",
"key_backup_inactive_warning": "Kyçet tuaj nuk po kopjeruhen nga ky sesion.",
- "manually_verify_all_sessions": "Verifikoni dorazi krejt sesionet e largët",
"message_search_disable_warning": "Në u çaktivizoftë, mesazhet prej dhomash të fshehtëzuara s’do të duken në përfundime kërkimi.",
"message_search_disabled": "Ruaj lokalisht në mënyrë të sigurt në fshehtinë mesazhet që të shfaqen në përfundime kërkimi.",
"message_search_enabled": {
diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json
index 834fa1c981..77fe12ee6e 100644
--- a/src/i18n/strings/sv.json
+++ b/src/i18n/strings/sv.json
@@ -2637,7 +2637,6 @@
"delete_backup_confirm_description": "Är du säker? Du kommer att förlora dina krypterade meddelanden om dina nycklar inte säkerhetskopieras ordentligt.",
"e2ee_default_disabled_warning": "Din serveradministratör har inaktiverat totalsträckskryptering som förval för privata rum och direktmeddelanden.",
"enable_message_search": "Aktivera meddelandesökning i krypterade rum",
- "encryption_individual_verification_mode": "Verifiera individuellt varje session som används av en användare för att markera den som betrodd, och lita inte på korssignerade enheter.",
"encryption_section": "Kryptering",
"error_loading_key_backup_status": "Kunde inte ladda status för nyckelsäkerhetskopiering",
"export_megolm_keys": "Exportera krypteringsrumsnycklar",
@@ -2656,7 +2655,6 @@
"key_backup_inactive": "Den här servern säkerhetskopierar inte dina nycklar, men du har en existerande säkerhetskopia du kan återställa ifrån och lägga till till i framtiden.",
"key_backup_inactive_warning": "Dina nycklar säkerhetskopieras inte från den här sessionen.",
"key_backup_latest_version": "Senaste säkerhetskopieringsversionen på servern:",
- "manually_verify_all_sessions": "Verifiera alla fjärrsessioner manuellt",
"message_search_disable_warning": "Om den är inaktiverad visas inte meddelanden från krypterade rum i sökresultaten.",
"message_search_disabled": "Cachar krypterade meddelanden säkert lokalt för att de ska visas i sökresultat.",
"message_search_enabled": {
diff --git a/src/i18n/strings/uk.json b/src/i18n/strings/uk.json
index 811e0234f8..1f3279ed9d 100644
--- a/src/i18n/strings/uk.json
+++ b/src/i18n/strings/uk.json
@@ -2562,7 +2562,6 @@
"delete_backup_confirm_description": "Ви впевнені? Ви втратите ваші зашифровані повідомлення якщо копія ключів не була створена коректно.",
"e2ee_default_disabled_warning": "Адміністратором вашого сервера було вимкнено автоматичне наскрізне шифрування у приватних кімнатах і особистих повідомленнях.",
"enable_message_search": "Увімкнути шукання повідомлень у зашифрованих кімнатах",
- "encryption_individual_verification_mode": "Індивідуально звіряйте кожен сеанс, який використовується користувачем, щоб позначити його довіреним, не довіряючи пристроям перехресного підписування.",
"encryption_section": "Шифрування",
"error_loading_key_backup_status": "Не вдалося завантажити стан резервного копіювання ключа",
"export_megolm_keys": "Експортувати ключі наскрізного шифрування кімнат",
@@ -2581,7 +2580,6 @@
"key_backup_inactive": "Цей сеанс не створює резервну копію ваших ключів, але у вас є резервна копія, з якої ви можете їх відновити.",
"key_backup_inactive_warning": "Резервна копія ваших ключів не створюється з цього сеансу.",
"key_backup_latest_version": "Остання версія резервної копії на сервері:",
- "manually_verify_all_sessions": "Звірити всі сеанси власноруч",
"message_search_disable_warning": "Якщо вимкнути, пошук не показуватиме повідомлень зашифрованих кімнат.",
"message_search_disabled": "Безпечно локально кешувати зашифровані повідомлення щоб вони з'являлись у результатах пошуку.",
"message_search_enabled": {
diff --git a/src/i18n/strings/vi.json b/src/i18n/strings/vi.json
index 976976637e..a3e2488cdb 100644
--- a/src/i18n/strings/vi.json
+++ b/src/i18n/strings/vi.json
@@ -2336,7 +2336,6 @@
"delete_backup_confirm_description": "Bạn có chắc không? Bạn sẽ mất các tin nhắn được mã hóa nếu các khóa của bạn không được sao lưu đúng cách.",
"e2ee_default_disabled_warning": "Người quản trị máy chủ của bạn đã vô hiệu hóa mã hóa đầu cuối theo mặc định trong phòng riêng và Tin nhắn trực tiếp.",
"enable_message_search": "Bật tính năng tìm kiếm tin nhắn trong các phòng được mã hóa",
- "encryption_individual_verification_mode": "Xác thực riêng từng phiên được người dùng sử dụng để đánh dấu phiên đó là đáng tin cậy, không tin cậy vào các thiết bị được xác thực chéo.",
"encryption_section": "Mã hóa",
"error_loading_key_backup_status": "Không thể tải trạng thái sao lưu khóa",
"export_megolm_keys": "Xuất các mã khoá phòng E2E",
@@ -2352,7 +2351,6 @@
"key_backup_in_progress": "Đang sao lưu %(sessionsRemaining)s khóa…",
"key_backup_inactive": "Phiên này đang không sao lưu các khóa, nhưng bạn có một bản sao lưu hiện có, bạn có thể khôi phục và thêm vào để về sau.",
"key_backup_inactive_warning": "Các khóa của bạn not being backed up from this session.",
- "manually_verify_all_sessions": "Xác thực thủ công tất cả các phiên từ xa",
"message_search_disable_warning": "Nếu bị tắt, tin nhắn từ các phòng được mã hóa sẽ không xuất hiện trong kết quả tìm kiếm.",
"message_search_disabled": "Bộ nhớ cache an toàn các tin nhắn được mã hóa cục bộ để chúng xuất hiện trong kết quả tìm kiếm.",
"message_search_enabled": {
diff --git a/src/i18n/strings/zh_Hans.json b/src/i18n/strings/zh_Hans.json
index c7d96fd36b..77dc330ef1 100644
--- a/src/i18n/strings/zh_Hans.json
+++ b/src/i18n/strings/zh_Hans.json
@@ -2353,7 +2353,6 @@
"delete_backup_confirm_description": "你确定吗?如果密钥没有正确地备份你将失去你的加密消息。",
"e2ee_default_disabled_warning": "你的服务器管理员默认关闭了私人房间和私聊中的端到端加密。",
"enable_message_search": "在加密房间中启用消息搜索",
- "encryption_individual_verification_mode": "逐一验证用户的每一个会话以将其标记为已信任,而不信任交叉签名的设备。",
"encryption_section": "加密",
"error_loading_key_backup_status": "无法载入密钥备份状态",
"export_megolm_keys": "导出房间的端到端加密密钥",
@@ -2368,7 +2367,6 @@
"key_backup_connect_prompt": "在登出前连接此会话到密钥备份以避免丢失可能仅在此会话上的密钥。",
"key_backup_inactive": "此会话未备份你的密钥,但如果你已有现存备份,你可以继续并从中恢复和向其添加。",
"key_backup_inactive_warning": "你的密钥没有被此会话备份。",
- "manually_verify_all_sessions": "手动验证所有远程会话",
"message_search_disable_warning": "如果被禁用,加密房间内的消息不会显示在搜索结果中。",
"message_search_disabled": "在本地安全地缓存加密消息以使其出现在搜索结果中。",
"message_search_enabled": {
diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json
index d8d4edc30a..f4ab63d787 100644
--- a/src/i18n/strings/zh_Hant.json
+++ b/src/i18n/strings/zh_Hant.json
@@ -2562,7 +2562,6 @@
"delete_backup_confirm_description": "您確定嗎?如果沒有正確備份金鑰的話,將會遺失所有加密訊息。",
"e2ee_default_disabled_warning": "您的伺服器管理員已停用在私密聊天室與私人訊息中預設的端對端加密。",
"enable_message_search": "在已加密的聊天室中啟用訊息搜尋",
- "encryption_individual_verification_mode": "逐一手動驗證使用者的工作階段,將其標記為受信任階段,不透過裝置的交叉簽署機制來信任。",
"encryption_section": "加密",
"error_loading_key_backup_status": "無法載入金鑰備份狀態",
"export_megolm_keys": "匯出聊天室的端對端加密金鑰",
@@ -2578,7 +2577,6 @@
"key_backup_in_progress": "正在備份 %(sessionsRemaining)s 把金鑰…",
"key_backup_inactive": "此工作階段並未備份您的金鑰,您可還原先前的備份後再繼續新增金鑰到備份內容中。",
"key_backup_inactive_warning": "您並未備份此裝置的金鑰。",
- "manually_verify_all_sessions": "手動驗證所有遠端工作階段",
"message_search_disable_warning": "若停用,從加密聊天室傳來的訊息將不會出現在搜尋結果中。",
"message_search_disabled": "將加密的訊息安全地在本機快取以出現在顯示結果中。",
"message_search_enabled": {
From 3221f7cadeb295d976bd3c0525eccb63862f30ad Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Mon, 15 Jul 2024 10:08:34 +0100
Subject: [PATCH 27/59] Align `widget_build_url_ignore_dm` with call behaviour
switch between 1:1 and Widget (#12760)
* Align `widget_build_url_ignore_dm` with call behaviour switch between 1:1 and Widget
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* Add tests
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* Improve coverage
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---------
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
src/LegacyCallHandler.tsx | 16 +++---
src/hooks/room/useRoomCall.ts | 3 +-
src/widgets/ManagedHybrid.ts | 42 +++++++---------
test/LegacyCallHandler-test.ts | 9 ++++
test/widgets/ManagedHybrid-test.ts | 79 +++++++++++++++++++++++++-----
5 files changed, 103 insertions(+), 46 deletions(-)
diff --git a/src/LegacyCallHandler.tsx b/src/LegacyCallHandler.tsx
index c8ee1d5c74..145784fe0c 100644
--- a/src/LegacyCallHandler.tsx
+++ b/src/LegacyCallHandler.tsx
@@ -861,6 +861,12 @@ export default class LegacyCallHandler extends EventEmitter {
public async placeCall(roomId: string, type: CallType, transferee?: MatrixCall): Promise {
const cli = MatrixClientPeg.safeGet();
+ const room = cli.getRoom(roomId);
+ if (!room) {
+ logger.error(`Room ${roomId} does not exist.`);
+ return;
+ }
+
// Pause current broadcast, if any
SdkContextClass.instance.voiceBroadcastPlaybacksStore.getCurrent()?.pause();
@@ -871,8 +877,8 @@ export default class LegacyCallHandler extends EventEmitter {
}
// We might be using managed hybrid widgets
- if (isManagedHybridWidgetEnabled(roomId)) {
- await addManagedHybridWidget(roomId);
+ if (isManagedHybridWidgetEnabled(room)) {
+ await addManagedHybridWidget(room);
return;
}
@@ -902,12 +908,6 @@ export default class LegacyCallHandler extends EventEmitter {
return;
}
- const room = cli.getRoom(roomId);
- if (!room) {
- logger.error(`Room ${roomId} does not exist.`);
- return;
- }
-
// We leave the check for whether there's already a call in this room until later,
// otherwise it can race.
diff --git a/src/hooks/room/useRoomCall.ts b/src/hooks/room/useRoomCall.ts
index fd2ed9c750..8dc18040a1 100644
--- a/src/hooks/room/useRoomCall.ts
+++ b/src/hooks/room/useRoomCall.ts
@@ -271,8 +271,7 @@ export const useRoomCall = (
}, [isViewingCall, room.roomId]);
// We hide the voice call button if it'd have the same effect as the video call button
- let hideVoiceCallButton =
- isManagedHybridWidgetEnabled(room.roomId) || !callOptions.includes(PlatformCallType.LegacyCall);
+ let hideVoiceCallButton = isManagedHybridWidgetEnabled(room) || !callOptions.includes(PlatformCallType.LegacyCall);
let hideVideoCallButton = false;
// We hide both buttons if they require widgets but widgets are disabled.
if (memberCount > 2 && !SettingsStore.getValue(UIFeature.Widgets)) {
diff --git a/src/widgets/ManagedHybrid.ts b/src/widgets/ManagedHybrid.ts
index ff06c295e6..6617933d97 100644
--- a/src/widgets/ManagedHybrid.ts
+++ b/src/widgets/ManagedHybrid.ts
@@ -16,6 +16,7 @@ limitations under the License.
import { IWidget } from "matrix-widget-api";
import { logger } from "matrix-js-sdk/src/logger";
+import { Room } from "matrix-js-sdk/src/matrix";
import { MatrixClientPeg } from "../MatrixClientPeg";
import { getCallBehaviourWellKnown } from "../utils/WellKnownUtils";
@@ -24,7 +25,7 @@ import { IStoredLayout, WidgetLayoutStore } from "../stores/widgets/WidgetLayout
import WidgetEchoStore from "../stores/WidgetEchoStore";
import WidgetStore, { IApp } from "../stores/WidgetStore";
import SdkConfig from "../SdkConfig";
-import DMRoomMap from "../utils/DMRoomMap";
+import { getJoinedNonFunctionalMembers } from "../utils/room/getJoinedNonFunctionalMembers";
/* eslint-disable camelcase */
interface IManagedHybridWidgetData {
@@ -34,8 +35,9 @@ interface IManagedHybridWidgetData {
}
/* eslint-enable camelcase */
-function getWidgetBuildUrl(roomId: string): string | undefined {
- const isDm = !!DMRoomMap.shared().getUserIdForRoomId(roomId);
+function getWidgetBuildUrl(room: Room): string | undefined {
+ const functionalMembers = getJoinedNonFunctionalMembers(room);
+ const isDm = functionalMembers.length === 2;
if (SdkConfig.get().widget_build_url) {
if (isDm && SdkConfig.get().widget_build_url_ignore_dm) {
return undefined;
@@ -51,35 +53,29 @@ function getWidgetBuildUrl(roomId: string): string | undefined {
return wellKnown?.widget_build_url;
}
-export function isManagedHybridWidgetEnabled(roomId: string): boolean {
- return !!getWidgetBuildUrl(roomId);
+export function isManagedHybridWidgetEnabled(room: Room): boolean {
+ return !!getWidgetBuildUrl(room);
}
-export async function addManagedHybridWidget(roomId: string): Promise {
- const cli = MatrixClientPeg.safeGet();
- const room = cli.getRoom(roomId);
- if (!room) {
- return;
- }
-
+export async function addManagedHybridWidget(room: Room): Promise {
// Check for permission
- if (!WidgetUtils.canUserModifyWidgets(cli, roomId)) {
- logger.error(`User not allowed to modify widgets in ${roomId}`);
+ if (!WidgetUtils.canUserModifyWidgets(room.client, room.roomId)) {
+ logger.error(`User not allowed to modify widgets in ${room.roomId}`);
return;
}
// Get widget data
/* eslint-disable-next-line camelcase */
- const widgetBuildUrl = getWidgetBuildUrl(roomId);
+ const widgetBuildUrl = getWidgetBuildUrl(room);
if (!widgetBuildUrl) {
return;
}
let widgetData: IManagedHybridWidgetData;
try {
- const response = await fetch(`${widgetBuildUrl}?roomId=${roomId}`);
+ const response = await fetch(`${widgetBuildUrl}?roomId=${room.roomId}`);
widgetData = await response.json();
} catch (e) {
- logger.error(`Managed hybrid widget builder failed for room ${roomId}`, e);
+ logger.error(`Managed hybrid widget builder failed for room ${room.roomId}`, e);
return;
}
if (!widgetData) {
@@ -88,21 +84,21 @@ export async function addManagedHybridWidget(roomId: string): Promise {
const { widget_id: widgetId, widget: widgetContent, layout } = widgetData;
// Ensure the widget is not already present in the room
- let widgets = WidgetStore.instance.getApps(roomId);
- const existing = widgets.some((w) => w.id === widgetId) || WidgetEchoStore.roomHasPendingWidgets(roomId, []);
+ let widgets = WidgetStore.instance.getApps(room.roomId);
+ const existing = widgets.some((w) => w.id === widgetId) || WidgetEchoStore.roomHasPendingWidgets(room.roomId, []);
if (existing) {
- logger.error(`Managed hybrid widget already present in room ${roomId}`);
+ logger.error(`Managed hybrid widget already present in room ${room.roomId}`);
return;
}
// Add the widget
try {
- await WidgetUtils.setRoomWidgetContent(cli, roomId, widgetId, {
+ await WidgetUtils.setRoomWidgetContent(room.client, room.roomId, widgetId, {
...widgetContent,
"io.element.managed_hybrid": true,
});
} catch (e) {
- logger.error(`Unable to add managed hybrid widget in room ${roomId}`, e);
+ logger.error(`Unable to add managed hybrid widget in room ${room.roomId}`, e);
return;
}
@@ -110,7 +106,7 @@ export async function addManagedHybridWidget(roomId: string): Promise {
if (!WidgetLayoutStore.instance.canCopyLayoutToRoom(room)) {
return;
}
- widgets = WidgetStore.instance.getApps(roomId);
+ widgets = WidgetStore.instance.getApps(room.roomId);
const installedWidget = widgets.find((w) => w.id === widgetId);
if (!installedWidget) {
return;
diff --git a/test/LegacyCallHandler-test.ts b/test/LegacyCallHandler-test.ts
index 005119cb7b..c310db55ab 100644
--- a/test/LegacyCallHandler-test.ts
+++ b/test/LegacyCallHandler-test.ts
@@ -52,6 +52,7 @@ import { mkVoiceBroadcastInfoStateEvent } from "./voice-broadcast/utils/test-uti
import { SdkContextClass } from "../src/contexts/SDKContext";
import Modal from "../src/Modal";
import { createAudioContext } from "../src/audio/compat";
+import * as ManagedHybrid from "../src/widgets/ManagedHybrid";
jest.mock("../src/Modal");
@@ -315,6 +316,7 @@ describe("LegacyCallHandler", () => {
document.body.removeChild(audioElement);
SdkConfig.reset();
+ jest.restoreAllMocks();
});
it("should look up the correct user and start a call in the room when a phone number is dialled", async () => {
@@ -403,6 +405,13 @@ describe("LegacyCallHandler", () => {
expect(callHandler.getCallForRoom(NATIVE_ROOM_CHARLIE)).toBe(fakeCall);
});
+ it("should place calls using managed hybrid widget if enabled", async () => {
+ const spy = jest.spyOn(ManagedHybrid, "addManagedHybridWidget");
+ jest.spyOn(ManagedHybrid, "isManagedHybridWidgetEnabled").mockReturnValue(true);
+ await callHandler.placeCall(NATIVE_ROOM_ALICE, CallType.Voice);
+ expect(spy).toHaveBeenCalledWith(MatrixClientPeg.safeGet().getRoom(NATIVE_ROOM_ALICE));
+ });
+
describe("when listening to a voice broadcast", () => {
let voiceBroadcastPlayback: VoiceBroadcastPlayback;
diff --git a/test/widgets/ManagedHybrid-test.ts b/test/widgets/ManagedHybrid-test.ts
index b91db09dc1..05093ed0d4 100644
--- a/test/widgets/ManagedHybrid-test.ts
+++ b/test/widgets/ManagedHybrid-test.ts
@@ -14,38 +14,91 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import { isManagedHybridWidgetEnabled } from "../../src/widgets/ManagedHybrid";
-import DMRoomMap from "../../src/utils/DMRoomMap";
+import { Room } from "matrix-js-sdk/src/matrix";
+import { logger } from "matrix-js-sdk/src/logger";
+import fetchMock from "fetch-mock-jest";
+
+import { addManagedHybridWidget, isManagedHybridWidgetEnabled } from "../../src/widgets/ManagedHybrid";
import { stubClient } from "../test-utils";
import SdkConfig from "../../src/SdkConfig";
+import WidgetUtils from "../../src/utils/WidgetUtils";
+import { WidgetLayoutStore } from "../../src/stores/widgets/WidgetLayoutStore";
+
+jest.mock("../../src/utils/room/getJoinedNonFunctionalMembers", () => ({
+ getJoinedNonFunctionalMembers: jest.fn().mockReturnValue([1, 2]),
+}));
describe("isManagedHybridWidgetEnabled", () => {
- let dmRoomMap: DMRoomMap;
+ let room: Room;
beforeEach(() => {
- stubClient();
- dmRoomMap = {
- getUserIdForRoomId: jest.fn().mockReturnValue("@user:server"),
- } as unknown as DMRoomMap;
- DMRoomMap.setShared(dmRoomMap);
+ const client = stubClient();
+ room = new Room("!room:server", client, client.getSafeUserId());
});
it("should return false if widget_build_url is unset", () => {
- expect(isManagedHybridWidgetEnabled("!room:server")).toBeFalsy();
+ expect(isManagedHybridWidgetEnabled(room)).toBeFalsy();
});
- it("should return true for DMs when widget_build_url_ignore_dm is unset", () => {
+ it("should return true for 1-1 rooms when widget_build_url_ignore_dm is unset", () => {
SdkConfig.put({
widget_build_url: "https://url",
});
- expect(isManagedHybridWidgetEnabled("!room:server")).toBeTruthy();
+ expect(isManagedHybridWidgetEnabled(room)).toBeTruthy();
});
- it("should return false for DMs when widget_build_url_ignore_dm is true", () => {
+ it("should return false for 1-1 rooms when widget_build_url_ignore_dm is true", () => {
SdkConfig.put({
widget_build_url: "https://url",
widget_build_url_ignore_dm: true,
});
- expect(isManagedHybridWidgetEnabled("!room:server")).toBeFalsy();
+ expect(isManagedHybridWidgetEnabled(room)).toBeFalsy();
+ });
+});
+
+describe("addManagedHybridWidget", () => {
+ let room: Room;
+
+ beforeEach(() => {
+ const client = stubClient();
+ room = new Room("!room:server", client, client.getSafeUserId());
+ });
+
+ it("should noop if user lacks permission", async () => {
+ const logSpy = jest.spyOn(logger, "error").mockImplementation();
+ jest.spyOn(WidgetUtils, "canUserModifyWidgets").mockReturnValue(false);
+
+ fetchMock.mockClear();
+ await addManagedHybridWidget(room);
+ expect(logSpy).toHaveBeenCalledWith("User not allowed to modify widgets in !room:server");
+ expect(fetchMock).toHaveBeenCalledTimes(0);
+ });
+
+ it("should noop if no widget_build_url", async () => {
+ jest.spyOn(WidgetUtils, "canUserModifyWidgets").mockReturnValue(true);
+
+ fetchMock.mockClear();
+ await addManagedHybridWidget(room);
+ expect(fetchMock).toHaveBeenCalledTimes(0);
+ });
+
+ it("should add the widget successfully", async () => {
+ fetchMock.get("https://widget-build-url/?roomId=!room:server", {
+ widget_id: "WIDGET_ID",
+ widget: { key: "value" },
+ });
+ jest.spyOn(WidgetUtils, "canUserModifyWidgets").mockReturnValue(true);
+ jest.spyOn(WidgetLayoutStore.instance, "canCopyLayoutToRoom").mockReturnValue(true);
+ const setRoomWidgetContentSpy = jest.spyOn(WidgetUtils, "setRoomWidgetContent").mockResolvedValue();
+ SdkConfig.put({
+ widget_build_url: "https://widget-build-url",
+ });
+
+ await addManagedHybridWidget(room);
+ expect(fetchMock).toHaveBeenCalledWith("https://widget-build-url?roomId=!room:server");
+ expect(setRoomWidgetContentSpy).toHaveBeenCalledWith(room.client, room.roomId, "WIDGET_ID", {
+ "key": "value",
+ "io.element.managed_hybrid": true,
+ });
});
});
From 2e0b5bb4624846ae223a04e3b7b2c89358e1795f Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Mon, 15 Jul 2024 11:02:23 +0100
Subject: [PATCH 28/59] Fix edge case of landing on 3pid email link with
registration disabled (#12771)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
src/components/structures/MatrixChat.tsx | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx
index 70c4f9ce5f..f9d6db3f53 100644
--- a/src/components/structures/MatrixChat.tsx
+++ b/src/components/structures/MatrixChat.tsx
@@ -551,7 +551,10 @@ export default class MatrixChat extends React.PureComponent {
.then((loadedSession) => {
if (!loadedSession) {
// fall back to showing the welcome screen... unless we have a 3pid invite pending
- if (ThreepidInviteStore.instance.pickBestInvite()) {
+ if (
+ ThreepidInviteStore.instance.pickBestInvite() &&
+ SettingsStore.getValue(UIFeature.Registration)
+ ) {
dis.dispatch({ action: "start_registration" });
} else {
dis.dispatch({ action: "view_welcome_page" });
@@ -951,6 +954,11 @@ export default class MatrixChat extends React.PureComponent {
}
private async startRegistration(params: { [key: string]: string }): Promise {
+ if (!SettingsStore.getValue(UIFeature.Registration)) {
+ this.showScreen("welcome");
+ return;
+ }
+
const newState: Partial = {
view: Views.REGISTER,
};
From 38e1da56260fcb5ba7a8fc33a4bc1fc0493b074a Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Mon, 15 Jul 2024 11:02:29 +0100
Subject: [PATCH 29/59] Fix inability to change accent colour consistently in
custom theming (#12772)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
res/themes/light-custom/css/_custom.pcss | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/res/themes/light-custom/css/_custom.pcss b/res/themes/light-custom/css/_custom.pcss
index 7fadb2cd0a..7912902645 100644
--- a/res/themes/light-custom/css/_custom.pcss
+++ b/res/themes/light-custom/css/_custom.pcss
@@ -18,7 +18,7 @@ $font-family: var(--font-family, $font-family);
$monospace-font-family: var(--font-family-monospace, $monospace-font-family);
/* Colors from Figma Compound https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=559%3A741 */
-$accent: var(--accent, $accent);
+$accent: var(--accent-color, $accent);
$alert: var(--alert, $alert);
$links: var(--links, $links);
$primary-content: var(--primary-content, $primary-content);
From b4ef5d3cc3ae414e5469b92d1cc9799f8a4c85b6 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Mon, 15 Jul 2024 11:33:41 +0100
Subject: [PATCH 30/59] Fix HTML export missing a bunch of Compound variables
(#12774)
---
package.json | 2 +
.../e2e/chat-export/html-export.spec.ts | 132 ++++++++++++++++++
.../html-export.spec.ts/html-export-linux.png | Bin 0 -> 40662 bytes
src/utils/exportUtils/HtmlExport.tsx | 132 +++++++++---------
src/utils/exportUtils/exportCSS.ts | 90 ++++++------
src/utils/exportUtils/exportCustomCSS.css | 5 +
.../__snapshots__/HTMLExport-test.ts.snap | 122 ++++++++--------
yarn.lock | 5 +
8 files changed, 315 insertions(+), 173 deletions(-)
create mode 100644 playwright/e2e/chat-export/html-export.spec.ts
create mode 100644 playwright/snapshots/chat-export/html-export.spec.ts/html-export-linux.png
diff --git a/package.json b/package.json
index 4250a9d1ca..14635fdeee 100644
--- a/package.json
+++ b/package.json
@@ -91,6 +91,7 @@
"classnames": "^2.2.6",
"commonmark": "^0.31.0",
"counterpart": "^0.18.6",
+ "css-tree": "^2.3.1",
"diff-dom": "^5.0.0",
"diff-match-patch": "^1.0.5",
"emojibase-regex": "15.3.2",
@@ -167,6 +168,7 @@
"@types/commonmark": "^0.27.4",
"@types/content-type": "^1.1.5",
"@types/counterpart": "^0.18.1",
+ "@types/css-tree": "^2.3.8",
"@types/diff-match-patch": "^1.0.32",
"@types/escape-html": "^1.0.1",
"@types/express": "^4.17.21",
diff --git a/playwright/e2e/chat-export/html-export.spec.ts b/playwright/e2e/chat-export/html-export.spec.ts
new file mode 100644
index 0000000000..947aa2c1bc
--- /dev/null
+++ b/playwright/e2e/chat-export/html-export.spec.ts
@@ -0,0 +1,132 @@
+/*
+Copyright 2024 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import os from "node:os";
+import path from "node:path";
+import * as fsp from "node:fs/promises";
+import * as fs from "node:fs";
+import JSZip from "jszip";
+
+import { test, expect } from "../../element-web-test";
+
+// Based on https://github.com/Stuk/jszip/issues/466#issuecomment-2097061912
+async function extractZipFileToPath(file: string, outputPath: string): Promise {
+ if (!fs.existsSync(outputPath)) {
+ fs.mkdirSync(outputPath, { recursive: true });
+ }
+
+ const data = await fsp.readFile(file);
+ const zip = await JSZip.loadAsync(data, { createFolders: true });
+
+ await new Promise((resolve, reject) => {
+ let entryCount = 0;
+ let errorOut = false;
+
+ zip.forEach(() => {
+ entryCount++;
+ }); // there is no other way to count the number of entries within the zip file.
+
+ zip.forEach((relativePath, zipEntry) => {
+ if (errorOut) {
+ return;
+ }
+
+ const outputEntryPath = path.join(outputPath, relativePath);
+ if (zipEntry.dir) {
+ if (!fs.existsSync(outputEntryPath)) {
+ fs.mkdirSync(outputEntryPath, { recursive: true });
+ }
+
+ entryCount--;
+
+ if (entryCount === 0) {
+ resolve();
+ }
+ } else {
+ void zipEntry
+ .async("blob")
+ .then(async (content) => Buffer.from(await content.arrayBuffer()))
+ .then((buffer) => {
+ const stream = fs.createWriteStream(outputEntryPath);
+ stream.write(buffer, (error) => {
+ if (error) {
+ reject(error);
+ errorOut = true;
+ }
+ });
+ stream.on("finish", () => {
+ entryCount--;
+
+ if (entryCount === 0) {
+ resolve();
+ }
+ });
+ stream.end(); // extremely important on Windows. On Mac / Linux, not so much since those platforms allow multiple apps to read from the same file. Windows doesn't allow that.
+ })
+ .catch((e) => {
+ errorOut = true;
+ reject(e);
+ });
+ }
+ });
+ });
+
+ return zip;
+}
+
+test.describe("HTML Export", () => {
+ test.use({
+ displayName: "Alice",
+ room: async ({ app, user }, use) => {
+ const roomId = await app.client.createRoom({ name: "Important Room" });
+ await app.viewRoomByName("Important Room");
+ await use({ roomId });
+ },
+ });
+
+ test("should export html successfully and match screenshot", async ({ page, app, room }) => {
+ // Send a bunch of messages to populate the room
+ for (let i = 1; i < 10; i++) {
+ await app.client.sendMessage(room.roomId, { body: `Testing ${i}`, msgtype: "m.text" });
+ }
+
+ // Wait for all the messages to be displayed
+ await expect(
+ page.locator(".mx_EventTile_last .mx_MTextBody .mx_EventTile_body").getByText("Testing 9"),
+ ).toBeVisible();
+
+ await page.getByRole("button", { name: "Room info" }).click();
+ await page.getByRole("menuitem", { name: "Export Chat" }).click();
+
+ const downloadPromise = page.waitForEvent("download");
+ await page.getByRole("button", { name: "Export", exact: true }).click();
+ const download = await downloadPromise;
+
+ const dirPath = path.join(os.tmpdir(), "html-export-test");
+ const zipPath = `${dirPath}.zip`;
+ await download.saveAs(zipPath);
+
+ const zip = await extractZipFileToPath(zipPath, dirPath);
+ await page.goto(`file://${dirPath}/${Object.keys(zip.files)[0]}/messages.html`);
+ await expect(page).toMatchScreenshot("html-export.png", {
+ mask: [
+ page.getByText("This is the start of export", { exact: false }),
+ page.locator(".mx_DateSeparator_dateHeading"),
+ page.locator(".mx_MessageTimestamp"),
+ ],
+ });
+ });
+});
diff --git a/playwright/snapshots/chat-export/html-export.spec.ts/html-export-linux.png b/playwright/snapshots/chat-export/html-export.spec.ts/html-export-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..99af1a757ba327b750e7859f9e8436102a2f8d7f
GIT binary patch
literal 40662
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