Merge branch 'develop' into widget_state_no_update_invitation_room

pull/28788/head^2
Mikhail Aheichyk 2023-01-12 10:06:51 +03:00
commit 73a8c5bcc4
157 changed files with 4983 additions and 2207 deletions

1
.gitignore vendored
View File

@ -24,6 +24,7 @@ package-lock.json
/cypress/downloads
/cypress/screenshots
/cypress/synapselogs
/cypress/dendritelogs
# These could have files in them but don't currently
# Cypress will still auto-create them though...
/cypress/performance

View File

@ -33,6 +33,7 @@ export default defineConfig({
env: {
// Docker tag to use for `ghcr.io/matrix-org/sliding-sync-proxy` image.
SLIDING_SYNC_PROXY_TAG: "v0.6.0",
HOMESERVER: "synapse",
},
retries: {
runMode: 4,

View File

@ -16,25 +16,25 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import { SettingLevel } from "../../../src/settings/SettingLevel";
describe("Composer", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
});
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
describe("CIDER", () => {
beforeEach(() => {
cy.initTestUser(synapse, "Janet").then(() => {
cy.initTestUser(homeserver, "Janet").then(() => {
cy.createRoom({ name: "Composing Room" });
});
cy.viewRoomByName("Composing Room");
@ -101,7 +101,7 @@ describe("Composer", () => {
describe("WYSIWYG", () => {
beforeEach(() => {
cy.enableLabsFeature("feature_wysiwyg_composer");
cy.initTestUser(synapse, "Janet").then(() => {
cy.initTestUser(homeserver, "Janet").then(() => {
cy.createRoom({ name: "Composing Room" });
});
cy.viewRoomByName("Composing Room");

View File

@ -16,7 +16,7 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import Chainable = Cypress.Chainable;
function openCreateRoomDialog(): Chainable<JQuery<HTMLElement>> {
@ -26,18 +26,18 @@ function openCreateRoomDialog(): Chainable<JQuery<HTMLElement>> {
}
describe("Create Room", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, "Jim");
cy.initTestUser(homeserver, "Jim");
});
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("should allow us to create a public room with name, topic & address set", () => {

View File

@ -18,12 +18,12 @@ import type { ISendEventResponse, MatrixClient, Room } from "matrix-js-sdk/src/m
import type { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
import type { ISasEvent } from "matrix-js-sdk/src/crypto/verification/SAS";
import type { CypressBot } from "../../support/bot";
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import Chainable = Cypress.Chainable;
type EmojiMapping = [emoji: string, name: string];
interface CryptoTestContext extends Mocha.Context {
synapse: SynapseInstance;
homeserver: HomeserverInstance;
bob: CypressBot;
}
@ -155,16 +155,16 @@ const verify = function (this: CryptoTestContext) {
describe("Cryptography", function () {
beforeEach(function () {
cy.startSynapse("default")
.as("synapse")
.then((synapse: SynapseInstance) => {
cy.initTestUser(synapse, "Alice", undefined, "alice_");
cy.getBot(synapse, { displayName: "Bob", autoAcceptInvites: false, userIdPrefix: "bob_" }).as("bob");
cy.startHomeserver("default")
.as("homeserver")
.then((homeserver: HomeserverInstance) => {
cy.initTestUser(homeserver, "Alice", undefined, "alice_");
cy.getBot(homeserver, { displayName: "Bob", autoAcceptInvites: false, userIdPrefix: "bob_" }).as("bob");
});
});
afterEach(function (this: CryptoTestContext) {
cy.stopSynapse(this.synapse);
cy.stopHomeserver(this.homeserver);
});
it("setting up secure key backup should work", () => {
@ -215,7 +215,7 @@ describe("Cryptography", function () {
cy.bootstrapCrossSigning();
// bob has a second, not cross-signed, device
cy.loginBot(this.synapse, this.bob.getUserId(), this.bob.__cypress_password, {}).as("bobSecondDevice");
cy.loginBot(this.homeserver, this.bob.getUserId(), this.bob.__cypress_password, {}).as("bobSecondDevice");
autoJoin(this.bob);

View File

@ -17,7 +17,7 @@ limitations under the License.
import type { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
import type { ISasEvent } from "matrix-js-sdk/src/crypto/verification/SAS";
import type { MatrixClient } from "matrix-js-sdk/src/matrix";
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import { UserCredentials } from "../../support/login";
import Chainable = Cypress.Chainable;
@ -56,20 +56,20 @@ const handleVerificationRequest = (request: VerificationRequest): Chainable<Emoj
};
describe("Decryption Failure Bar", () => {
let synapse: SynapseInstance | undefined;
let homeserver: HomeserverInstance | undefined;
let testUser: UserCredentials | undefined;
let bot: MatrixClient | undefined;
let roomId: string;
beforeEach(function () {
cy.startSynapse("default").then((syn: SynapseInstance) => {
synapse = syn;
cy.initTestUser(synapse, TEST_USER)
cy.startHomeserver("default").then((hs: HomeserverInstance) => {
homeserver = hs;
cy.initTestUser(homeserver, TEST_USER)
.then((creds: UserCredentials) => {
testUser = creds;
})
.then(() => {
cy.getBot(synapse, { displayName: BOT_USER }).then((cli) => {
cy.getBot(homeserver, { displayName: BOT_USER }).then((cli) => {
bot = cli;
});
})
@ -97,7 +97,7 @@ describe("Decryption Failure Bar", () => {
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it(
@ -105,7 +105,7 @@ describe("Decryption Failure Bar", () => {
"and there are other verified devices or backups",
() => {
let otherDevice: MatrixClient | undefined;
cy.loginBot(synapse, testUser.username, testUser.password, {})
cy.loginBot(homeserver, testUser.username, testUser.password, {})
.then(async (cli) => {
otherDevice = cli;
await otherDevice.bootstrapCrossSigning({
@ -169,7 +169,7 @@ describe("Decryption Failure Bar", () => {
"should prompt the user to reset keys, if this device isn't verified " +
"and there are no other verified devices or backups",
() => {
cy.loginBot(synapse, testUser.username, testUser.password, {}).then(async (cli) => {
cy.loginBot(homeserver, testUser.username, testUser.password, {}).then(async (cli) => {
await cli.bootstrapCrossSigning({
authUploadDeviceSigningKeys: async (makeRequest) => {
await makeRequest({});

View File

@ -16,24 +16,26 @@ limitations under the License.
/// <reference types="cypress" />
import { MessageEvent } from "matrix-events-sdk";
import type { MsgType } from "matrix-js-sdk/src/@types/event";
import type { ISendEventResponse } from "matrix-js-sdk/src/@types/requests";
import type { EventType } from "matrix-js-sdk/src/@types/event";
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import Chainable = Cypress.Chainable;
const sendEvent = (roomId: string): Chainable<ISendEventResponse> => {
return cy.sendEvent(roomId, null, "m.room.message" as EventType, MessageEvent.from("Message").serialize().content);
return cy.sendEvent(roomId, null, "m.room.message" as EventType, {
msgtype: "m.text" as MsgType,
body: "Message",
});
};
describe("Editing", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.initTestUser(synapse, "Edith").then(() => {
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(homeserver, "Edith").then(() => {
cy.injectAxe();
return cy.createRoom({ name: "Test room" }).as("roomId");
});
@ -41,7 +43,7 @@ describe("Editing", () => {
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("should close the composer when clicking save after making a change and undoing it", () => {

View File

@ -16,7 +16,7 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import { UserCredentials } from "../../support/login";
const ROOM_NAME = "Integration Manager Test";
@ -73,17 +73,17 @@ function sendActionFromIntegrationManager(integrationManagerUrl: string) {
describe("Integration Manager: Get OpenID Token", () => {
let testUser: UserCredentials;
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
let integrationManagerUrl: string;
beforeEach(() => {
cy.serveHtmlFile(INTEGRATION_MANAGER_HTML).then((url) => {
integrationManagerUrl = url;
});
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, USER_DISPLAY_NAME, () => {
cy.initTestUser(homeserver, USER_DISPLAY_NAME, () => {
cy.window().then((win) => {
win.localStorage.setItem("mx_scalar_token", INTEGRATION_MANAGER_TOKEN);
win.localStorage.setItem(`mx_scalar_token_at_${integrationManagerUrl}`, INTEGRATION_MANAGER_TOKEN);
@ -122,7 +122,7 @@ describe("Integration Manager: Get OpenID Token", () => {
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
cy.stopWebServers();
});

View File

@ -16,7 +16,7 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import { MatrixClient } from "../../global";
import { UserCredentials } from "../../support/login";
@ -94,17 +94,17 @@ function expectKickedMessage(shouldExist: boolean) {
describe("Integration Manager: Kick", () => {
let testUser: UserCredentials;
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
let integrationManagerUrl: string;
beforeEach(() => {
cy.serveHtmlFile(INTEGRATION_MANAGER_HTML).then((url) => {
integrationManagerUrl = url;
});
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, USER_DISPLAY_NAME, () => {
cy.initTestUser(homeserver, USER_DISPLAY_NAME, () => {
cy.window().then((win) => {
win.localStorage.setItem("mx_scalar_token", INTEGRATION_MANAGER_TOKEN);
win.localStorage.setItem(`mx_scalar_token_at_${integrationManagerUrl}`, INTEGRATION_MANAGER_TOKEN);
@ -140,12 +140,12 @@ describe("Integration Manager: Kick", () => {
name: ROOM_NAME,
}).as("roomId");
cy.getBot(synapse, { displayName: BOT_DISPLAY_NAME, autoAcceptInvites: true }).as("bob");
cy.getBot(homeserver, { displayName: BOT_DISPLAY_NAME, autoAcceptInvites: true }).as("bob");
});
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
cy.stopWebServers();
});

View File

@ -16,7 +16,7 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import { UserCredentials } from "../../support/login";
const ROOM_NAME = "Integration Manager Test";
@ -87,17 +87,17 @@ function sendActionFromIntegrationManager(
describe("Integration Manager: Read Events", () => {
let testUser: UserCredentials;
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
let integrationManagerUrl: string;
beforeEach(() => {
cy.serveHtmlFile(INTEGRATION_MANAGER_HTML).then((url) => {
integrationManagerUrl = url;
});
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, USER_DISPLAY_NAME, () => {
cy.initTestUser(homeserver, USER_DISPLAY_NAME, () => {
cy.window().then((win) => {
win.localStorage.setItem("mx_scalar_token", INTEGRATION_MANAGER_TOKEN);
win.localStorage.setItem(`mx_scalar_token_at_${integrationManagerUrl}`, INTEGRATION_MANAGER_TOKEN);
@ -136,7 +136,7 @@ describe("Integration Manager: Read Events", () => {
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
cy.stopWebServers();
});

View File

@ -16,7 +16,7 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import { UserCredentials } from "../../support/login";
const ROOM_NAME = "Integration Manager Test";
@ -93,17 +93,17 @@ function sendActionFromIntegrationManager(
describe("Integration Manager: Send Event", () => {
let testUser: UserCredentials;
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
let integrationManagerUrl: string;
beforeEach(() => {
cy.serveHtmlFile(INTEGRATION_MANAGER_HTML).then((url) => {
integrationManagerUrl = url;
});
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, USER_DISPLAY_NAME, () => {
cy.initTestUser(homeserver, USER_DISPLAY_NAME, () => {
cy.window().then((win) => {
win.localStorage.setItem("mx_scalar_token", INTEGRATION_MANAGER_TOKEN);
win.localStorage.setItem(`mx_scalar_token_at_${integrationManagerUrl}`, INTEGRATION_MANAGER_TOKEN);
@ -142,7 +142,7 @@ describe("Integration Manager: Send Event", () => {
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
cy.stopWebServers();
});

View File

@ -16,7 +16,7 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import { MatrixClient } from "../../global";
import Chainable = Cypress.Chainable;
@ -26,7 +26,7 @@ interface Charly {
}
describe("Lazy Loading", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
let bob: MatrixClient;
const charlies: Charly[] = [];
@ -35,12 +35,12 @@ describe("Lazy Loading", () => {
win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests
});
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, "Alice");
cy.initTestUser(homeserver, "Alice");
cy.getBot(synapse, {
cy.getBot(homeserver, {
displayName: "Bob",
startClient: false,
autoAcceptInvites: false,
@ -50,7 +50,7 @@ describe("Lazy Loading", () => {
for (let i = 1; i <= 10; i++) {
const displayName = `Charly #${i}`;
cy.getBot(synapse, {
cy.getBot(homeserver, {
displayName,
startClient: false,
autoAcceptInvites: false,
@ -62,7 +62,7 @@ describe("Lazy Loading", () => {
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
const name = "Lazy Loading Test";

View File

@ -16,11 +16,11 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import Chainable = Cypress.Chainable;
describe("Location sharing", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
const selectLocationShareTypeOption = (shareType: string): Chainable<JQuery> => {
return cy.get(`[data-test-id="share-location-option-${shareType}"]`);
@ -34,15 +34,15 @@ describe("Location sharing", () => {
cy.window().then((win) => {
win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests
});
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, "Tom");
cy.initTestUser(homeserver, "Tom");
});
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("sends and displays pin drop location message successfully", () => {

View File

@ -18,21 +18,21 @@ limitations under the License.
import { SinonStub } from "cypress/types/sinon";
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
describe("Consent", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
beforeEach(() => {
cy.startSynapse("consent").then((data) => {
synapse = data;
cy.startHomeserver("consent").then((data) => {
homeserver = data;
cy.initTestUser(synapse, "Bob");
cy.initTestUser(homeserver, "Bob");
});
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("should prompt the user to consent to terms when server deems it necessary", () => {
@ -53,8 +53,8 @@ describe("Consent", () => {
cy.get<SinonStub>("@windowOpen").then((stub) => {
const url = stub.getCall(0).args[0];
// Go to Synapse's consent page and accept it
cy.origin(synapse.baseUrl, { args: { url } }, ({ url }) => {
// Go to Homeserver's consent page and accept it
cy.origin(homeserver.baseUrl, { args: { url } }, ({ url }) => {
cy.visit(url);
cy.get('[type="submit"]').click();

View File

@ -16,17 +16,17 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
describe("Login", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
beforeEach(() => {
cy.stubDefaultServer();
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
describe("m.login.password", () => {
@ -34,9 +34,9 @@ describe("Login", () => {
const password = "p4s5W0rD";
beforeEach(() => {
cy.startSynapse("consent").then((data) => {
synapse = data;
cy.registerUser(synapse, username, password);
cy.startHomeserver("consent").then((data) => {
homeserver = data;
cy.registerUser(homeserver, username, password);
cy.visit("/#/login");
});
});
@ -49,7 +49,7 @@ describe("Login", () => {
cy.checkA11y();
cy.get(".mx_ServerPicker_change").click();
cy.get(".mx_ServerPickerDialog_otherHomeserver").type(synapse.baseUrl);
cy.get(".mx_ServerPickerDialog_otherHomeserver").type(homeserver.baseUrl);
cy.get(".mx_ServerPickerDialog_continue").click();
// wait for the dialog to go away
cy.get(".mx_ServerPickerDialog").should("not.exist");
@ -64,9 +64,9 @@ describe("Login", () => {
describe("logout", () => {
beforeEach(() => {
cy.startSynapse("consent").then((data) => {
synapse = data;
cy.initTestUser(synapse, "Erin");
cy.startHomeserver("consent").then((data) => {
homeserver = data;
cy.initTestUser(homeserver, "Erin");
});
});

View File

@ -18,14 +18,14 @@ limitations under the License.
import { PollResponseEvent } from "matrix-events-sdk";
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import { MatrixClient } from "../../global";
import Chainable = Cypress.Chainable;
const hideTimestampCSS = ".mx_MessageTimestamp { visibility: hidden !important; }";
describe("Polls", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
type CreatePollOptions = {
title: string;
@ -77,24 +77,24 @@ describe("Polls", () => {
};
beforeEach(() => {
cy.enableLabsFeature("feature_threadstable");
cy.enableLabsFeature("feature_threadenabled");
cy.window().then((win) => {
win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests
});
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, "Tom");
cy.initTestUser(homeserver, "Tom");
});
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("should be creatable and votable", () => {
let bot: MatrixClient;
cy.getBot(synapse, { displayName: "BotBob" }).then((_bot) => {
cy.getBot(homeserver, { displayName: "BotBob" }).then((_bot) => {
bot = _bot;
});
@ -163,7 +163,7 @@ describe("Polls", () => {
it("should be editable from context menu if no votes have been cast", () => {
let bot: MatrixClient;
cy.getBot(synapse, { displayName: "BotBob" }).then((_bot) => {
cy.getBot(homeserver, { displayName: "BotBob" }).then((_bot) => {
bot = _bot;
});
@ -206,7 +206,7 @@ describe("Polls", () => {
it("should not be editable from context menu if votes have been cast", () => {
let bot: MatrixClient;
cy.getBot(synapse, { displayName: "BotBob" }).then((_bot) => {
cy.getBot(homeserver, { displayName: "BotBob" }).then((_bot) => {
bot = _bot;
});
@ -256,10 +256,10 @@ describe("Polls", () => {
it("should be displayed correctly in thread panel", () => {
let botBob: MatrixClient;
let botCharlie: MatrixClient;
cy.getBot(synapse, { displayName: "BotBob" }).then((_bot) => {
cy.getBot(homeserver, { displayName: "BotBob" }).then((_bot) => {
botBob = _bot;
});
cy.getBot(synapse, { displayName: "BotCharlie" }).then((_bot) => {
cy.getBot(homeserver, { displayName: "BotCharlie" }).then((_bot) => {
botCharlie = _bot;
});

View File

@ -16,21 +16,21 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
describe("Registration", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
beforeEach(() => {
cy.stubDefaultServer();
cy.visit("/#/register");
cy.startSynapse("consent").then((data) => {
synapse = data;
cy.startHomeserver("consent").then((data) => {
homeserver = data;
});
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("registers an account and lands on the home screen", () => {
@ -42,13 +42,13 @@ describe("Registration", () => {
cy.get(".mx_Dialog").percySnapshotElement("Server Picker", { widths: [516] });
cy.checkA11y();
cy.get(".mx_ServerPickerDialog_otherHomeserver").type(synapse.baseUrl);
cy.get(".mx_ServerPickerDialog_otherHomeserver").type(homeserver.baseUrl);
cy.get(".mx_ServerPickerDialog_continue").click();
// wait for the dialog to go away
cy.get(".mx_ServerPickerDialog").should("not.exist");
cy.get("#mx_RegistrationForm_username").should("be.visible");
// Hide the server text as it contains the randomly allocated Synapse port
// Hide the server text as it contains the randomly allocated Homeserver port
const percyCSS = ".mx_ServerPicker_server { visibility: hidden !important; }";
cy.percySnapshot("Registration", { percyCSS });
cy.checkA11y();
@ -88,7 +88,7 @@ describe("Registration", () => {
it("should require username to fulfil requirements and be available", () => {
cy.get(".mx_ServerPicker_change", { timeout: 15000 }).click();
cy.get(".mx_ServerPickerDialog_continue").should("be.visible");
cy.get(".mx_ServerPickerDialog_otherHomeserver").type(synapse.baseUrl);
cy.get(".mx_ServerPickerDialog_otherHomeserver").type(homeserver.baseUrl);
cy.get(".mx_ServerPickerDialog_continue").click();
// wait for the dialog to go away
cy.get(".mx_ServerPickerDialog").should("not.exist");

View File

@ -16,21 +16,21 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
describe("Pills", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, "Sally");
cy.initTestUser(homeserver, "Sally");
});
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("should navigate clicks internally to the app", () => {

View File

@ -16,7 +16,7 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import Chainable = Cypress.Chainable;
const ROOM_NAME = "Test room";
@ -43,12 +43,12 @@ const checkRoomSummaryCard = (name: string): Chainable<JQuery<HTMLElement>> => {
};
describe("RightPanel", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.initTestUser(synapse, NAME).then(() =>
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(homeserver, NAME).then(() =>
cy.window({ log: false }).then(() => {
cy.createRoom({ name: ROOM_NAME });
cy.createSpace({ name: SPACE_NAME });
@ -58,7 +58,7 @@ describe("RightPanel", () => {
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
describe("in rooms", () => {

View File

@ -16,23 +16,23 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import { MatrixClient } from "../../global";
describe("Room Directory", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, "Ray");
cy.getBot(synapse, { displayName: "Paul" }).as("bot");
cy.initTestUser(homeserver, "Ray");
cy.getBot(homeserver, { displayName: "Paul" }).as("bot");
});
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("should allow admin to add alias & publish room to directory", () => {

View File

@ -18,34 +18,34 @@ limitations under the License.
import { EventType } from "matrix-js-sdk/src/@types/event";
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import { MatrixClient } from "../../global";
describe("Room Directory", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, "Alice");
cy.initTestUser(homeserver, "Alice");
});
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("should switch between existing dm rooms without a loader", () => {
let bobClient: MatrixClient;
let charlieClient: MatrixClient;
cy.getBot(synapse, {
cy.getBot(homeserver, {
displayName: "Bob",
}).then((bob) => {
bobClient = bob;
});
cy.getBot(synapse, {
cy.getBot(homeserver, {
displayName: "Charlie",
}).then((charlie) => {
charlieClient = charlie;

View File

@ -16,34 +16,34 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import type { UserCredentials } from "../../support/login";
describe("Device manager", () => {
let synapse: SynapseInstance | undefined;
let homeserver: HomeserverInstance | undefined;
let user: UserCredentials | undefined;
beforeEach(() => {
cy.enableLabsFeature("feature_new_device_manager");
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, "Alice")
cy.initTestUser(homeserver, "Alice")
.then((credentials) => {
user = credentials;
})
.then(() => {
// create some extra sessions to manage
return cy.loginUser(synapse, user.username, user.password);
return cy.loginUser(homeserver, user.username, user.password);
})
.then(() => {
return cy.loginUser(synapse, user.username, user.password);
return cy.loginUser(homeserver, user.username, user.password);
});
});
});
afterEach(() => {
cy.stopSynapse(synapse!);
cy.stopHomeserver(homeserver!);
});
it("should display sessions", () => {

View File

@ -16,10 +16,10 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
function seedLabs(synapse: SynapseInstance, labsVal: boolean | null): void {
cy.initTestUser(synapse, "Sally", () => {
function seedLabs(homeserver: HomeserverInstance, labsVal: boolean | null): void {
cy.initTestUser(homeserver, "Sally", () => {
// seed labs flag
cy.window({ log: false }).then((win) => {
if (typeof labsVal === "boolean") {
@ -61,30 +61,30 @@ describe("Hidden Read Receipts Setting Migration", () => {
// For a security-sensitive feature like hidden read receipts, it's absolutely vital
// that we migrate the setting appropriately.
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
});
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("should not migrate the lack of a labs flag", () => {
seedLabs(synapse, null);
seedLabs(homeserver, null);
testForVal(null);
});
it("should migrate labsHiddenRR=false as sendRR=true", () => {
seedLabs(synapse, false);
seedLabs(homeserver, false);
testForVal(true);
});
it("should migrate labsHiddenRR=true as sendRR=false", () => {
seedLabs(synapse, true);
seedLabs(homeserver, true);
testForVal(false);
});
});

View File

@ -20,43 +20,45 @@ import _ from "lodash";
import { MatrixClient } from "matrix-js-sdk/src/matrix";
import { Interception } from "cypress/types/net-stubbing";
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import { SettingLevel } from "../../../src/settings/SettingLevel";
import { Layout } from "../../../src/settings/enums/Layout";
import { ProxyInstance } from "../../plugins/sliding-sync";
describe("Sliding Sync", () => {
beforeEach(() => {
cy.startSynapse("default")
.as("synapse")
.then((synapse) => {
cy.startProxy(synapse).as("proxy");
cy.startHomeserver("default")
.as("homeserver")
.then((homeserver) => {
cy.startProxy(homeserver).as("proxy");
});
cy.all([cy.get<SynapseInstance>("@synapse"), cy.get<ProxyInstance>("@proxy")]).then(([synapse, proxy]) => {
cy.enableLabsFeature("feature_sliding_sync");
cy.all([cy.get<HomeserverInstance>("@homeserver"), cy.get<ProxyInstance>("@proxy")]).then(
([homeserver, proxy]) => {
cy.enableLabsFeature("feature_sliding_sync");
cy.intercept("/config.json?cachebuster=*", (req) => {
return req.continue((res) => {
res.send(200, {
...res.body,
setting_defaults: {
feature_sliding_sync_proxy_url: `http://localhost:${proxy.port}`,
},
cy.intercept("/config.json?cachebuster=*", (req) => {
return req.continue((res) => {
res.send(200, {
...res.body,
setting_defaults: {
feature_sliding_sync_proxy_url: `http://localhost:${proxy.port}`,
},
});
});
});
});
cy.initTestUser(synapse, "Sloth").then(() => {
return cy.window({ log: false }).then(() => {
cy.createRoom({ name: "Test Room" }).as("roomId");
cy.initTestUser(homeserver, "Sloth").then(() => {
return cy.window({ log: false }).then(() => {
cy.createRoom({ name: "Test Room" }).as("roomId");
});
});
});
});
},
);
});
afterEach(() => {
cy.get<SynapseInstance>("@synapse").then(cy.stopSynapse);
cy.get<HomeserverInstance>("@homeserver").then(cy.stopHomeserver);
cy.get<ProxyInstance>("@proxy").then(cy.stopProxy);
});
@ -84,9 +86,9 @@ describe("Sliding Sync", () => {
};
const createAndJoinBob = () => {
// create a Bob user
cy.get<SynapseInstance>("@synapse").then((synapse) => {
cy.get<HomeserverInstance>("@homeserver").then((homeserver) => {
return cy
.getBot(synapse, {
.getBot(homeserver, {
displayName: "Bob",
})
.as("bob");

View File

@ -18,7 +18,7 @@ limitations under the License.
import type { MatrixClient } from "matrix-js-sdk/src/client";
import type { ICreateRoomOpts } from "matrix-js-sdk/src/@types/requests";
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import Chainable = Cypress.Chainable;
import { UserCredentials } from "../../support/login";
@ -59,14 +59,14 @@ function spaceChildInitialState(roomId: string): ICreateRoomOpts["initial_state"
}
describe("Spaces", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
let user: UserCredentials;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, "Sue").then((_user) => {
cy.initTestUser(homeserver, "Sue").then((_user) => {
user = _user;
cy.mockClipboard();
});
@ -74,7 +74,7 @@ describe("Spaces", () => {
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it.only("should allow user to create public space", () => {
@ -173,7 +173,7 @@ describe("Spaces", () => {
it("should allow user to invite another to a space", () => {
let bot: MatrixClient;
cy.getBot(synapse, { displayName: "BotBob" }).then((_bot) => {
cy.getBot(homeserver, { displayName: "BotBob" }).then((_bot) => {
bot = _bot;
});
@ -208,7 +208,7 @@ describe("Spaces", () => {
});
cy.getSpacePanelButton("My Space").should("exist");
cy.getBot(synapse, { displayName: "BotBob" }).then({ timeout: 10000 }, async (bot) => {
cy.getBot(homeserver, { displayName: "BotBob" }).then({ timeout: 10000 }, async (bot) => {
const { room_id: roomId } = await bot.createRoom(spaceCreateOptions("Space Space"));
await bot.invite(roomId, user.userId);
});

View File

@ -17,7 +17,7 @@ limitations under the License.
/// <reference types="cypress" />
import { MatrixClient } from "../../global";
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import Chainable = Cypress.Chainable;
import Loggable = Cypress.Loggable;
import Timeoutable = Cypress.Timeoutable;
@ -136,7 +136,7 @@ Cypress.Commands.add("startDM", (name: string) => {
});
describe("Spotlight", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
const bot1Name = "BotBob";
let bot1: MatrixClient;
@ -154,16 +154,16 @@ describe("Spotlight", () => {
let room3Id: string;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.initTestUser(synapse, "Jim")
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(homeserver, "Jim")
.then(() =>
cy.getBot(synapse, { displayName: bot1Name }).then((_bot1) => {
cy.getBot(homeserver, { displayName: bot1Name }).then((_bot1) => {
bot1 = _bot1;
}),
)
.then(() =>
cy.getBot(synapse, { displayName: bot2Name }).then((_bot2) => {
cy.getBot(homeserver, { displayName: bot2Name }).then((_bot2) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
bot2 = _bot2;
}),
@ -205,7 +205,7 @@ describe("Spotlight", () => {
afterEach(() => {
cy.visit("/#/home");
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("should be able to add and remove filters via keyboard", () => {

View File

@ -16,7 +16,7 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import { MatrixClient } from "../../global";
function markWindowBeforeReload(): void {
@ -25,23 +25,23 @@ function markWindowBeforeReload(): void {
}
describe("Threads", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
beforeEach(() => {
// Default threads to ON for this spec
cy.enableLabsFeature("feature_threadstable");
cy.enableLabsFeature("feature_threadenabled");
cy.window().then((win) => {
win.localStorage.setItem("mx_lhs_size", "0"); // Collapse left panel for these tests
});
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, "Tom");
cy.initTestUser(homeserver, "Tom");
});
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("should reload when enabling threads beta", () => {
@ -75,7 +75,7 @@ describe("Threads", () => {
it("should be usable for a conversation", () => {
let bot: MatrixClient;
cy.getBot(synapse, {
cy.getBot(homeserver, {
displayName: "BotBob",
autoAcceptInvites: false,
}).then((_bot) => {

View File

@ -16,11 +16,9 @@ limitations under the License.
/// <reference types="cypress" />
import { MessageEvent } from "matrix-events-sdk";
import type { ISendEventResponse } from "matrix-js-sdk/src/@types/requests";
import type { EventType } from "matrix-js-sdk/src/@types/event";
import { SynapseInstance } from "../../plugins/synapsedocker";
import type { EventType, MsgType } from "matrix-js-sdk/src/@types/event";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import { SettingLevel } from "../../../src/settings/SettingLevel";
import { Layout } from "../../../src/settings/enums/Layout";
import Chainable = Cypress.Chainable;
@ -55,16 +53,21 @@ const expectAvatar = (e: JQuery<HTMLElement>, avatarUrl: string): void => {
};
const sendEvent = (roomId: string, html = false): Chainable<ISendEventResponse> => {
return cy.sendEvent(
roomId,
null,
"m.room.message" as EventType,
MessageEvent.from("Message", html ? "<b>Message</b>" : undefined).serialize().content,
);
const content = {
msgtype: "m.text" as MsgType,
body: "Message",
format: undefined,
formatted_body: undefined,
};
if (html) {
content.format = "org.matrix.custom.html";
content.formatted_body = "<b>Message</b>";
}
return cy.sendEvent(roomId, null, "m.room.message" as EventType, content);
};
describe("Timeline", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
let roomId: string;
@ -72,9 +75,9 @@ describe("Timeline", () => {
let newAvatarUrl: string;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.initTestUser(synapse, OLD_NAME).then(() =>
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(homeserver, OLD_NAME).then(() =>
cy.createRoom({ name: ROOM_NAME }).then((_room1Id) => {
roomId = _room1Id;
}),
@ -83,7 +86,7 @@ describe("Timeline", () => {
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
describe("useOnlyCurrentProfiles", () => {
@ -314,12 +317,10 @@ describe("Timeline", () => {
},
}).as("preview_url");
cy.sendEvent(
roomId,
null,
"m.room.message" as EventType,
MessageEvent.from("https://call.element.io/").serialize().content,
);
cy.sendEvent(roomId, null, "m.room.message" as EventType, {
msgtype: "m.text" as MsgType,
body: "https://call.element.io/",
});
cy.visit("/#/room/" + roomId);
cy.get(".mx_LinkPreviewWidget").should("exist").should("contain.text", "Element Call");

View File

@ -16,7 +16,7 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import Chainable = Cypress.Chainable;
function assertNoToasts(): void {
@ -40,10 +40,10 @@ function rejectToast(expectedTitle: string): void {
}
describe("Analytics Toast", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("should not show an analytics toast if config has nothing about posthog", () => {
@ -55,9 +55,9 @@ describe("Analytics Toast", () => {
});
});
cy.startSynapse("default").then((data) => {
synapse = data;
cy.initTestUser(synapse, "Tod");
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(homeserver, "Tod");
});
rejectToast("Notifications");
@ -78,9 +78,9 @@ describe("Analytics Toast", () => {
});
});
cy.startSynapse("default").then((data) => {
synapse = data;
cy.initTestUser(synapse, "Tod");
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(homeserver, "Tod");
rejectToast("Notifications");
});
});

View File

@ -16,19 +16,19 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
describe("Update", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
});
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("should navigate to ?updated=$VERSION if realises it is immediately out of date on load", () => {
@ -42,7 +42,7 @@ describe("Update", () => {
},
}).as("version");
cy.initTestUser(synapse, "Ursa");
cy.initTestUser(homeserver, "Ursa");
cy.wait("@version");
cy.url()

View File

@ -16,25 +16,25 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import type { UserCredentials } from "../../support/login";
describe("User Menu", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
let user: UserCredentials;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, "Jeff").then((credentials) => {
cy.initTestUser(homeserver, "Jeff").then((credentials) => {
user = credentials;
});
});
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("should contain our name & userId", () => {

View File

@ -17,18 +17,18 @@ limitations under the License.
/// <reference types="cypress" />
import { MatrixClient } from "../../global";
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
describe("User Onboarding (new user)", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
const bot1Name = "BotBob";
let bot1: MatrixClient;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.initTestUser(synapse, "Jane Doe");
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(homeserver, "Jane Doe");
cy.window({ log: false }).then((win) => {
win.localStorage.setItem("mx_registration_time", "1656633601");
});
@ -36,7 +36,7 @@ describe("User Onboarding (new user)", () => {
// wait for the app to load
return cy.get(".mx_MatrixChat", { timeout: 15000 });
});
cy.getBot(synapse, { displayName: bot1Name }).then((_bot1) => {
cy.getBot(homeserver, { displayName: bot1Name }).then((_bot1) => {
bot1 = _bot1;
});
cy.get(".mx_UserOnboardingPage").should("exist");
@ -51,7 +51,7 @@ describe("User Onboarding (new user)", () => {
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("page is shown and preference exists", () => {

View File

@ -16,15 +16,15 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
describe("User Onboarding (old user)", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.initTestUser(synapse, "Jane Doe");
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(homeserver, "Jane Doe");
cy.window({ log: false }).then((win) => {
win.localStorage.setItem("mx_registration_time", "2");
});
@ -37,7 +37,7 @@ describe("User Onboarding (old user)", () => {
afterEach(() => {
cy.visit("/#/home");
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("page and preference are hidden", () => {

View File

@ -16,23 +16,23 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import { MatrixClient } from "../../global";
describe("UserView", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, "Violet");
cy.getBot(synapse, { displayName: "Usman" }).as("bot");
cy.initTestUser(homeserver, "Violet");
cy.getBot(homeserver, { displayName: "Usman" }).as("bot");
});
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
});
it("should render the user view as expected", () => {

View File

@ -17,7 +17,7 @@ limitations under the License.
import { IWidget } from "matrix-widget-api";
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
const ROOM_NAME = "Test Room";
const WIDGET_ID = "fake-widget";
@ -34,14 +34,14 @@ const WIDGET_HTML = `
describe("Widget Layout", () => {
let widgetUrl: string;
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
let roomId: string;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, "Sally");
cy.initTestUser(homeserver, "Sally");
});
cy.serveHtmlFile(WIDGET_HTML).then((url) => {
widgetUrl = url;
@ -91,7 +91,7 @@ describe("Widget Layout", () => {
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
cy.stopWebServers();
});

View File

@ -16,7 +16,7 @@ limitations under the License.
/// <reference types="cypress" />
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
const STICKER_PICKER_WIDGET_ID = "fake-sticker-picker";
const STICKER_PICKER_WIDGET_NAME = "Fake Stickers";
@ -102,13 +102,13 @@ describe("Stickers", () => {
// See sendStickerFromPicker() for more detail on iframe comms.
let stickerPickerUrl: string;
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, "Sally");
cy.initTestUser(homeserver, "Sally");
});
cy.serveHtmlFile(WIDGET_HTML).then((url) => {
stickerPickerUrl = url;
@ -116,7 +116,7 @@ describe("Stickers", () => {
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
cy.stopWebServers();
});

View File

@ -20,7 +20,7 @@ limitations under the License.
import { IWidget } from "matrix-widget-api/src/interfaces/IWidget";
import type { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
import { SynapseInstance } from "../../plugins/synapsedocker";
import { HomeserverInstance } from "../../plugins/utils/homeserver";
import { UserCredentials } from "../../support/login";
const DEMO_WIDGET_ID = "demo-widget-id";
@ -90,7 +90,7 @@ function waitForRoomWidget(win: Cypress.AUTWindow, widgetId: string, roomId: str
}
describe("Widget PIP", () => {
let synapse: SynapseInstance;
let homeserver: HomeserverInstance;
let user: UserCredentials;
let bot: MatrixClient;
let demoWidgetUrl: string;
@ -173,13 +173,13 @@ describe("Widget PIP", () => {
}
beforeEach(() => {
cy.startSynapse("default").then((data) => {
synapse = data;
cy.startHomeserver("default").then((data) => {
homeserver = data;
cy.initTestUser(synapse, "Mike").then((_user) => {
cy.initTestUser(homeserver, "Mike").then((_user) => {
user = _user;
});
cy.getBot(synapse, { displayName: "Bot", autoAcceptInvites: false }).then((_bot) => {
cy.getBot(homeserver, { displayName: "Bot", autoAcceptInvites: false }).then((_bot) => {
bot = _bot;
});
});
@ -189,7 +189,7 @@ describe("Widget PIP", () => {
});
afterEach(() => {
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
cy.stopWebServers();
});

View File

@ -0,0 +1,181 @@
/*
Copyright 2023 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.
*/
/// <reference types="cypress" />
import * as path from "path";
import * as os from "os";
import * as crypto from "crypto";
import * as fse from "fs-extra";
import PluginEvents = Cypress.PluginEvents;
import PluginConfigOptions = Cypress.PluginConfigOptions;
import { getFreePort } from "../utils/port";
import { dockerExec, dockerLogs, dockerRun, dockerStop } from "../docker";
import { HomeserverConfig, HomeserverInstance } from "../utils/homeserver";
// A cypress plugins to add command to start & stop dendrites in
// docker with preset templates.
const dendrites = new Map<string, HomeserverInstance>();
function randB64Bytes(numBytes: number): string {
return crypto.randomBytes(numBytes).toString("base64").replace(/=*$/, "");
}
async function cfgDirFromTemplate(template: string): Promise<HomeserverConfig> {
template = "default";
const templateDir = path.join(__dirname, "templates", template);
const configFile = "dendrite.yaml";
const stats = await fse.stat(templateDir);
if (!stats?.isDirectory) {
throw new Error(`No such template: ${template}`);
}
const tempDir = await fse.mkdtemp(path.join(os.tmpdir(), "react-sdk-dendritedocker-"));
// copy the contents of the template dir, omitting homeserver.yaml as we'll template that
console.log(`Copy ${templateDir} -> ${tempDir}`);
await fse.copy(templateDir, tempDir, { filter: (f) => path.basename(f) !== configFile });
const registrationSecret = randB64Bytes(16);
const port = await getFreePort();
const baseUrl = `http://localhost:${port}`;
// now copy homeserver.yaml, applying substitutions
console.log(`Gen ${path.join(templateDir, configFile)}`);
let hsYaml = await fse.readFile(path.join(templateDir, configFile), "utf8");
hsYaml = hsYaml.replace(/{{REGISTRATION_SECRET}}/g, registrationSecret);
await fse.writeFile(path.join(tempDir, configFile), hsYaml);
await dockerRun({
image: "matrixdotorg/dendrite-monolith:main",
params: ["--rm", "--entrypoint=", "-v", `${tempDir}:/mnt`],
containerName: `react-sdk-cypress-dendrite-keygen`,
cmd: ["/usr/bin/generate-keys", "-private-key", "/mnt/matrix_key.pem"],
});
return {
port,
baseUrl,
configDir: tempDir,
registrationSecret,
};
}
// Start a dendrite instance: the template must be the name of
// one of the templates in the cypress/plugins/dendritedocker/templates
// directory
async function dendriteStart(template: string): Promise<HomeserverInstance> {
const denCfg = await cfgDirFromTemplate(template);
console.log(`Starting dendrite with config dir ${denCfg.configDir}...`);
const dendriteId = await dockerRun({
image: "matrixdotorg/dendrite-monolith:main",
params: [
"--rm",
"-v",
`${denCfg.configDir}:/etc/dendrite`,
"-p",
`${denCfg.port}:8008/tcp`,
"--entrypoint",
"/usr/bin/dendrite-monolith-server",
],
containerName: `react-sdk-cypress-dendrite`,
cmd: ["--really-enable-open-registration", "true", "run"],
});
console.log(`Started dendrite with id ${dendriteId} on port ${denCfg.port}.`);
// Await Dendrite healthcheck
await dockerExec({
containerId: dendriteId,
params: [
"curl",
"--connect-timeout",
"30",
"--retry",
"30",
"--retry-delay",
"1",
"--retry-all-errors",
"--silent",
"http://localhost:8008/_matrix/client/versions",
],
});
const dendrite: HomeserverInstance = { serverId: dendriteId, ...denCfg };
dendrites.set(dendriteId, dendrite);
return dendrite;
}
async function dendriteStop(id: string): Promise<void> {
const denCfg = dendrites.get(id);
if (!denCfg) throw new Error("Unknown dendrite ID");
const dendriteLogsPath = path.join("cypress", "dendritelogs", id);
await fse.ensureDir(dendriteLogsPath);
await dockerLogs({
containerId: id,
stdoutFile: path.join(dendriteLogsPath, "stdout.log"),
stderrFile: path.join(dendriteLogsPath, "stderr.log"),
});
await dockerStop({
containerId: id,
});
await fse.remove(denCfg.configDir);
dendrites.delete(id);
console.log(`Stopped dendrite id ${id}.`);
// cypress deliberately fails if you return 'undefined', so
// return null to signal all is well, and we've handled the task.
return null;
}
/**
* @type {Cypress.PluginConfig}
*/
export function dendriteDocker(on: PluginEvents, config: PluginConfigOptions) {
on("task", {
dendriteStart,
dendriteStop,
});
on("after:spec", async (spec) => {
// Cleans up any remaining dendrite instances after a spec run
// This is on the theory that we should avoid re-using dendrite
// instances between spec runs: they should be cheap enough to
// start that we can have a separate one for each spec run or even
// test. If we accidentally re-use dendrites, we could inadvertently
// make our tests depend on each other.
for (const denId of dendrites.keys()) {
console.warn(`Cleaning up dendrite ID ${denId} after ${spec.name}`);
await dendriteStop(denId);
}
});
on("before:run", async () => {
// tidy up old dendrite log files before each run
await fse.emptyDir(path.join("cypress", "dendritelogs"));
});
}

View File

@ -0,0 +1,374 @@
# This is the Dendrite configuration file.
#
# The configuration is split up into sections - each Dendrite component has a
# configuration section, in addition to the "global" section which applies to
# all components.
# The version of the configuration file.
version: 2
# Global Matrix configuration. This configuration applies to all components.
global:
# The domain name of this homeserver.
server_name: localhost
# The path to the signing private key file, used to sign requests and events.
# Note that this is NOT the same private key as used for TLS! To generate a
# signing key, use "./bin/generate-keys --private-key matrix_key.pem".
private_key: matrix_key.pem
# The paths and expiry timestamps (as a UNIX timestamp in millisecond precision)
# to old signing keys that were formerly in use on this domain name. These
# keys will not be used for federation request or event signing, but will be
# provided to any other homeserver that asks when trying to verify old events.
old_private_keys:
# If the old private key file is available:
# - private_key: old_matrix_key.pem
# expired_at: 1601024554498
# If only the public key (in base64 format) and key ID are known:
# - public_key: mn59Kxfdq9VziYHSBzI7+EDPDcBS2Xl7jeUdiiQcOnM=
# key_id: ed25519:mykeyid
# expired_at: 1601024554498
# How long a remote server can cache our server signing key before requesting it
# again. Increasing this number will reduce the number of requests made by other
# servers for our key but increases the period that a compromised key will be
# considered valid by other homeservers.
key_validity_period: 168h0m0s
# Global database connection pool, for PostgreSQL monolith deployments only. If
# this section is populated then you can omit the "database" blocks in all other
# sections. For polylith deployments, or monolith deployments using SQLite databases,
# you must configure the "database" block for each component instead.
# database:
# connection_string: postgresql://username:password@hostname/dendrite?sslmode=disable
# max_open_conns: 90
# max_idle_conns: 5
# conn_max_lifetime: -1
# Configuration for in-memory caches. Caches can often improve performance by
# keeping frequently accessed items (like events, identifiers etc.) in memory
# rather than having to read them from the database.
cache:
# The estimated maximum size for the global cache in bytes, or in terabytes,
# gigabytes, megabytes or kilobytes when the appropriate 'tb', 'gb', 'mb' or
# 'kb' suffix is specified. Note that this is not a hard limit, nor is it a
# memory limit for the entire process. A cache that is too small may ultimately
# provide little or no benefit.
max_size_estimated: 1gb
# The maximum amount of time that a cache entry can live for in memory before
# it will be evicted and/or refreshed from the database. Lower values result in
# easier admission of new cache entries but may also increase database load in
# comparison to higher values, so adjust conservatively. Higher values may make
# it harder for new items to make it into the cache, e.g. if new rooms suddenly
# become popular.
max_age: 1h
# The server name to delegate server-server communications to, with optional port
# e.g. localhost:443
well_known_server_name: ""
# The server name to delegate client-server communications to, with optional port
# e.g. localhost:443
well_known_client_name: ""
# Lists of domains that the server will trust as identity servers to verify third
# party identifiers such as phone numbers and email addresses.
trusted_third_party_id_servers:
- matrix.org
- vector.im
# Disables federation. Dendrite will not be able to communicate with other servers
# in the Matrix federation and the federation API will not be exposed.
disable_federation: false
# Configures the handling of presence events. Inbound controls whether we receive
# presence events from other servers, outbound controls whether we send presence
# events for our local users to other servers.
presence:
enable_inbound: false
enable_outbound: false
# Configures phone-home statistics reporting. These statistics contain the server
# name, number of active users and some information on your deployment config.
# We use this information to understand how Dendrite is being used in the wild.
report_stats:
enabled: false
endpoint: https://matrix.org/report-usage-stats/push
# Server notices allows server admins to send messages to all users on the server.
server_notices:
enabled: false
# The local part, display name and avatar URL (as a mxc:// URL) for the user that
# will send the server notices. These are visible to all users on the deployment.
local_part: "_server"
display_name: "Server Alerts"
avatar_url: ""
# The room name to be used when sending server notices. This room name will
# appear in user clients.
room_name: "Server Alerts"
# Configuration for NATS JetStream
jetstream:
# A list of NATS Server addresses to connect to. If none are specified, an
# internal NATS server will be started automatically when running Dendrite in
# monolith mode. For polylith deployments, it is required to specify the address
# of at least one NATS Server node.
addresses:
# - localhost:4222
# Disable the validation of TLS certificates of NATS. This is
# not recommended in production since it may allow NATS traffic
# to be sent to an insecure endpoint.
disable_tls_validation: false
# Persistent directory to store JetStream streams in. This directory should be
# preserved across Dendrite restarts.
storage_path: ./
# The prefix to use for stream names for this homeserver - really only useful
# if you are running more than one Dendrite server on the same NATS deployment.
topic_prefix: Dendrite
# Configuration for Prometheus metric collection.
metrics:
enabled: false
basic_auth:
username: metrics
password: metrics
# Optional DNS cache. The DNS cache may reduce the load on DNS servers if there
# is no local caching resolver available for use.
dns_cache:
enabled: false
cache_size: 256
cache_lifetime: "5m" # 5 minutes; https://pkg.go.dev/time@master#ParseDuration
# Configuration for the Appservice API.
app_service_api:
# Disable the validation of TLS certificates of appservices. This is
# not recommended in production since it may allow appservice traffic
# to be sent to an insecure endpoint.
disable_tls_validation: false
# Appservice configuration files to load into this homeserver.
config_files:
# - /path/to/appservice_registration.yaml
# Configuration for the Client API.
client_api:
# Prevents new users from being able to register on this homeserver, except when
# using the registration shared secret below.
registration_disabled: false
# Prevents new guest accounts from being created. Guest registration is also
# disabled implicitly by setting 'registration_disabled' above.
guests_disabled: true
# If set, allows registration by anyone who knows the shared secret, regardless
# of whether registration is otherwise disabled.
registration_shared_secret: "{{REGISTRATION_SECRET}}"
# Whether to require reCAPTCHA for registration. If you have enabled registration
# then this is HIGHLY RECOMMENDED to reduce the risk of your homeserver being used
# for coordinated spam attacks.
enable_registration_captcha: false
# Settings for ReCAPTCHA.
recaptcha_public_key: ""
recaptcha_private_key: ""
recaptcha_bypass_secret: ""
# To use hcaptcha.com instead of ReCAPTCHA, set the following parameters, otherwise just keep them empty.
# recaptcha_siteverify_api: "https://hcaptcha.com/siteverify"
# recaptcha_api_js_url: "https://js.hcaptcha.com/1/api.js"
# recaptcha_form_field: "h-captcha-response"
# recaptcha_sitekey_class: "h-captcha"
# TURN server information that this homeserver should send to clients.
turn:
turn_user_lifetime: "5m"
turn_uris:
# - turn:turn.server.org?transport=udp
# - turn:turn.server.org?transport=tcp
turn_shared_secret: ""
# If your TURN server requires static credentials, then you will need to enter
# them here instead of supplying a shared secret. Note that these credentials
# will be visible to clients!
# turn_username: ""
# turn_password: ""
# Settings for rate-limited endpoints. Rate limiting kicks in after the threshold
# number of "slots" have been taken by requests from a specific host. Each "slot"
# will be released after the cooloff time in milliseconds. Server administrators
# and appservice users are exempt from rate limiting by default.
rate_limiting:
enabled: true
threshold: 20
cooloff_ms: 500
exempt_user_ids:
# - "@user:domain.com"
# Configuration for the Federation API.
federation_api:
# How many times we will try to resend a failed transaction to a specific server. The
# backoff is 2**x seconds, so 1 = 2 seconds, 2 = 4 seconds, 3 = 8 seconds etc. Once
# the max retries are exceeded, Dendrite will no longer try to send transactions to
# that server until it comes back to life and connects to us again.
send_max_retries: 16
# Disable the validation of TLS certificates of remote federated homeservers. Do not
# enable this option in production as it presents a security risk!
disable_tls_validation: false
# Disable HTTP keepalives, which also prevents connection reuse. Dendrite will typically
# keep HTTP connections open to remote hosts for 5 minutes as they can be reused much
# more quickly than opening new connections each time. Disabling keepalives will close
# HTTP connections immediately after a successful request but may result in more CPU and
# memory being used on TLS handshakes for each new connection instead.
disable_http_keepalives: false
# Perspective keyservers to use as a backup when direct key fetches fail. This may
# be required to satisfy key requests for servers that are no longer online when
# joining some rooms.
key_perspectives:
- server_name: matrix.org
keys:
- key_id: ed25519:auto
public_key: Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw
- key_id: ed25519:a_RXGa
public_key: l8Hft5qXKn1vfHrg3p4+W8gELQVo8N13JkluMfmn2sQ
# This option will control whether Dendrite will prefer to look up keys directly
# or whether it should try perspective servers first, using direct fetches as a
# last resort.
prefer_direct_fetch: false
database:
connection_string: file:dendrite-federationapi.db
# Configuration for the Media API.
media_api:
# Storage path for uploaded media. May be relative or absolute.
base_path: ./media_store
# The maximum allowed file size (in bytes) for media uploads to this homeserver
# (0 = unlimited). If using a reverse proxy, ensure it allows requests at least
#this large (e.g. the client_max_body_size setting in nginx).
max_file_size_bytes: 10485760
# Whether to dynamically generate thumbnails if needed.
dynamic_thumbnails: false
# The maximum number of simultaneous thumbnail generators to run.
max_thumbnail_generators: 10
# A list of thumbnail sizes to be generated for media content.
thumbnail_sizes:
- width: 32
height: 32
method: crop
- width: 96
height: 96
method: crop
- width: 640
height: 480
method: scale
database:
connection_string: file:dendrite-mediaapi.db
# Configuration for enabling experimental MSCs on this homeserver.
mscs:
mscs:
# - msc2836 # (Threading, see https://github.com/matrix-org/matrix-doc/pull/2836)
# - msc2946 # (Spaces Summary, see https://github.com/matrix-org/matrix-doc/pull/2946)
database:
connection_string: file:dendrite-msc.db
# Configuration for the Sync API.
sync_api:
# This option controls which HTTP header to inspect to find the real remote IP
# address of the client. This is likely required if Dendrite is running behind
# a reverse proxy server.
# real_ip_header: X-Real-IP
# Configuration for the full-text search engine.
search:
# Whether or not search is enabled.
enabled: false
# The path where the search index will be created in.
index_path: "./searchindex"
# The language most likely to be used on the server - used when indexing, to
# ensure the returned results match expectations. A full list of possible languages
# can be found at https://github.com/blevesearch/bleve/tree/master/analysis/lang
language: "en"
database:
connection_string: file:dendrite-syncapi.db
# Configuration for the User API.
user_api:
# The cost when hashing passwords on registration/login. Default: 10. Min: 4, Max: 31
# See https://pkg.go.dev/golang.org/x/crypto/bcrypt for more information.
# Setting this lower makes registration/login consume less CPU resources at the cost
# of security should the database be compromised. Setting this higher makes registration/login
# consume more CPU resources but makes it harder to brute force password hashes. This value
# can be lowered if performing tests or on embedded Dendrite instances (e.g WASM builds).
bcrypt_cost: 10
# The length of time that a token issued for a relying party from
# /_matrix/client/r0/user/{userId}/openid/request_token endpoint
# is considered to be valid in milliseconds.
# The default lifetime is 3600000ms (60 minutes).
# openid_token_lifetime_ms: 3600000
# Users who register on this homeserver will automatically be joined to the rooms listed under "auto_join_rooms" option.
# By default, any room aliases included in this list will be created as a publicly joinable room
# when the first user registers for the homeserver. If the room already exists,
# make certain it is a publicly joinable room, i.e. the join rule of the room must be set to 'public'.
# As Spaces are just rooms under the hood, Space aliases may also be used.
auto_join_rooms:
# - "#main:matrix.org"
account_database:
connection_string: file:dendrite-userapi.db
room_server:
database:
connection_string: file:dendrite-roomserverapi.db
key_server:
database:
connection_string: file:dendrite-keyserverapi.db
# Configuration for Opentracing.
# See https://github.com/matrix-org/dendrite/tree/master/docs/tracing for information on
# how this works and how to set it up.
tracing:
enabled: false
jaeger:
serviceName: ""
disabled: false
rpc_metrics: false
tags: []
sampler: null
reporter: null
headers: null
baggage_restrictions: null
throttler: null
# Logging configuration. The "std" logging type controls the logs being sent to
# stdout. The "file" logging type controls logs being written to a log folder on
# the disk. Supported log levels are "debug", "info", "warn", "error".
logging:
- type: std
level: debug
- type: file
level: debug
params:
path: ./logs

View File

@ -30,7 +30,7 @@ export function dockerRun(opts: {
image: string;
containerName: string;
params?: string[];
cmd?: string;
cmd?: string[];
}): Promise<string> {
const userInfo = os.userInfo();
const params = opts.params ?? [];
@ -49,7 +49,7 @@ export function dockerRun(opts: {
opts.image,
];
if (opts.cmd) args.push(opts.cmd);
if (opts.cmd) args.push(...opts.cmd);
return new Promise<string>((resolve, reject) => {
childProcess.execFile("docker", args, (err, stdout) => {

View File

@ -19,6 +19,7 @@ limitations under the License.
import PluginEvents = Cypress.PluginEvents;
import PluginConfigOptions = Cypress.PluginConfigOptions;
import { synapseDocker } from "./synapsedocker";
import { dendriteDocker } from "./dendritedocker";
import { slidingSyncProxyDocker } from "./sliding-sync";
import { webserver } from "./webserver";
import { docker } from "./docker";
@ -30,6 +31,7 @@ import { log } from "./log";
export default function (on: PluginEvents, config: PluginConfigOptions) {
docker(on, config);
synapseDocker(on, config);
dendriteDocker(on, config);
slidingSyncProxyDocker(on, config);
webserver(on, config);
log(on, config);

View File

@ -20,7 +20,7 @@ import PluginEvents = Cypress.PluginEvents;
import PluginConfigOptions = Cypress.PluginConfigOptions;
import { dockerExec, dockerIp, dockerRun, dockerStop } from "../docker";
import { getFreePort } from "../utils/port";
import { SynapseInstance } from "../synapsedocker";
import { HomeserverInstance } from "../utils/homeserver";
// A cypress plugin to add command to start & stop https://github.com/matrix-org/sliding-sync
// SLIDING_SYNC_PROXY_TAG env used as the docker tag to use for `ghcr.io/matrix-org/sliding-sync-proxy` image.
@ -35,7 +35,7 @@ const instances = new Map<string, ProxyInstance>();
const PG_PASSWORD = "p4S5w0rD";
async function proxyStart(dockerTag: string, synapse: SynapseInstance): Promise<ProxyInstance> {
async function proxyStart(dockerTag: string, homeserver: HomeserverInstance): Promise<ProxyInstance> {
console.log(new Date(), "Starting sliding sync proxy...");
const postgresId = await dockerRun({
@ -45,7 +45,7 @@ async function proxyStart(dockerTag: string, synapse: SynapseInstance): Promise<
});
const postgresIp = await dockerIp({ containerId: postgresId });
const synapseIp = await dockerIp({ containerId: synapse.synapseId });
const homeserverIp = await dockerIp({ containerId: homeserver.serverId });
console.log(new Date(), "postgres container up");
const waitTimeMillis = 30000;
@ -81,7 +81,7 @@ async function proxyStart(dockerTag: string, synapse: SynapseInstance): Promise<
"-e",
"SYNCV3_SECRET=bwahahaha",
"-e",
`SYNCV3_SERVER=http://${synapseIp}:8008`,
`SYNCV3_SERVER=http://${homeserverIp}:8008`,
"-e",
`SYNCV3_DB=user=postgres dbname=postgres password=${PG_PASSWORD} host=${postgresIp} sslmode=disable`,
],

View File

@ -25,29 +25,18 @@ import PluginEvents = Cypress.PluginEvents;
import PluginConfigOptions = Cypress.PluginConfigOptions;
import { getFreePort } from "../utils/port";
import { dockerExec, dockerLogs, dockerRun, dockerStop } from "../docker";
import { HomeserverConfig, HomeserverInstance } from "../utils/homeserver";
// A cypress plugins to add command to start & stop synapses in
// docker with preset templates.
interface SynapseConfig {
configDir: string;
registrationSecret: string;
// Synapse must be configured with its public_baseurl so we have to allocate a port & url at this stage
baseUrl: string;
port: number;
}
export interface SynapseInstance extends SynapseConfig {
synapseId: string;
}
const synapses = new Map<string, SynapseInstance>();
const synapses = new Map<string, HomeserverInstance>();
function randB64Bytes(numBytes: number): string {
return crypto.randomBytes(numBytes).toString("base64").replace(/=*$/, "");
}
async function cfgDirFromTemplate(template: string): Promise<SynapseConfig> {
async function cfgDirFromTemplate(template: string): Promise<HomeserverConfig> {
const templateDir = path.join(__dirname, "templates", template);
const stats = await fse.stat(templateDir);
@ -94,7 +83,7 @@ async function cfgDirFromTemplate(template: string): Promise<SynapseConfig> {
// Start a synapse instance: the template must be the name of
// one of the templates in the cypress/plugins/synapsedocker/templates
// directory
async function synapseStart(template: string): Promise<SynapseInstance> {
async function synapseStart(template: string): Promise<HomeserverInstance> {
const synCfg = await cfgDirFromTemplate(template);
console.log(`Starting synapse with config dir ${synCfg.configDir}...`);
@ -103,7 +92,7 @@ async function synapseStart(template: string): Promise<SynapseInstance> {
image: "matrixdotorg/synapse:develop",
containerName: `react-sdk-cypress-synapse`,
params: ["--rm", "-v", `${synCfg.configDir}:/data`, "-p", `${synCfg.port}:8008/tcp`],
cmd: "run",
cmd: ["run"],
});
console.log(`Started synapse with id ${synapseId} on port ${synCfg.port}.`);
@ -125,7 +114,7 @@ async function synapseStart(template: string): Promise<SynapseInstance> {
],
});
const synapse: SynapseInstance = { synapseId, ...synCfg };
const synapse: HomeserverInstance = { serverId: synapseId, ...synCfg };
synapses.set(synapseId, synapse);
return synapse;
}

View File

@ -0,0 +1,28 @@
/*
Copyright 2023 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.
*/
/// <reference types="cypress" />
export interface HomeserverConfig {
configDir: string;
registrationSecret: string;
baseUrl: string;
port: number;
}
export interface HomeserverInstance extends HomeserverConfig {
serverId: string;
}

View File

@ -17,8 +17,8 @@ limitations under the License.
/// <reference types="cypress" />
import type { ISendEventResponse, MatrixClient, Room } from "matrix-js-sdk/src/matrix";
import { SynapseInstance } from "../plugins/synapsedocker";
import { Credentials } from "./synapse";
import { HomeserverInstance } from "../plugins/utils/homeserver";
import { Credentials } from "./homeserver";
import Chainable = Cypress.Chainable;
interface CreateBotOpts {
@ -61,19 +61,19 @@ declare global {
interface Chainable {
/**
* Returns a new Bot instance
* @param synapse the instance on which to register the bot user
* @param homeserver the instance on which to register the bot user
* @param opts create bot options
*/
getBot(synapse: SynapseInstance, opts: CreateBotOpts): Chainable<CypressBot>;
getBot(homeserver: HomeserverInstance, opts: CreateBotOpts): Chainable<CypressBot>;
/**
* Returns a new Bot instance logged in as an existing user
* @param synapse the instance on which to register the bot user
* @param homeserver the instance on which to register the bot user
* @param username the username for the bot to log in with
* @param password the password for the bot to log in with
* @param opts create bot options
*/
loginBot(
synapse: SynapseInstance,
homeserver: HomeserverInstance,
username: string,
password: string,
opts: CreateBotOpts,
@ -102,7 +102,7 @@ declare global {
}
function setupBotClient(
synapse: SynapseInstance,
homeserver: HomeserverInstance,
credentials: Credentials,
opts: CreateBotOpts,
): Chainable<MatrixClient> {
@ -119,7 +119,7 @@ function setupBotClient(
};
const cli = new win.matrixcs.MatrixClient({
baseUrl: synapse.baseUrl,
baseUrl: homeserver.baseUrl,
userId: credentials.userId,
deviceId: credentials.deviceId,
accessToken: credentials.accessToken,
@ -160,15 +160,15 @@ function setupBotClient(
});
}
Cypress.Commands.add("getBot", (synapse: SynapseInstance, opts: CreateBotOpts): Chainable<CypressBot> => {
Cypress.Commands.add("getBot", (homeserver: HomeserverInstance, opts: CreateBotOpts): Chainable<CypressBot> => {
opts = Object.assign({}, defaultCreateBotOptions, opts);
const username = Cypress._.uniqueId(opts.userIdPrefix);
const password = Cypress._.uniqueId("password_");
return cy
.registerUser(synapse, username, password, opts.displayName)
.registerUser(homeserver, username, password, opts.displayName)
.then((credentials) => {
cy.log(`Registered bot user ${username} with displayname ${opts.displayName}`);
return setupBotClient(synapse, credentials, opts);
return setupBotClient(homeserver, credentials, opts);
})
.then((client): Chainable<CypressBot> => {
Object.assign(client, { __cypress_password: password });
@ -178,10 +178,15 @@ Cypress.Commands.add("getBot", (synapse: SynapseInstance, opts: CreateBotOpts):
Cypress.Commands.add(
"loginBot",
(synapse: SynapseInstance, username: string, password: string, opts: CreateBotOpts): Chainable<MatrixClient> => {
(
homeserver: HomeserverInstance,
username: string,
password: string,
opts: CreateBotOpts,
): Chainable<MatrixClient> => {
opts = Object.assign({}, defaultCreateBotOptions, { bootstrapCrossSigning: false }, opts);
return cy.loginUser(synapse, username, password).then((credentials) => {
return setupBotClient(synapse, credentials, opts);
return cy.loginUser(homeserver, username, password).then((credentials) => {
return setupBotClient(homeserver, credentials, opts);
});
},
);

View File

@ -19,7 +19,7 @@ limitations under the License.
import "@percy/cypress";
import "cypress-real-events";
import "./synapse";
import "./homeserver";
import "./login";
import "./labs";
import "./client";

View File

@ -1,5 +1,5 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Copyright 2023 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.
@ -20,34 +20,34 @@ import * as crypto from "crypto";
import Chainable = Cypress.Chainable;
import AUTWindow = Cypress.AUTWindow;
import { SynapseInstance } from "../plugins/synapsedocker";
import { HomeserverInstance } from "../plugins/utils/homeserver";
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
interface Chainable {
/**
* Start a synapse instance with a given config template.
* @param template path to template within cypress/plugins/synapsedocker/template/ directory.
* Start a homeserver instance with a given config template.
* @param template path to template within cypress/plugins/{homeserver}docker/template/ directory.
*/
startSynapse(template: string): Chainable<SynapseInstance>;
startHomeserver(template: string): Chainable<HomeserverInstance>;
/**
* Custom command wrapping task:synapseStop whilst preventing uncaught exceptions
* for if Synapse stopping races with the app's background sync loop.
* @param synapse the synapse instance returned by startSynapse
* Custom command wrapping task:{homeserver}Stop whilst preventing uncaught exceptions
* for if Homeserver stopping races with the app's background sync loop.
* @param homeserver the homeserver instance returned by start{Homeserver}
*/
stopSynapse(synapse: SynapseInstance): Chainable<AUTWindow>;
stopHomeserver(homeserver: HomeserverInstance): Chainable<AUTWindow>;
/**
* Register a user on the given Synapse using the shared registration secret.
* @param synapse the synapse instance returned by startSynapse
* Register a user on the given Homeserver using the shared registration secret.
* @param homeserver the homeserver instance returned by start{Homeserver}
* @param username the username of the user to register
* @param password the password of the user to register
* @param displayName optional display name to set on the newly registered user
*/
registerUser(
synapse: SynapseInstance,
homeserver: HomeserverInstance,
username: string,
password: string,
displayName?: string,
@ -56,16 +56,18 @@ declare global {
}
}
function startSynapse(template: string): Chainable<SynapseInstance> {
return cy.task<SynapseInstance>("synapseStart", template);
function startHomeserver(template: string): Chainable<HomeserverInstance> {
const homeserverName = Cypress.env("HOMESERVER");
return cy.task<HomeserverInstance>(homeserverName + "Start", template);
}
function stopSynapse(synapse?: SynapseInstance): Chainable<AUTWindow> {
if (!synapse) return;
// Navigate away from app to stop the background network requests which will race with Synapse shutting down
function stopHomeserver(homeserver?: HomeserverInstance): Chainable<AUTWindow> {
if (!homeserver) return;
// Navigate away from app to stop the background network requests which will race with Homeserver shutting down
return cy.window({ log: false }).then((win) => {
win.location.href = "about:blank";
cy.task("synapseStop", synapse.synapseId);
const homeserverName = Cypress.env("HOMESERVER");
cy.task(homeserverName + "Stop", homeserver.serverId);
});
}
@ -77,12 +79,12 @@ export interface Credentials {
}
function registerUser(
synapse: SynapseInstance,
homeserver: HomeserverInstance,
username: string,
password: string,
displayName?: string,
): Chainable<Credentials> {
const url = `${synapse.baseUrl}/_synapse/admin/v1/register`;
const url = `${homeserver.baseUrl}/_synapse/admin/v1/register`;
return cy
.then(() => {
// get a nonce
@ -91,7 +93,7 @@ function registerUser(
.then((response) => {
const { nonce } = response.body;
const mac = crypto
.createHmac("sha1", synapse.registrationSecret)
.createHmac("sha1", homeserver.registrationSecret)
.update(`${nonce}\0${username}\0${password}\0notadmin`)
.digest("hex");
@ -121,6 +123,6 @@ function registerUser(
}));
}
Cypress.Commands.add("startSynapse", startSynapse);
Cypress.Commands.add("stopSynapse", stopSynapse);
Cypress.Commands.add("startHomeserver", startHomeserver);
Cypress.Commands.add("stopHomeserver", stopHomeserver);
Cypress.Commands.add("registerUser", registerUser);

View File

@ -17,7 +17,7 @@ limitations under the License.
/// <reference types="cypress" />
import Chainable = Cypress.Chainable;
import { SynapseInstance } from "../plugins/synapsedocker";
import { HomeserverInstance } from "../plugins/utils/homeserver";
export interface UserCredentials {
accessToken: string;
@ -41,7 +41,7 @@ declare global {
* useed.
*/
initTestUser(
synapse: SynapseInstance,
homeserver: HomeserverInstance,
displayName: string,
prelaunchFn?: () => void,
userIdPrefix?: string,
@ -52,7 +52,7 @@ declare global {
* @param username login username
* @param password login password
*/
loginUser(synapse: SynapseInstance, username: string, password: string): Chainable<UserCredentials>;
loginUser(synapse: HomeserverInstance, username: string, password: string): Chainable<UserCredentials>;
}
}
}
@ -60,8 +60,8 @@ declare global {
// eslint-disable-next-line max-len
Cypress.Commands.add(
"loginUser",
(synapse: SynapseInstance, username: string, password: string): Chainable<UserCredentials> => {
const url = `${synapse.baseUrl}/_matrix/client/r0/login`;
(homeserver: HomeserverInstance, username: string, password: string): Chainable<UserCredentials> => {
const url = `${homeserver.baseUrl}/_matrix/client/r0/login`;
return cy
.request<{
access_token: string;
@ -95,7 +95,7 @@ Cypress.Commands.add(
Cypress.Commands.add(
"initTestUser",
(
synapse: SynapseInstance,
homeserver: HomeserverInstance,
displayName: string,
prelaunchFn?: () => void,
userIdPrefix = "user_",
@ -112,15 +112,15 @@ Cypress.Commands.add(
const username = Cypress._.uniqueId(userIdPrefix);
const password = Cypress._.uniqueId("password_");
return cy
.registerUser(synapse, username, password, displayName)
.registerUser(homeserver, username, password, displayName)
.then(() => {
return cy.loginUser(synapse, username, password);
return cy.loginUser(homeserver, username, password);
})
.then((response) => {
cy.log(`Registered test user ${username} with displayname ${displayName}`);
cy.window({ log: false }).then((win) => {
// Seed the localStorage with the required credentials
win.localStorage.setItem("mx_hs_url", synapse.baseUrl);
win.localStorage.setItem("mx_hs_url", homeserver.baseUrl);
win.localStorage.setItem("mx_user_id", response.userId);
win.localStorage.setItem("mx_access_token", response.accessToken);
win.localStorage.setItem("mx_device_id", response.deviceId);

View File

@ -19,7 +19,7 @@ limitations under the License.
import Chainable = Cypress.Chainable;
import AUTWindow = Cypress.AUTWindow;
import { ProxyInstance } from "../plugins/sliding-sync";
import { SynapseInstance } from "../plugins/synapsedocker";
import { HomeserverInstance } from "../plugins/utils/homeserver";
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
@ -27,9 +27,9 @@ declare global {
interface Chainable {
/**
* Start a sliding sync proxy instance.
* @param synapse the synapse instance returned by startSynapse
* @param homeserver the homeserver instance returned by startHomeserver
*/
startProxy(synapse: SynapseInstance): Chainable<ProxyInstance>;
startProxy(homeserver: HomeserverInstance): Chainable<ProxyInstance>;
/**
* Custom command wrapping task:proxyStop whilst preventing uncaught exceptions
@ -41,13 +41,13 @@ declare global {
}
}
function startProxy(synapse: SynapseInstance): Chainable<ProxyInstance> {
return cy.task<ProxyInstance>("proxyStart", synapse);
function startProxy(homeserver: HomeserverInstance): Chainable<ProxyInstance> {
return cy.task<ProxyInstance>("proxyStart", homeserver);
}
function stopProxy(proxy?: ProxyInstance): Chainable<AUTWindow> {
if (!proxy) return;
// Navigate away from app to stop the background network requests which will race with Synapse shutting down
// Navigate away from app to stop the background network requests which will race with Homeserver shutting down
return cy.window({ log: false }).then((win) => {
win.location.href = "about:blank";
cy.task("proxyStop", proxy);

View File

@ -1,5 +1,5 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Copyright 2022-2023 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.
@ -16,12 +16,6 @@ limitations under the License.
/// <reference types="cypress" />
// @see https://github.com/cypress-io/cypress/issues/915#issuecomment-475862672
// Modified due to changes to `cy.queue` https://github.com/cypress-io/cypress/pull/17448
// Note: this DOES NOT run Promises in parallel like `Promise.all` due to the nature
// of Cypress promise-like objects and command queue. This only makes it convenient to use the same
// API but runs the commands sequentially.
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
@ -31,51 +25,35 @@ declare global {
all<T extends Cypress.Chainable[] | []>(
commands: T,
): Cypress.Chainable<{ [P in keyof T]: ChainableValue<T[P]> }>;
queue: any;
}
interface Chainable {
chainerId: string;
}
}
}
const chainStart = Symbol("chainStart");
/**
* @description Returns a single Chainable that resolves when all of the Chainables pass.
* @param {Cypress.Chainable[]} commands - List of Cypress.Chainable to resolve.
* @returns {Cypress.Chainable} Cypress when all Chainables are resolved.
*/
cy.all = function all(commands): Cypress.Chainable {
const chain = cy.wrap(null, { log: false });
const stopCommand = Cypress._.find(cy.queue.get(), {
attributes: { chainerId: chain.chainerId },
const resultArray = [];
// as each command completes, store the result in the corresponding location of resultArray.
for (let i = 0; i < commands.length; i++) {
commands[i].then((val) => {
resultArray[i] = val;
});
}
// add an entry to the log which, when clicked, will write the results to the console.
Cypress.log({
name: "all",
consoleProps: () => ({ Results: resultArray }),
});
const startCommand = Cypress._.find(cy.queue.get(), {
attributes: { chainerId: commands[0].chainerId },
});
const p = chain.then(() => {
return cy.wrap(
// @see https://lodash.com/docs/4.17.15#lodash
Cypress._(commands)
.map((cmd) => {
return cmd[chainStart]
? cmd[chainStart].attributes
: Cypress._.find(cy.queue.get(), {
attributes: { chainerId: cmd.chainerId },
}).attributes;
})
.concat(stopCommand.attributes)
.slice(1)
.map((cmd) => {
return cmd.prev.get("subject");
})
.value(),
);
});
p[chainStart] = startCommand;
return p;
// return a chainable which wraps the resultArray. Although this doesn't have a direct dependency on the input
// commands, cypress won't process it until the commands that precede it on the command queue (which must include
// the input commands) have passed.
return cy.wrap(resultArray, { log: false });
};
// Needed to make this file a module

View File

@ -21,7 +21,7 @@ be tested. When running Cypress tests yourself, the standard `yarn start` from t
element-web project is fine: leave it running it a different terminal as you would
when developing.
The tests use Docker to launch Synapse instances to test against, so you'll also
The tests use Docker to launch Homeserver (Synapse or Dendrite) instances to test against, so you'll also
need to have Docker installed and working in order to run the Cypress tests.
There are a few different ways to run the tests yourself. The simplest is to run:
@ -58,10 +58,10 @@ Synapse can be launched with different configurations in order to test element
in different configurations. `cypress/plugins/synapsedocker/templates` contains
template configuration files for each different configuration.
Each test suite can then launch whatever Synapse instances it needs it whatever
Each test suite can then launch whatever Synapse instances it needs in whatever
configurations.
Note that although tests should stop the Synapse instances after running and the
Note that although tests should stop the Homeserver instances after running and the
plugin also stop any remaining instances after all tests have run, it is possible
to be left with some stray containers if, for example, you terminate a test such
that the `after()` does not run and also exit Cypress uncleanly. All the containers
@ -82,29 +82,29 @@ a read.
### Getting a Synapse
The key difference is in starting Synapse instances. Tests use this plugin via
`cy.startSynapse()` to provide a Synapse instance to log into:
`cy.startHomeserver()` to provide a Homeserver instance to log into:
```javascript
cy.startSynapse("consent").then((result) => {
synapse = result;
cy.startHomeserver("consent").then((result) => {
homeserver = result;
});
```
This returns an object with information about the Synapse instance, including what port
This returns an object with information about the Homeserver instance, including what port
it was started on and the ID that needs to be passed to shut it down again. It also
returns the registration shared secret (`registrationSecret`) that can be used to
register users via the REST API. The Synapse has been ensured ready to go by awaiting
register users via the REST API. The Homeserver has been ensured ready to go by awaiting
its internal health-check.
Synapse instances should be reasonably cheap to start (you may see the first one take a
Homeserver instances should be reasonably cheap to start (you may see the first one take a
while as it pulls the Docker image), so it's generally expected that tests will start a
Synapse instance for each test suite, i.e. in `before()`, and then tear it down in `after()`.
Homeserver instance for each test suite, i.e. in `before()`, and then tear it down in `after()`.
To later destroy your Synapse you should call `stopSynapse`, passing the SynapseInstance
To later destroy your Homeserver you should call `stopHomeserver`, passing the HomeserverInstance
object you received when starting it.
```javascript
cy.stopSynapse(synapse);
cy.stopHomeserver(homeserver);
```
### Synapse Config Templates
@ -131,10 +131,10 @@ in a template can be referenced in the config as `/data/foo.html`.
There exists a basic utility to start the app with a random user already logged in:
```javascript
cy.initTestUser(synapse, "Jeff");
cy.initTestUser(homeserver, "Jeff");
```
It takes the SynapseInstance you received from `startSynapse` and a display name for your test user.
It takes the HomeserverInstance you received from `startHomeserver` and a display name for your test user.
This custom command will register a random userId using the registrationSecret with a random password
and the given display name. The returned Chainable will contain details about the credentials for if
they are needed for User-Interactive Auth or similar but localStorage will already be seeded with them
@ -147,11 +147,11 @@ but the signature can be maintained for simpler maintenance.
Many tests will also want to start with the client in a room, ready to send & receive messages. Best
way to do this may be to get an access token for the user and use this to create a room with the REST
API before logging the user in. You can make use of `cy.getBot(synapse)` and `cy.getClient()` to do this.
API before logging the user in. You can make use of `cy.getBot(homeserver)` and `cy.getClient()` to do this.
### Convenience APIs
We should probably end up with convenience APIs that wrap the synapse creation, logging in and room
We should probably end up with convenience APIs that wrap the homeserver creation, logging in and room
creation that can be called to set up tests.
### Using matrix-js-sdk

View File

@ -57,7 +57,7 @@
"dependencies": {
"@babel/runtime": "^7.12.5",
"@matrix-org/analytics-events": "^0.3.0",
"@matrix-org/matrix-wysiwyg": "^0.13.0",
"@matrix-org/matrix-wysiwyg": "^0.16.0",
"@matrix-org/react-sdk-module-api": "^0.0.3",
"@sentry/browser": "^7.0.0",
"@sentry/tracing": "^7.0.0",

View File

@ -27,6 +27,11 @@ limitations under the License.
}
.mx_CompoundDialog {
.mx_Dialog {
display: flex;
flex-direction: column;
}
.mx_CompoundDialog_header {
padding: 32px 32px 16px 32px;
@ -49,6 +54,13 @@ limitations under the License.
}
}
.mx_CompoundDialog_form {
display: flex;
flex-direction: column;
min-height: 0;
max-height: 100%;
}
.mx_CompoundDialog_content {
overflow: auto;
padding: 8px 32px;
@ -57,10 +69,6 @@ limitations under the License.
.mx_CompoundDialog_footer {
padding: 20px 32px;
text-align: right;
position: absolute;
bottom: 0;
left: 0;
right: 0;
.mx_AccessibleButton {
margin-left: 24px;
@ -69,14 +77,17 @@ limitations under the License.
}
.mx_ScrollableBaseDialog {
display: flex;
flex-direction: column;
width: 544px; /* fixed */
height: 516px; /* fixed */
.mx_CompoundDialog_content {
height: 349px; /* dialogHeight - header - footer */
}
max-width: 100%;
min-height: 0;
max-height: 80%;
.mx_CompoundDialog_footer {
box-shadow: 0px -4px 4px rgba(0, 0, 0, 0.05); /* hardcoded colour for both themes */
z-index: 1; /* needed to make footer & shadow appear above dialog content */
}
}

View File

@ -970,6 +970,7 @@ $left-gutter: 64px;
font-size: $font-12px;
max-width: var(--MessageTimestamp-max-width);
position: initial;
margin-left: auto; /* to ensure it's end-aligned even if it's the only element of its parent */
}
&:hover {
@ -1297,7 +1298,7 @@ $left-gutter: 64px;
.mx_EventTile_details {
display: flex;
width: -webkit-fill-available;
width: fill-available;
align-items: center;
justify-content: space-between;
gap: $spacing-8;

View File

@ -16,14 +16,32 @@ limitations under the License.
.mx_LinkModal {
padding: $spacing-32;
.mx_Dialog_content {
margin-top: 30px;
margin-bottom: 42px;
}
max-width: 600px;
height: 341px;
box-sizing: border-box;
display: flex;
flex-direction: column;
.mx_LinkModal_content {
display: flex;
flex-direction: column;
flex: 1;
gap: $spacing-8;
margin-top: 7px;
.mx_LinkModal_Field {
flex: initial;
height: 40px;
}
.mx_LinkModal_buttons {
display: flex;
flex: 1;
align-items: flex-end;
.mx_Dialog_buttons {
display: inline-block;
}
}
}
}

View File

@ -4,31 +4,14 @@
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g
clip-path="url(#clip0_1456_146365)"
id="g53">
<path
d="M7.00042 13.7333H13.6671L14.5471 15.8667C14.7471 16.3467 15.2137 16.6667 15.7337 16.6667C16.6537 16.6667 17.2671 15.72 16.9071 14.88L11.7337 2.92C11.4937 2.36 10.9471 2 10.3337 2C9.72042 2 9.17375 2.36 8.93375 2.92L3.76042 14.88C3.40042 15.72 4.02708 16.6667 4.94708 16.6667C5.46708 16.6667 5.93375 16.3467 6.13375 15.8667L7.00042 13.7333ZM10.3337 4.64L12.8271 11.3333H7.84042L10.3337 4.64Z"
fill="#C1C6CD"
id="path49" />
<path
d="m 1.497495,8.96927 c 0,0.793654 0.7402877,1.441437 1.6473569,1.441437 H 17.521786 c 0.907096,0 1.647419,-0.647783 1.647419,-1.441437 0,-0.7936857 -0.740323,-1.4414375 -1.647419,-1.4414375 H 11.127487 3.1448519 c -0.4734211,0 -0.9014103,0.1764504 -1.2024293,0.4580061 C 1.7722258,8.1450309 1.6426187,8.3378225 1.568339,8.5513189 1.522281,8.6837006 1.497495,8.8240421 1.497495,8.96927 Z"
fill="#c1c6cd"
stroke="#ffffff"
id="path51"
style="stroke:none;stroke-width:0.840525;stroke-opacity:1" />
</g>
<defs
id="defs58">
<clipPath
id="clip0_1456_146365">
<rect
width="20"
height="20"
fill="white"
id="rect55" />
</clipPath>
</defs>
xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2168_154906)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.5467 15.8667L13.6667 13.7333H7.00005L6.13338 15.8667C5.93338 16.3467 5.46672 16.6667 4.94672 16.6667C4.02672 16.6667 3.40005 15.72 3.76005 14.88L5.00323 12.0059H15.6635L16.9067 14.88C17.2667 15.72 16.6534 16.6667 15.7334 16.6667C15.2134 16.6667 14.7467 16.3467 14.5467 15.8667ZM13.6435 7.33594L11.7334 2.92C11.4934 2.36 10.9467 2 10.3334 2C9.72005 2 9.17338 2.36 8.93338 2.92L7.02326 7.33594H9.32912L10.3334 4.64L11.3376 7.33594H13.6435Z" fill="currentColor"/>
<path d="M1 9.67708C1 10.4104 1.6 11.0104 2.33333 11.0104H18.3333C19.0667 11.0104 19.6667 10.4104 19.6667 9.67708C19.6667 8.94375 19.0667 8.34375 18.3333 8.34375H2.33333C1.6 8.34375 1 8.94375 1 9.67708Z" fill="currentColor"/>
</g>
<defs>
<clipPath id="clip0_2168_154906">
<rect width="20" height="20" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -218,7 +218,7 @@ class MatrixClientPegClass implements IMatrixClientPeg {
opts.pendingEventOrdering = PendingEventOrdering.Detached;
opts.lazyLoadMembers = true;
opts.clientWellKnownPollPeriod = 2 * 60 * 60; // 2 hours
opts.experimentalThreadSupport = SettingsStore.getValue("feature_threadstable");
opts.experimentalThreadSupport = SettingsStore.getValue("feature_threadenabled");
if (SettingsStore.getValue("feature_sliding_sync")) {
const proxyUrl = SettingsStore.getValue("feature_sliding_sync_proxy_url");

View File

@ -20,15 +20,7 @@ import { logger } from "matrix-js-sdk/src/logger";
import { removeDirectionOverrideChars } from "matrix-js-sdk/src/utils";
import { GuestAccess, HistoryVisibility, JoinRule } from "matrix-js-sdk/src/@types/partials";
import { EventType, MsgType } from "matrix-js-sdk/src/@types/event";
import {
M_EMOTE,
M_NOTICE,
M_MESSAGE,
MessageEvent,
M_POLL_START,
M_POLL_END,
PollStartEvent,
} from "matrix-events-sdk";
import { M_POLL_START, M_POLL_END, PollStartEvent } from "matrix-events-sdk";
import { _t } from "./languageHandler";
import * as Roles from "./Roles";
@ -347,17 +339,6 @@ function textForMessageEvent(ev: MatrixEvent): () => string | null {
message = textForRedactedPollAndMessageEvent(ev);
}
if (SettingsStore.isEnabled("feature_extensible_events")) {
const extev = ev.unstableExtensibleEvent as MessageEvent;
if (extev) {
if (extev.isEquivalentTo(M_EMOTE)) {
return `* ${senderDisplayName} ${extev.text}`;
} else if (extev.isEquivalentTo(M_NOTICE) || extev.isEquivalentTo(M_MESSAGE)) {
return `${senderDisplayName}: ${extev.text}`;
}
}
}
if (ev.getContent().msgtype === MsgType.Emote) {
message = "* " + senderDisplayName + " " + message;
} else if (ev.getContent().msgtype === MsgType.Image) {

View File

@ -15,6 +15,7 @@ limitations under the License.
*/
import { Room } from "matrix-js-sdk/src/models/room";
import { Thread } from "matrix-js-sdk/src/models/thread";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { EventType } from "matrix-js-sdk/src/@types/event";
import { M_BEACON } from "matrix-js-sdk/src/@types/beacon";
@ -59,35 +60,39 @@ export function doesRoomHaveUnreadMessages(room: Room): boolean {
return false;
}
for (const timeline of [room, ...room.getThreads()]) {
// If the current timeline has unread messages, we're done.
if (doesRoomOrThreadHaveUnreadMessages(timeline)) {
return true;
}
}
// If we got here then no timelines were found with unread messages.
return false;
}
export function doesRoomOrThreadHaveUnreadMessages(roomOrThread: Room | Thread): boolean {
// If there are no messages yet in the timeline then it isn't fully initialised
// and cannot be unread.
if (!roomOrThread || roomOrThread.timeline.length === 0) {
return false;
}
const myUserId = MatrixClientPeg.get().getUserId();
// as we don't send RRs for our own messages, make sure we special case that
// if *we* sent the last message into the room, we consider it not unread!
// Should fix: https://github.com/vector-im/element-web/issues/3263
// https://github.com/vector-im/element-web/issues/2427
// ...and possibly some of the others at
// https://github.com/vector-im/element-web/issues/3363
if (roomOrThread.timeline.at(-1)?.getSender() === myUserId) {
return false;
}
// get the most recent read receipt sent by our account.
// N.B. this is NOT a read marker (RM, aka "read up to marker"),
// despite the name of the method :((
const readUpToId = room.getEventReadUpTo(myUserId!);
if (!SettingsStore.getValue("feature_threadstable")) {
// as we don't send RRs for our own messages, make sure we special case that
// if *we* sent the last message into the room, we consider it not unread!
// Should fix: https://github.com/vector-im/element-web/issues/3263
// https://github.com/vector-im/element-web/issues/2427
// ...and possibly some of the others at
// https://github.com/vector-im/element-web/issues/3363
if (room.timeline.length && room.timeline[room.timeline.length - 1].getSender() === myUserId) {
return false;
}
}
// if the read receipt relates to an event is that part of a thread
// we consider that there are no unread messages
// This might be a false negative, but probably the best we can do until
// the read receipts have evolved to cater for threads
if (readUpToId) {
const event = room.findEventById(readUpToId);
if (event?.getThread()) {
return false;
}
}
const readUpToId = roomOrThread.getEventReadUpTo(myUserId!);
// this just looks at whatever history we have, which if we've only just started
// up probably won't be very much, so if the last couple of events are ones that
@ -96,8 +101,8 @@ export function doesRoomHaveUnreadMessages(room: Room): boolean {
// but currently we just guess.
// Loop through messages, starting with the most recent...
for (let i = room.timeline.length - 1; i >= 0; --i) {
const ev = room.timeline[i];
for (let i = roomOrThread.timeline.length - 1; i >= 0; --i) {
const ev = roomOrThread.timeline[i];
if (ev.getId() == readUpToId) {
// If we've read up to this event, there's nothing more recent

View File

@ -287,7 +287,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
// and we check this in a hot code path. This is also cached in our
// RoomContext, however we still need a fallback for roomless MessagePanels.
this._showHiddenEvents = SettingsStore.getValue("showHiddenEventsInTimeline");
this.threadsEnabled = SettingsStore.getValue("feature_threadstable");
this.threadsEnabled = SettingsStore.getValue("feature_threadenabled");
this.showTypingNotificationsWatcherRef = SettingsStore.watchSetting(
"showTypingNotifications",

View File

@ -100,7 +100,7 @@ export const RoomSearchView = forwardRef<ScrollPanel, Props>(
return b.length - a.length;
});
if (SettingsStore.getValue("feature_threadstable")) {
if (SettingsStore.getValue("feature_threadenabled")) {
// Process all thread roots returned in this batch of search results
// XXX: This won't work for results coming from Seshat which won't include the bundled relationship
for (const result of results.results) {

View File

@ -1182,7 +1182,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
CHAT_EFFECTS.forEach((effect) => {
if (containsEmoji(ev.getContent(), effect.emojis) || ev.getContent().msgtype === effect.msgType) {
// For initial threads launch, chat effects are disabled see #19731
if (!SettingsStore.getValue("feature_threadstable") || !ev.isRelation(THREAD_RELATION_TYPE.name)) {
if (!SettingsStore.getValue("feature_threadenabled") || !ev.isRelation(THREAD_RELATION_TYPE.name)) {
dis.dispatch({ action: `effects.${effect.command}` });
}
}

View File

@ -249,7 +249,7 @@ const ThreadPanel: React.FC<IProps> = ({ roomId, onClose, permalinkCreator }) =>
const openFeedback = shouldShowFeedback()
? () => {
Modal.createDialog(BetaFeedbackDialog, {
featureId: "feature_threadstable",
featureId: "feature_threadenabled",
});
}
: null;

View File

@ -1688,7 +1688,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
is very tied to the main room timeline, we are forcing the timeline to
send read receipts for threaded events */
const isThreadTimeline = this.context.timelineRenderingType === TimelineRenderingType.Thread;
if (SettingsStore.getValue("feature_threadstable") && isThreadTimeline) {
if (SettingsStore.getValue("feature_threadenabled") && isThreadTimeline) {
return 0;
}
const index = this.state.events.findIndex((ev) => ev.getId() === evId);

View File

@ -71,7 +71,7 @@ const ReplyInThreadButton = ({ mxEvent, closeMenu }: IReplyInThreadButton) => {
if (Boolean(relationType) && relationType !== RelationType.Thread) return null;
const onClick = (): void => {
if (!SettingsStore.getValue("feature_threadstable")) {
if (!SettingsStore.getValue("feature_threadenabled")) {
dis.dispatch({
action: Action.ViewUserSettings,
initialTabId: UserTab.Labs,
@ -640,7 +640,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
rightClick &&
contentActionable &&
canSendMessages &&
SettingsStore.getValue("feature_threadstable") &&
SettingsStore.getValue("feature_threadenabled") &&
Thread.hasServerSideSupport &&
timelineRenderingType !== TimelineRenderingType.Thread
) {

View File

@ -0,0 +1,36 @@
/*
Copyright 2022 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 { _t } from "../../../languageHandler";
import Modal from "../../../Modal";
import InfoDialog from "./InfoDialog";
export const createCantStartVoiceMessageBroadcastDialog = (): void => {
Modal.createDialog(InfoDialog, {
title: _t("Can't start voice message"),
description: (
<p>
{_t(
"You can't start a voice message as you are currently recording a live broadcast. " +
"Please end your live broadcast in order to start recording a voice message.",
)}
</p>
),
hasCloseButton: true,
});
};

View File

@ -96,7 +96,7 @@ export default abstract class ScrollableBaseModal<
aria-label={_t("Close dialog")}
/>
</div>
<form onSubmit={this.onSubmit}>
<form onSubmit={this.onSubmit} className="mx_CompoundDialog_form">
<div className="mx_CompoundDialog_content">{this.renderContent()}</div>
<div className="mx_CompoundDialog_footer">
<AccessibleButton onClick={this.onCancel} kind="primary_outline">

View File

@ -262,7 +262,7 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
this.inputRef = inputRef || React.createRef();
inputProps.placeholder = inputProps.placeholder || inputProps.label;
inputProps.placeholder = inputProps.placeholder ?? inputProps.label;
inputProps.id = this.id; // this overwrites the id from props
inputProps.onFocus = this.onFocus;

View File

@ -204,7 +204,7 @@ const ReplyInThreadButton = ({ mxEvent }: IReplyInThreadButton) => {
const relationType = mxEvent?.getRelation()?.rel_type;
const hasARelation = !!relationType && relationType !== RelationType.Thread;
const threadsEnabled = SettingsStore.getValue("feature_threadstable");
const threadsEnabled = SettingsStore.getValue("feature_threadenabled");
if (!threadsEnabled && !Thread.hasServerSideSupport) {
// hide the prompt if the user would only have degraded mode
@ -216,7 +216,7 @@ const ReplyInThreadButton = ({ mxEvent }: IReplyInThreadButton) => {
e.preventDefault();
e.stopPropagation();
if (!SettingsStore.getValue("feature_threadstable")) {
if (!SettingsStore.getValue("feature_threadenabled")) {
dis.dispatch({
action: Action.ViewUserSettings,
initialTabId: UserTab.Labs,
@ -252,7 +252,7 @@ const ReplyInThreadButton = ({ mxEvent }: IReplyInThreadButton) => {
</div>
{!hasARelation && (
<div className="mx_Tooltip_sub">
{SettingsStore.getValue("feature_threadstable")
{SettingsStore.getValue("feature_threadenabled")
? _t("Beta feature")
: _t("Beta feature. Click to learn more.")}
</div>
@ -548,7 +548,7 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
);
}
} else if (
SettingsStore.getValue("feature_threadstable") &&
SettingsStore.getValue("feature_threadenabled") &&
// Show thread icon even for deleted messages, but only within main timeline
this.context.timelineRenderingType === TimelineRenderingType.Room &&
this.props.mxEvent.getThread()

View File

@ -18,7 +18,6 @@ import React, { createRef, SyntheticEvent, MouseEvent, ReactNode } from "react";
import ReactDOM from "react-dom";
import highlight from "highlight.js";
import { MsgType } from "matrix-js-sdk/src/@types/event";
import { isEventLike, LegacyMsgType, M_MESSAGE, MessageEvent } from "matrix-events-sdk";
import * as HtmlUtils from "../../../HtmlUtils";
import { formatDate } from "../../../DateUtils";
@ -579,29 +578,6 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
// only strip reply if this is the original replying event, edits thereafter do not have the fallback
const stripReply = !mxEvent.replacingEvent() && !!getParentEventId(mxEvent);
let body: ReactNode;
if (SettingsStore.isEnabled("feature_extensible_events")) {
const extev = this.props.mxEvent.unstableExtensibleEvent as MessageEvent;
if (extev?.isEquivalentTo(M_MESSAGE)) {
isEmote = isEventLike(extev.wireFormat, LegacyMsgType.Emote);
isNotice = isEventLike(extev.wireFormat, LegacyMsgType.Notice);
body = HtmlUtils.bodyToHtml(
{
body: extev.text,
format: extev.html ? "org.matrix.custom.html" : undefined,
formatted_body: extev.html,
msgtype: MsgType.Text,
},
this.props.highlights,
{
disableBigEmoji: isEmote || !SettingsStore.getValue<boolean>("TextualBody.enableBigEmoji"),
// Part of Replies fallback support
stripReplyFallback: stripReply,
ref: this.contentRef,
returnString: false,
},
);
}
}
if (!body) {
isEmote = content.msgtype === MsgType.Emote;
isNotice = content.msgtype === MsgType.Notice;

View File

@ -69,7 +69,7 @@ const BaseCard: React.FC<IProps> = forwardRef<HTMLDivElement, IProps>(
if (onClose) {
closeButton = (
<AccessibleButton
data-test-id="base-card-close-button"
data-testid="base-card-close-button"
className="mx_BaseCard_close"
onClick={onClose}
title={closeLabel || _t("Close")}

View File

@ -21,6 +21,7 @@ limitations under the License.
import React from "react";
import classNames from "classnames";
import { NotificationCountType, Room, RoomEvent } from "matrix-js-sdk/src/models/room";
import { ThreadEvent } from "matrix-js-sdk/src/models/thread";
import { Feature, ServerSupport } from "matrix-js-sdk/src/feature";
import { _t } from "../../../languageHandler";
@ -44,6 +45,7 @@ import { NotificationStateEvents } from "../../../stores/notifications/Notificat
import PosthogTrackers from "../../../PosthogTrackers";
import { ButtonEvent } from "../elements/AccessibleButton";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { doesRoomOrThreadHaveUnreadMessages } from "../../../Unread";
const ROOM_INFO_PHASES = [
RightPanelPhases.RoomSummary,
@ -154,7 +156,17 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
if (!this.supportsThreadNotifications) {
this.threadNotificationState?.on(NotificationStateEvents.Update, this.onNotificationUpdate);
} else {
// Notification badge may change if the notification counts from the
// server change, if a new thread is created or updated, or if a
// receipt is sent in the thread.
this.props.room?.on(RoomEvent.UnreadNotifications, this.onNotificationUpdate);
this.props.room?.on(RoomEvent.Receipt, this.onNotificationUpdate);
this.props.room?.on(RoomEvent.Timeline, this.onNotificationUpdate);
this.props.room?.on(RoomEvent.Redaction, this.onNotificationUpdate);
this.props.room?.on(RoomEvent.LocalEchoUpdated, this.onNotificationUpdate);
this.props.room?.on(RoomEvent.MyMembership, this.onNotificationUpdate);
this.props.room?.on(ThreadEvent.New, this.onNotificationUpdate);
this.props.room?.on(ThreadEvent.Update, this.onNotificationUpdate);
}
this.onNotificationUpdate();
RoomNotificationStateStore.instance.on(UPDATE_STATUS_INDICATOR, this.onUpdateStatus);
@ -166,6 +178,13 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
this.threadNotificationState?.off(NotificationStateEvents.Update, this.onNotificationUpdate);
} else {
this.props.room?.off(RoomEvent.UnreadNotifications, this.onNotificationUpdate);
this.props.room?.off(RoomEvent.Receipt, this.onNotificationUpdate);
this.props.room?.off(RoomEvent.Timeline, this.onNotificationUpdate);
this.props.room?.off(RoomEvent.Redaction, this.onNotificationUpdate);
this.props.room?.off(RoomEvent.LocalEchoUpdated, this.onNotificationUpdate);
this.props.room?.off(RoomEvent.MyMembership, this.onNotificationUpdate);
this.props.room?.off(ThreadEvent.New, this.onNotificationUpdate);
this.props.room?.off(ThreadEvent.Update, this.onNotificationUpdate);
}
RoomNotificationStateStore.instance.off(UPDATE_STATUS_INDICATOR, this.onUpdateStatus);
}
@ -191,9 +210,17 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
return NotificationColor.Red;
case NotificationCountType.Total:
return NotificationColor.Grey;
default:
return NotificationColor.None;
}
// We don't have any notified messages, but we might have unread messages. Let's
// find out.
for (const thread of this.props.room!.getThreads()) {
// If the current thread has unread messages, we're done.
if (doesRoomOrThreadHaveUnreadMessages(thread)) {
return NotificationColor.Bold;
}
}
// Otherwise, no notification color.
return NotificationColor.None;
}
private onUpdateStatus = (notificationState: SummarizedNotificationState): void => {
@ -297,7 +324,7 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
);
rightPanelPhaseButtons.set(
RightPanelPhases.ThreadPanel,
SettingsStore.getValue("feature_threadstable") ? (
SettingsStore.getValue("feature_threadenabled") ? (
<HeaderButton
key={RightPanelPhases.ThreadPanel}
name="threadsButton"

View File

@ -29,6 +29,7 @@ import { EventType } from "matrix-js-sdk/src/@types/event";
import { logger } from "matrix-js-sdk/src/logger";
import { CryptoEvent } from "matrix-js-sdk/src/crypto";
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
import { UserTrustLevel } from "matrix-js-sdk/src/crypto/CrossSigning";
import dis from "../../../dispatcher/dispatcher";
import Modal from "../../../Modal";
@ -84,7 +85,7 @@ export interface IDevice {
getDisplayName(): string;
}
const disambiguateDevices = (devices: IDevice[]) => {
export const disambiguateDevices = (devices: IDevice[]) => {
const names = Object.create(null);
for (let i = 0; i < devices.length; i++) {
const name = devices[i].getDisplayName();
@ -94,7 +95,7 @@ const disambiguateDevices = (devices: IDevice[]) => {
}
for (const name in names) {
if (names[name].length > 1) {
names[name].forEach((j) => {
names[name].forEach((j: number) => {
devices[j].ambiguous = true;
});
}
@ -149,7 +150,7 @@ function useHasCrossSigningKeys(cli: MatrixClient, member: User, canVerify: bool
}, [cli, member, canVerify]);
}
function DeviceItem({ userId, device }: { userId: string; device: IDevice }) {
export function DeviceItem({ userId, device }: { userId: string; device: IDevice }) {
const cli = useContext(MatrixClientContext);
const isMe = userId === cli.getUserId();
const deviceTrust = cli.checkDeviceTrust(userId, device.deviceId);
@ -172,7 +173,10 @@ function DeviceItem({ userId, device }: { userId: string; device: IDevice }) {
});
const onDeviceClick = () => {
verifyDevice(cli.getUser(userId), device);
const user = cli.getUser(userId);
if (user) {
verifyDevice(user, device);
}
};
let deviceName;
@ -315,7 +319,7 @@ const MessageButton = ({ member }: { member: RoomMember }) => {
);
};
const UserOptionsSection: React.FC<{
export const UserOptionsSection: React.FC<{
member: RoomMember;
isIgnored: boolean;
canInvite: boolean;
@ -367,7 +371,8 @@ const UserOptionsSection: React.FC<{
dis.dispatch<ViewRoomPayload>({
action: Action.ViewRoom,
highlighted: true,
event_id: room.getEventReadUpTo(member.userId),
// this could return null, the default prevents a type error
event_id: room?.getEventReadUpTo(member.userId) || undefined,
room_id: member.roomId,
metricsTrigger: undefined, // room doesn't change
});
@ -402,16 +407,18 @@ const UserOptionsSection: React.FC<{
const onInviteUserButton = async (ev: ButtonEvent) => {
try {
// We use a MultiInviter to re-use the invite logic, even though we're only inviting one user.
const inviter = new MultiInviter(roomId);
const inviter = new MultiInviter(roomId || "");
await inviter.invite([member.userId]).then(() => {
if (inviter.getCompletionState(member.userId) !== "invited") {
throw new Error(inviter.getErrorText(member.userId));
}
});
} catch (err) {
const description = err instanceof Error ? err.message : _t("Operation failed");
Modal.createDialog(ErrorDialog, {
title: _t("Failed to invite"),
description: err && err.message ? err.message : _t("Operation failed"),
description,
});
}
@ -432,10 +439,7 @@ const UserOptionsSection: React.FC<{
</AccessibleButton>
);
let directMessageButton: JSX.Element;
if (!isMe) {
directMessageButton = <MessageButton member={member} />;
}
const directMessageButton = isMe ? null : <MessageButton member={member} />;
return (
<div className="mx_UserInfo_container">
@ -499,16 +503,24 @@ interface IPowerLevelsContent {
redact?: number;
}
const isMuted = (member: RoomMember, powerLevelContent: IPowerLevelsContent) => {
export const isMuted = (member: RoomMember, powerLevelContent: IPowerLevelsContent) => {
if (!powerLevelContent || !member) return false;
const levelToSend =
(powerLevelContent.events ? powerLevelContent.events["m.room.message"] : null) ||
powerLevelContent.events_default;
// levelToSend could be undefined as .events_default is optional. Coercing in this case using
// Number() would always return false, so this preserves behaviour
// FIXME: per the spec, if `events_default` is unset, it defaults to zero. If
// the member has a negative powerlevel, this will give an incorrect result.
if (levelToSend === undefined) return false;
return member.powerLevel < levelToSend;
};
const getPowerLevels = (room) => room?.currentState?.getStateEvents(EventType.RoomPowerLevels, "")?.getContent() || {};
export const getPowerLevels = (room: Room): IPowerLevelsContent =>
room?.currentState?.getStateEvents(EventType.RoomPowerLevels, "")?.getContent() || {};
export const useRoomPowerLevels = (cli: MatrixClient, room: Room) => {
const [powerLevels, setPowerLevels] = useState<IPowerLevelsContent>(getPowerLevels(room));
@ -538,7 +550,7 @@ interface IBaseProps {
stopUpdating(): void;
}
const RoomKickButton = ({ room, member, startUpdating, stopUpdating }: Omit<IBaseRoomProps, "powerLevels">) => {
export const RoomKickButton = ({ room, member, startUpdating, stopUpdating }: Omit<IBaseRoomProps, "powerLevels">) => {
const cli = useContext(MatrixClientContext);
// check if user can be kicked/disinvited
@ -566,7 +578,7 @@ const RoomKickButton = ({ room, member, startUpdating, stopUpdating }: Omit<IBas
space: room,
spaceChildFilter: (child: Room) => {
// Return true if the target member is not banned and we have sufficient PL to ban them
const myMember = child.getMember(cli.credentials.userId);
const myMember = child.getMember(cli.credentials.userId || "");
const theirMember = child.getMember(member.userId);
return (
myMember &&
@ -648,7 +660,7 @@ const RedactMessagesButton: React.FC<IBaseProps> = ({ member }) => {
);
};
const BanToggleButton = ({ room, member, startUpdating, stopUpdating }: Omit<IBaseRoomProps, "powerLevels">) => {
export const BanToggleButton = ({ room, member, startUpdating, stopUpdating }: Omit<IBaseRoomProps, "powerLevels">) => {
const cli = useContext(MatrixClientContext);
const isBanned = member.membership === "ban";
@ -674,7 +686,7 @@ const BanToggleButton = ({ room, member, startUpdating, stopUpdating }: Omit<IBa
spaceChildFilter: isBanned
? (child: Room) => {
// Return true if the target member is banned and we have sufficient PL to unban
const myMember = child.getMember(cli.credentials.userId);
const myMember = child.getMember(cli.credentials.userId || "");
const theirMember = child.getMember(member.userId);
return (
myMember &&
@ -686,7 +698,7 @@ const BanToggleButton = ({ room, member, startUpdating, stopUpdating }: Omit<IBa
}
: (child: Room) => {
// Return true if the target member isn't banned and we have sufficient PL to ban
const myMember = child.getMember(cli.credentials.userId);
const myMember = child.getMember(cli.credentials.userId || "");
const theirMember = child.getMember(member.userId);
return (
myMember &&
@ -835,7 +847,7 @@ const MuteToggleButton: React.FC<IBaseRoomProps> = ({ member, room, powerLevels,
);
};
const RoomAdminToolsContainer: React.FC<IBaseRoomProps> = ({
export const RoomAdminToolsContainer: React.FC<IBaseRoomProps> = ({
room,
children,
member,
@ -855,7 +867,7 @@ const RoomAdminToolsContainer: React.FC<IBaseRoomProps> = ({
// if these do not exist in the event then they should default to 50 as per the spec
const { ban: banPowerLevel = 50, kick: kickPowerLevel = 50, redact: redactPowerLevel = 50 } = powerLevels;
const me = room.getMember(cli.getUserId());
const me = room.getMember(cli.getUserId() || "");
if (!me) {
// we aren't in the room, so return no admin tooling
return <div />;
@ -879,7 +891,7 @@ const RoomAdminToolsContainer: React.FC<IBaseRoomProps> = ({
<BanToggleButton room={room} member={member} startUpdating={startUpdating} stopUpdating={stopUpdating} />
);
}
if (!isMe && canAffectUser && me.powerLevel >= editPowerLevel && !room.isSpaceRoom()) {
if (!isMe && canAffectUser && me.powerLevel >= Number(editPowerLevel) && !room.isSpaceRoom()) {
muteButton = (
<MuteToggleButton
member={member}
@ -949,7 +961,7 @@ function useRoomPermissions(cli: MatrixClient, room: Room, user: RoomMember): IR
const powerLevels = room?.currentState.getStateEvents(EventType.RoomPowerLevels, "")?.getContent();
if (!powerLevels) return;
const me = room.getMember(cli.getUserId());
const me = room.getMember(cli.getUserId() || "");
if (!me) return;
const them = user;
@ -1006,7 +1018,7 @@ const PowerLevelSection: React.FC<{
}
};
const PowerLevelEditor: React.FC<{
export const PowerLevelEditor: React.FC<{
user: RoomMember;
room: Room;
roomPermissions: IRoomPermissions;
@ -1022,8 +1034,13 @@ const PowerLevelEditor: React.FC<{
async (powerLevel: number) => {
setSelectedPowerLevel(powerLevel);
const applyPowerChange = (roomId, target, powerLevel, powerLevelEvent) => {
return cli.setPowerLevel(roomId, target, parseInt(powerLevel), powerLevelEvent).then(
const applyPowerChange = (
roomId: string,
target: string,
powerLevel: number,
powerLevelEvent: MatrixEvent,
) => {
return cli.setPowerLevel(roomId, target, powerLevel, powerLevelEvent).then(
function () {
// NO-OP; rely on the m.room.member event coming down else we could
// get out of sync if we force setState here!
@ -1046,7 +1063,7 @@ const PowerLevelEditor: React.FC<{
if (!powerLevelEvent) return;
const myUserId = cli.getUserId();
const myPower = powerLevelEvent.getContent().users[myUserId];
const myPower = powerLevelEvent.getContent().users[myUserId || ""];
if (myPower && parseInt(myPower) <= powerLevel && myUserId !== target) {
const { finished } = Modal.createDialog(QuestionDialog, {
title: _t("Warning!"),
@ -1085,7 +1102,7 @@ const PowerLevelEditor: React.FC<{
return (
<div className="mx_UserInfo_profileField">
<PowerSelector
label={null}
label={undefined}
value={selectedPowerLevel}
maxValue={roomPermissions.modifyLevelMax}
usersDefault={powerLevelUsersDefault}
@ -1099,7 +1116,7 @@ export const useDevices = (userId: string) => {
const cli = useContext(MatrixClientContext);
// undefined means yet to be loaded, null means failed to load, otherwise list of devices
const [devices, setDevices] = useState(undefined);
const [devices, setDevices] = useState<undefined | null | IDevice[]>(undefined);
// Download device lists
useEffect(() => {
setDevices(undefined);
@ -1116,8 +1133,8 @@ export const useDevices = (userId: string) => {
return;
}
disambiguateDevices(devices);
setDevices(devices);
disambiguateDevices(devices as IDevice[]);
setDevices(devices as IDevice[]);
} catch (err) {
setDevices(null);
}
@ -1136,17 +1153,17 @@ export const useDevices = (userId: string) => {
const updateDevices = async () => {
const newDevices = cli.getStoredDevicesForUser(userId);
if (cancel) return;
setDevices(newDevices);
setDevices(newDevices as IDevice[]);
};
const onDevicesUpdated = (users) => {
const onDevicesUpdated = (users: string[]) => {
if (!users.includes(userId)) return;
updateDevices();
};
const onDeviceVerificationChanged = (_userId, device) => {
const onDeviceVerificationChanged = (_userId: string, deviceId: string) => {
if (_userId !== userId) return;
updateDevices();
};
const onUserTrustStatusChanged = (_userId, trustStatus) => {
const onUserTrustStatusChanged = (_userId: string, trustLevel: UserTrustLevel) => {
if (_userId !== userId) return;
updateDevices();
};
@ -1229,9 +1246,11 @@ const BasicUserInfo: React.FC<{
logger.error("Failed to deactivate user");
logger.error(err);
const description = err instanceof Error ? err.message : _t("Operation failed");
Modal.createDialog(ErrorDialog, {
title: _t("Failed to deactivate user"),
description: err && err.message ? err.message : _t("Operation failed"),
description,
});
}
}, [cli, member.userId]);
@ -1317,12 +1336,12 @@ const BasicUserInfo: React.FC<{
const homeserverSupportsCrossSigning = useHomeserverSupportsCrossSigning(cli);
const userTrust = cryptoEnabled && cli.checkUserTrust(member.userId);
const userVerified = cryptoEnabled && userTrust.isCrossSigningVerified();
const userVerified = cryptoEnabled && userTrust && userTrust.isCrossSigningVerified();
const isMe = member.userId === cli.getUserId();
const canVerify =
cryptoEnabled && homeserverSupportsCrossSigning && !userVerified && !isMe && devices && devices.length > 0;
const setUpdating = (updating) => {
const setUpdating: SetUpdating = (updating) => {
setPendingUpdateCount((count) => count + (updating ? 1 : -1));
};
const hasCrossSigningKeys = useHasCrossSigningKeys(cli, member as User, canVerify, setUpdating);
@ -1408,9 +1427,9 @@ const BasicUserInfo: React.FC<{
export type Member = User | RoomMember;
const UserInfoHeader: React.FC<{
export const UserInfoHeader: React.FC<{
member: Member;
e2eStatus: E2EStatus;
e2eStatus?: E2EStatus;
roomId?: string;
}> = ({ member, e2eStatus, roomId }) => {
const cli = useContext(MatrixClientContext);
@ -1427,9 +1446,11 @@ const UserInfoHeader: React.FC<{
name: (member as RoomMember).name || (member as User).displayName,
};
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox", null, true);
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox", undefined, true);
}, [member]);
const avatarUrl = (member as User).avatarUrl;
const avatarElement = (
<div className="mx_UserInfo_avatar">
<div className="mx_UserInfo_avatar_transition">
@ -1442,7 +1463,7 @@ const UserInfoHeader: React.FC<{
resizeMethod="scale"
fallbackUserId={member.userId}
onClick={onMemberAvatarClick}
urls={(member as User).avatarUrl ? [(member as User).avatarUrl] : undefined}
urls={avatarUrl ? [avatarUrl] : undefined}
/>
</div>
</div>
@ -1475,10 +1496,7 @@ const UserInfoHeader: React.FC<{
);
}
let e2eIcon;
if (e2eStatus) {
e2eIcon = <E2EIcon size={18} status={e2eStatus} isUser={true} />;
}
const e2eIcon = e2eStatus ? <E2EIcon size={18} status={e2eStatus} isUser={true} /> : null;
const displayName = (member as RoomMember).rawDisplayName;
return (
@ -1496,7 +1514,7 @@ const UserInfoHeader: React.FC<{
</h2>
</div>
<div className="mx_UserInfo_profile_mxid">
{UserIdentifierCustomisations.getDisplayUserIdentifier(member.userId, {
{UserIdentifierCustomisations.getDisplayUserIdentifier?.(member.userId, {
roomId,
withDisplayName: true,
})}
@ -1533,7 +1551,7 @@ const UserInfo: React.FC<IProps> = ({ user, room, onClose, phase = RightPanelPha
const classes = ["mx_UserInfo"];
let cardState: IRightPanelCardState;
let cardState: IRightPanelCardState = {};
// We have no previousPhase for when viewing a UserInfo without a Room at this time
if (room && phase === RightPanelPhases.EncryptionPanel) {
cardState = { member };
@ -1551,10 +1569,10 @@ const UserInfo: React.FC<IProps> = ({ user, room, onClose, phase = RightPanelPha
case RightPanelPhases.SpaceMemberInfo:
content = (
<BasicUserInfo
room={room}
room={room as Room}
member={member as User}
devices={devices}
isRoomEncrypted={isRoomEncrypted}
devices={devices as IDevice[]}
isRoomEncrypted={Boolean(isRoomEncrypted)}
/>
);
break;
@ -1565,7 +1583,7 @@ const UserInfo: React.FC<IProps> = ({ user, room, onClose, phase = RightPanelPha
{...(props as React.ComponentProps<typeof EncryptionPanel>)}
member={member as User | RoomMember}
onClose={onEncryptionPanelClose}
isRoomEncrypted={isRoomEncrypted}
isRoomEncrypted={Boolean(isRoomEncrypted)}
/>
);
break;
@ -1582,7 +1600,7 @@ const UserInfo: React.FC<IProps> = ({ user, room, onClose, phase = RightPanelPha
let scopeHeader;
if (room?.isSpaceRoom()) {
scopeHeader = (
<div data-test-id="space-header" className="mx_RightPanel_scopeHeader">
<div data-testid="space-header" className="mx_RightPanel_scopeHeader">
<RoomAvatar room={room} height={32} width={32} />
<RoomName room={room} />
</div>

View File

@ -36,6 +36,7 @@ import { Layout } from "../../../settings/enums/Layout";
import { formatTime } from "../../../DateUtils";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import DecryptionFailureBody from "../messages/DecryptionFailureBody";
import { E2EState } from "./E2EIcon";
import RoomAvatar from "../avatars/RoomAvatar";
import MessageContextMenu from "../context_menus/MessageContextMenu";
@ -386,7 +387,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
}
}
if (SettingsStore.getValue("feature_threadstable")) {
if (SettingsStore.getValue("feature_threadenabled")) {
this.props.mxEvent.on(ThreadEvent.Update, this.updateThread);
if (this.thread && !this.supportsThreadNotifications) {
@ -469,7 +470,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
if (this.props.showReactions) {
this.props.mxEvent.removeListener(MatrixEventEvent.RelationsCreated, this.onReactionsCreated);
}
if (SettingsStore.getValue("feature_threadstable")) {
if (SettingsStore.getValue("feature_threadenabled")) {
this.props.mxEvent.off(ThreadEvent.Update, this.updateThread);
}
this.threadState?.off(NotificationStateEvents.Update, this.onThreadStateUpdate);
@ -500,7 +501,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
};
private get thread(): Thread | null {
if (!SettingsStore.getValue("feature_threadstable")) {
if (!SettingsStore.getValue("feature_threadenabled")) {
return null;
}
@ -1329,6 +1330,8 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
<div className="mx_EventTile_body">
{this.props.mxEvent.isRedacted() ? (
<RedactedBody mxEvent={this.props.mxEvent} />
) : this.props.mxEvent.isDecryptionFailure() ? (
<DecryptionFailureBody />
) : (
MessagePreviewStore.instance.generatePreviewForEvent(this.props.mxEvent)
)}

View File

@ -58,6 +58,8 @@ import { SendWysiwygComposer, sendMessage, getConversionFunctions } from "./wysi
import { MatrixClientProps, withMatrixClientHOC } from "../../../contexts/MatrixClientContext";
import { setUpVoiceBroadcastPreRecording } from "../../../voice-broadcast/utils/setUpVoiceBroadcastPreRecording";
import { SdkContextClass } from "../../../contexts/SDKContext";
import { VoiceBroadcastInfoState } from "../../../voice-broadcast";
import { createCantStartVoiceMessageBroadcastDialog } from "../dialogs/CantStartVoiceMessageBroadcastDialog";
let instanceCount = 0;
@ -445,6 +447,20 @@ export class MessageComposer extends React.Component<IProps, IState> {
}
}
private onRecordStartEndClick = (): void => {
const currentBroadcastRecording = SdkContextClass.instance.voiceBroadcastRecordingsStore.getCurrent();
if (currentBroadcastRecording && currentBroadcastRecording.getState() !== VoiceBroadcastInfoState.Stopped) {
createCantStartVoiceMessageBroadcastDialog();
} else {
this.voiceRecordingButton.current?.onRecordStartEndClick();
}
if (this.context.narrow) {
this.toggleButtonMenu();
}
};
public render() {
const hasE2EIcon = Boolean(!this.state.isWysiwygLabEnabled && this.props.e2eStatus);
const e2eIcon = hasE2EIcon && (
@ -588,12 +604,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
isStickerPickerOpen={this.state.isStickerPickerOpen}
menuPosition={menuPosition}
relation={this.props.relation}
onRecordStartEndClick={() => {
this.voiceRecordingButton.current?.onRecordStartEndClick();
if (this.context.narrow) {
this.toggleButtonMenu();
}
}}
onRecordStartEndClick={this.onRecordStartEndClick}
setStickerPickerOpen={this.setStickerPickerOpen}
showLocationButton={!window.electron}
showPollsButton={this.state.showPollsButton}

View File

@ -376,8 +376,8 @@ function ComposerModeButton({ isRichTextEnabled, onClick }: WysiwygToggleButtonP
<CollapsibleButton
className="mx_MessageComposer_button"
iconClassName={classNames({
mx_MessageComposer_plain_text: isRichTextEnabled,
mx_MessageComposer_rich_text: !isRichTextEnabled,
mx_MessageComposer_plain_text: !isRichTextEnabled,
mx_MessageComposer_rich_text: isRichTextEnabled,
})}
onClick={onClick}
title={title}

View File

@ -498,9 +498,10 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
}
const myUserId = MatrixClientPeg.get().getUserId();
const memberEventContent = this.props.room.currentState.getMember(myUserId).events.member.getContent();
const member = this.props.room?.currentState.getMember(myUserId);
const memberEventContent = member?.events.member?.getContent();
if (memberEventContent.reason) {
if (memberEventContent?.reason) {
reasonElement = (
<InviteReason
reason={memberEventContent.reason}

View File

@ -68,7 +68,7 @@ export default class SearchResultTile extends React.Component<IProps> {
const layout = SettingsStore.getValue("layout");
const isTwelveHour = SettingsStore.getValue("showTwelveHourTimestamps");
const alwaysShowTimestamps = SettingsStore.getValue("alwaysShowTimestamps");
const threadsEnabled = SettingsStore.getValue("feature_threadstable");
const threadsEnabled = SettingsStore.getValue("feature_threadenabled");
for (let j = 0; j < timeline.length; j++) {
const mxEv = timeline[j];

View File

@ -436,7 +436,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
// For initial threads launch, chat effects are disabled
// see #19731
const isNotThread = this.props.relation?.rel_type !== THREAD_RELATION_TYPE.name;
if (!SettingsStore.getValue("feature_threadstable") || isNotThread) {
if (!SettingsStore.getValue("feature_threadenabled") || isNotThread) {
dis.dispatch({ action: `effects.${effect.command}` });
}
}

View File

@ -109,9 +109,19 @@ export const ThreadMessagePreview = ({ thread, showDisplayname = false }: IPrevi
{showDisplayname && (
<div className="mx_ThreadSummary_sender">{lastReply.sender?.name ?? lastReply.getSender()}</div>
)}
<div className="mx_ThreadSummary_content" title={preview}>
<span className="mx_ThreadSummary_message-preview">{preview}</span>
</div>
{lastReply.isDecryptionFailure() ? (
<div
className="mx_ThreadSummary_content mx_DecryptionFailureBody"
title={_t("Unable to decrypt message")}
>
<span className="mx_ThreadSummary_message-preview">{_t("Unable to decrypt message")}</span>
</div>
) : (
<div className="mx_ThreadSummary_content" title={preview}>
<span className="mx_ThreadSummary_message-preview">{preview}</span>
</div>
)}
</>
);
};

View File

@ -120,7 +120,7 @@ export function FormattingButtons({ composer, actionStates }: FormattingButtonsP
<Button
isActive={actionStates.link === "reversed"}
label={_td("Link")}
onClick={() => openLinkModal(composer, composerContext)}
onClick={() => openLinkModal(composer, composerContext, actionStates.link === "reversed")}
icon={<LinkIcon className="mx_FormattingButtons_Icon" />}
/>
</div>

View File

@ -17,17 +17,28 @@ limitations under the License.
import { FormattingFunctions } from "@matrix-org/matrix-wysiwyg";
import React, { ChangeEvent, useState } from "react";
import { _td } from "../../../../../languageHandler";
import { _t } from "../../../../../languageHandler";
import Modal from "../../../../../Modal";
import QuestionDialog from "../../../dialogs/QuestionDialog";
import Field from "../../../elements/Field";
import { ComposerContextState } from "../ComposerContext";
import { isSelectionEmpty, setSelection } from "../utils/selection";
import BaseDialog from "../../../dialogs/BaseDialog";
import DialogButtons from "../../../elements/DialogButtons";
export function openLinkModal(composer: FormattingFunctions, composerContext: ComposerContextState) {
export function openLinkModal(
composer: FormattingFunctions,
composerContext: ComposerContextState,
isEditing: boolean,
) {
const modal = Modal.createDialog(
LinkModal,
{ composerContext, composer, onClose: () => modal.close(), isTextEnabled: isSelectionEmpty() },
{
composerContext,
composer,
onClose: () => modal.close(),
isTextEnabled: isSelectionEmpty(),
isEditing,
},
"mx_CompoundDialog",
false,
true,
@ -43,48 +54,86 @@ interface LinkModalProps {
isTextEnabled: boolean;
onClose: () => void;
composerContext: ComposerContextState;
isEditing: boolean;
}
export function LinkModal({ composer, isTextEnabled, onClose, composerContext }: LinkModalProps) {
const [fields, setFields] = useState({ text: "", link: "" });
const isSaveDisabled = (isTextEnabled && isEmpty(fields.text)) || isEmpty(fields.link);
export function LinkModal({ composer, isTextEnabled, onClose, composerContext, isEditing }: LinkModalProps) {
const [hasLinkChanged, setHasLinkChanged] = useState(false);
const [fields, setFields] = useState({ text: "", link: isEditing ? composer.getLink() : "" });
const hasText = !isEditing && isTextEnabled;
const isSaveDisabled = !hasLinkChanged || (hasText && isEmpty(fields.text)) || isEmpty(fields.link);
return (
<QuestionDialog
<BaseDialog
className="mx_LinkModal"
title={_td("Create a link")}
button={_td("Save")}
buttonDisabled={isSaveDisabled}
hasCancelButton={true}
onFinished={async (isClickOnSave: boolean) => {
if (isClickOnSave) {
title={isEditing ? _t("Edit link") : _t("Create a link")}
hasCancel={true}
onFinished={onClose}
>
<form
className="mx_LinkModal_content"
onSubmit={async (evt) => {
evt.preventDefault();
evt.stopPropagation();
onClose();
// When submitting is done when pressing enter when the link field has the focus,
// The link field is getting back the focus (due to react-focus-lock)
// So we are waiting that the focus stuff is done to play with the composer selection
await new Promise((resolve) => setTimeout(resolve, 0));
await setSelection(composerContext.selection);
composer.link(fields.link, isTextEnabled ? fields.text : undefined);
}
onClose();
}}
description={
<div className="mx_LinkModal_content">
{isTextEnabled && (
<Field
autoFocus={true}
label={_td("Text")}
value={fields.text}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
setFields((fields) => ({ ...fields, text: e.target.value }))
}
/>
)}
}}
>
{hasText && (
<Field
autoFocus={!isTextEnabled}
label={_td("Link")}
value={fields.link}
required={true}
autoFocus={true}
label={_t("Text")}
value={fields.text}
className="mx_LinkModal_Field"
placeholder=""
onChange={(e: ChangeEvent<HTMLInputElement>) =>
setFields((fields) => ({ ...fields, link: e.target.value }))
setFields((fields) => ({ ...fields, text: e.target.value }))
}
/>
)}
<Field
required={true}
autoFocus={!hasText}
label={_t("Link")}
value={fields.link}
className="mx_LinkModal_Field"
placeholder=""
onChange={(e: ChangeEvent<HTMLInputElement>) => {
setFields((fields) => ({ ...fields, link: e.target.value }));
setHasLinkChanged(true);
}}
/>
<div className="mx_LinkModal_buttons">
{isEditing && (
<button
type="button"
className="danger"
onClick={() => {
composer.removeLinks();
onClose();
}}
>
{_t("Remove")}
</button>
)}
<DialogButtons
primaryButton={_t("Save")}
primaryDisabled={isSaveDisabled}
primaryIsSubmit={true}
onCancel={onClose}
/>
</div>
}
/>
</form>
</BaseDialog>
);
}

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { WysiwygInputEvent } from "@matrix-org/matrix-wysiwyg";
import { WysiwygEvent } from "@matrix-org/matrix-wysiwyg";
import { useCallback } from "react";
import { useSettingValue } from "../../../../../hooks/useSettings";
@ -22,12 +22,20 @@ import { useSettingValue } from "../../../../../hooks/useSettings";
export function useInputEventProcessor(onSend: () => void) {
const isCtrlEnter = useSettingValue<boolean>("MessageComposerInput.ctrlEnterToSend");
return useCallback(
(event: WysiwygInputEvent) => {
(event: WysiwygEvent) => {
if (event instanceof ClipboardEvent) {
return event;
}
if ((event.inputType === "insertParagraph" && !isCtrlEnter) || event.inputType === "sendMessage") {
const isKeyboardEvent = event instanceof KeyboardEvent;
const isEnterPress =
!isCtrlEnter && (isKeyboardEvent ? event.key === "Enter" : event.inputType === "insertParagraph");
// sendMessage is sent when ctrl+enter is pressed
const isSendMessage = !isKeyboardEvent && event.inputType === "sendMessage";
if (isEnterPress || isSendMessage) {
event.stopPropagation?.();
event.preventDefault?.();
onSend();
return null;
}

View File

@ -112,7 +112,7 @@ export async function sendMessage(
// For initial threads launch, chat effects are disabled
// see #19731
const isNotThread = relation?.rel_type !== THREAD_RELATION_TYPE.name;
if (!SettingsStore.getValue("feature_threadstable") || isNotThread) {
if (!SettingsStore.getValue("feature_threadenabled") || isNotThread) {
dis.dispatch({ action: `effects.${effect.command}` });
}
}

View File

@ -15,12 +15,13 @@ limitations under the License.
*/
import { NotificationCount, NotificationCountType, Room, RoomEvent } from "matrix-js-sdk/src/models/room";
import { Thread } from "matrix-js-sdk/src/models/thread";
import { useCallback, useEffect, useState } from "react";
import { getUnsentMessages } from "../components/structures/RoomStatusBar";
import { getRoomNotifsState, getUnreadNotificationCount, RoomNotifState } from "../RoomNotifs";
import { NotificationColor } from "../stores/notifications/NotificationColor";
import { doesRoomHaveUnreadMessages } from "../Unread";
import { doesRoomOrThreadHaveUnreadMessages } from "../Unread";
import { EffectiveMembership, getEffectiveMembership } from "../utils/membership";
import { useEventEmitter } from "./useEventEmitter";
@ -75,12 +76,14 @@ export const useUnreadNotifications = (
setColor(NotificationColor.Red);
} else if (greyNotifs > 0) {
setColor(NotificationColor.Grey);
} else if (!threadId) {
// TODO: No support for `Bold` on threads at the moment
} else {
// We don't have any notified messages, but we might have unread messages. Let's
// find out.
const hasUnread = doesRoomHaveUnreadMessages(room);
let roomOrThread: Room | Thread = room;
if (threadId) {
roomOrThread = room.getThread(threadId)!;
}
const hasUnread = doesRoomOrThreadHaveUnreadMessages(roomOrThread);
setColor(hasUnread ? NotificationColor.Bold : NotificationColor.None);
}
}

View File

@ -683,7 +683,6 @@
"Show message in desktop notification": "إظهار الرسالة في إشعارات سطح المكتب",
"Enable desktop notifications for this session": "تمكين إشعارات سطح المكتب لهذا الاتصال",
"Notification targets": "أهداف الإشعار",
"Clear notifications": "محو الإشعارات",
"You've successfully verified your device!": "لقد نجحت في التحقق من جهازك!",
"Verify all users in a room to ensure it's secure.": "تحقق من جميع المستخدمين في الغرفة للتأكد من أنها آمنة.",
"Yes": "نعم",
@ -849,8 +848,6 @@
"Show previews/thumbnails for images": "إظهار المعاينات / الصور المصغرة للصور",
"Show hidden events in timeline": "إظهار الأحداث المخفية في الجدول الزمني",
"Show shortcuts to recently viewed rooms above the room list": "إظهار اختصارات للغرف التي تم عرضها مؤخرًا أعلى قائمة الغرف",
"Show rooms with unread notifications first": "اعرض الغرف ذات الإشعارات غير المقروءة أولاً",
"Order rooms by name": "ترتيب الغرف بالاسم",
"Prompt before sending invites to potentially invalid matrix IDs": "أعلمني قبل إرسال دعوات لمعرِّفات قد لا تكون صحيحة",
"Enable URL previews by default for participants in this room": "تمكين معاينة الروابط أصلاً لأي مشارك في هذه الغرفة",
"Enable URL previews for this room (only affects you)": "تمكين معاينة الروابط لهذه الغرفة (يؤثر عليك فقط)",
@ -901,12 +898,6 @@
"Update %(brand)s": "حدّث: %(brand)s",
"New login. Was this you?": "تسجيل دخول جديد. هل كان ذاك أنت؟",
"Other users may not trust it": "قد لا يثق به المستخدمون الآخرون",
"This message cannot be decrypted": "لا يمكن فك تشفير هذه الرسالة",
"<requestLink>Re-request encryption keys</requestLink> from your other sessions.": "<requestLink> إعادة طلب مفاتيح التشفير </requestLink> من اتصالاتك الأخرى.",
"Key request sent.": "تم إرسال طلب المفتاح.",
"If your other sessions do not have the key for this message you will not be able to decrypt them.": "إذا كانت اتصالاتك الأخرى لا تحتوي على مفتاح هذه الرسالة ، فلن تتمكن من فك تشفيرها.",
"Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.": "يتم إرسال طلبات مشاركة المفاتيح إلى اتصالاتك الأخرى تلقائيًا. إذا رفضت أو ألغيت طلب مشاركة المفتاح في اتصالاتك الأخرى ، فانقر هنا لطلب مفاتيح هذا الاتصال مرة أخرى.",
"Your key share request has been sent - please check your other sessions for key share requests.": "تم إرسال طلبك لمشاركة المفتاح - يرجى التحقق من اتصالاتك الأخرى في طلبات مشاركة المفتاح.",
"This event could not be displayed": "تعذر عرض هذا الحدث",
"Mod": "مشرف",
"Edit message": "تعديل الرسالة",

View File

@ -402,7 +402,6 @@
"The exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure. To help with this, you should enter a passphrase below, which will be used to encrypt the exported data. It will only be possible to import the data by using the same passphrase.": "Експортираният файл ще позволи на всеки, който може да го прочете, да разшифрова всяко шифровано съобщение, което можете да видите. Трябва да го държите на сигурно място. За да направите това, трябва да въведете парола по-долу, която ще се използва за шифроване на експортираните данни. Ще бъде възможно да се импортират данните само с използване на същата парола.",
"This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.": "Този процес позволява да импортирате ключове за шифроване, които преди сте експортирали от друг Matrix клиент. Тогава ще можете да разшифровате всяко съобщение, което другият клиент може да разшифрова.",
"The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "Експортираният файл може да бъде предпазен с парола. Трябва да въведете парола тук, за да разшифровате файла.",
"Key request sent.": "Заявката за ключ е изпратена.",
"Code": "Код",
"Submit debug logs": "Изпрати логове за дебъгване",
"Opens the Developer Tools dialog": "Отваря прозорец с инструменти на разработчика",
@ -1120,7 +1119,6 @@
"Connecting to integration manager...": "Свързане с мениджъра на интеграции...",
"Cannot connect to integration manager": "Неуспешна връзка с мениджъра на интеграции",
"The integration manager is offline or it cannot reach your homeserver.": "Мениджъра на интеграции е офлайн или не може да се свърже със сървъра ви.",
"Clear notifications": "Изчисти уведомленията",
"Error upgrading room": "Грешка при обновяване на стаята",
"Double check that your server supports the room version chosen and try again.": "Проверете дали сървъра поддържа тази версия на стаята и опитайте пак.",
"%(senderName)s removed the rule banning users matching %(glob)s": "%(senderName)s премахна правилото блокиращо достъпа на потребители отговарящи на %(glob)s",
@ -1180,7 +1178,6 @@
"If this isn't what you want, please use a different tool to ignore users.": "Ако това не е каквото искате, използвайте друг инструмент за игнориране на потребители.",
"Subscribe": "Абонирай ме",
"Cross-signing": "Кръстосано-подписване",
"This message cannot be decrypted": "Съобщението не може да бъде дешифровано",
"Unencrypted": "Нешифровано",
"Close preview": "Затвори прегледа",
"<userName/> wants to chat": "<userName/> иска да чати",
@ -1285,8 +1282,6 @@
"Show typing notifications": "Показвай уведомления за писане",
"Never send encrypted messages to unverified sessions from this session": "Никога не изпращай шифровани съобщения към непотвърдени сесии от тази сесия",
"Never send encrypted messages to unverified sessions in this room from this session": "Никога не изпращай шифровани съобщения към непотвърдени сесии в тази стая от тази сесия",
"Order rooms by name": "Подреждай стаите по име",
"Show rooms with unread notifications first": "Показвай първи стаите с непрочетени уведомления",
"Show shortcuts to recently viewed rooms above the room list": "Показвай преки пътища до скоро-прегледаните стаи над списъка със стаи",
"Enable message search in encrypted rooms": "Включи търсенето на съобщения в шифровани стаи",
"How fast should messages be downloaded.": "Колко бързо да се изтеглят съобщенията.",
@ -1363,10 +1358,6 @@
"This room is end-to-end encrypted": "Тази стая е шифрована от-край-до-край",
"Everyone in this room is verified": "Всички в тази стая са верифицирани",
"Mod": "Модератор",
"Your key share request has been sent - please check your other sessions for key share requests.": "Заявката ви за споделяне на ключ е изпратена - проверете останалите си сесии за заявки за споделяне на ключове.",
"Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.": "Заявките за споделяне на ключове се изпращат до другите ви сесии автоматично. Ако сте отказали заявката от другите ви сесии, кликнете тук за да изпратите заявка за тази сесия отново.",
"If your other sessions do not have the key for this message you will not be able to decrypt them.": "Ако другите ви сесии нямат ключа за това съобщения, няма да можете да ги дешифровате.",
"<requestLink>Re-request encryption keys</requestLink> from your other sessions.": "<requestLink>Поискай отново ключове за шифроване</requestLink> от другите сесии.",
"Encrypted by an unverified session": "Шифровано от неверифицирана сесия",
"Encrypted by a deleted session": "Шифровано от изтрита сесия",
"Scroll to most recent messages": "Отиди до най-скорошните съобщения",

View File

@ -457,7 +457,6 @@
"Send analytics data": "Odesílat analytická data",
"Enable widget screenshots on supported widgets": "Povolit screenshot widgetu pro podporované widgety",
"This event could not be displayed": "Tato událost nemohla být zobrazena",
"Key request sent.": "Žádost o klíč poslána.",
"Demote yourself?": "Snížit Vaši vlastní hodnost?",
"You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.": "Tuto změnu nebudete moci vzít zpět, protože snižujete svoji vlastní hodnost, jste-li poslední privilegovaný uživatel v místnosti, bude nemožné vaši současnou hodnost získat zpět.",
"Demote": "Degradovat",
@ -1137,7 +1136,6 @@
"Connecting to integration manager...": "Připojování se ke správci integrací...",
"Cannot connect to integration manager": "Nepovedlo se připojení ke správci integrací",
"The integration manager is offline or it cannot reach your homeserver.": "Správce integrací neběží nebo se nemůže připojit k vašemu domovskému serveru.",
"Clear notifications": "Odstranit oznámení",
"Manage integrations": "Správa integrací",
"Ignored/Blocked": "Ignorováno/Blokováno",
"Error adding ignored user/server": "Chyba při přidávání ignorovaného uživatele/serveru",
@ -1164,7 +1162,6 @@
"eg: @bot:* or example.org": "např.: @bot:* nebo example.org",
"Subscribed lists": "Odebírané seznamy",
"Subscribe": "Odebírat",
"This message cannot be decrypted": "Zprávu nelze dešifrovat",
"Unencrypted": "Nezašifrované",
"<userName/> wants to chat": "<userName/> si chce psát",
"Start chatting": "Zahájit konverzaci",
@ -1291,10 +1288,6 @@
"This room is end-to-end encrypted": "Místnost je koncově šifrovaná",
"Everyone in this room is verified": "V této místnosti jsou všichni ověřeni",
"Mod": "Moderátor",
"Your key share request has been sent - please check your other sessions for key share requests.": "Požadavek na sdílení klíčů byl odeslán - podívejte se prosím na své ostatní relace, jestli vám přišel.",
"Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.": "Požadavky na sdílení klíčů jsou vašim ostatním relacím odesílány automaticky. Pokud jste nějaký zamítli nebo ignorovali, tímto tlačítkem si ho můžete poslat znovu.",
"If your other sessions do not have the key for this message you will not be able to decrypt them.": "Pokud vaše ostatní relace nemají klíč pro tuto zprávu, nebudete mít možnost jí dešifrovat.",
"<requestLink>Re-request encryption keys</requestLink> from your other sessions.": "<requestLink>Znovu zažádat o šifrovací klíče</requestLink> z vašich ostatních relací.",
"Encrypted by an unverified session": "Šifrované neověřenou relací",
"Encrypted by a deleted session": "Šifrované smazanou relací",
"Send a reply…": "Odpovědět…",
@ -1387,8 +1380,6 @@
"Sign In or Create Account": "Přihlásit nebo vytvořit nový účet",
"Use your account or create a new one to continue.": "Pro pokračování se přihlaste stávajícím účtem, nebo si vytvořte nový.",
"Create Account": "Vytvořit účet",
"Order rooms by name": "Seřadit místnosti podle názvu",
"Show rooms with unread notifications first": "Zobrazovat místnosti s nepřečtenými oznámeními navrchu",
"Show shortcuts to recently viewed rooms above the room list": "Zobrazovat zkratky do nedávno zobrazených místností navrchu",
"Cancelling…": "Rušení…",
"Your homeserver does not support cross-signing.": "Váš domovský server nepodporuje křížové podepisování.",
@ -2633,7 +2624,6 @@
"To avoid these issues, create a <a>new encrypted room</a> for the conversation you plan to have.": "Chcete-li se těmto problémům vyhnout, vytvořte pro plánovanou konverzaci <a>novou šifrovanou místnost</a>.",
"Are you sure you want to add encryption to this public room?": "Opravdu chcete šifrovat tuto veřejnou místnost?",
"Cross-signing is ready but keys are not backed up.": "Křížové podepisování je připraveno, ale klíče nejsou zálohovány.",
"Threaded messaging": "Zprávy ve vláknech",
"Thread": "Vlákno",
"The above, but in <Room /> as well": "Výše uvedené, ale také v <Room />",
"The above, but in any room you are joined or invited to as well": "Výše uvedené, ale také v jakékoli místnosti, ke které jste připojeni nebo do které jste pozváni",
@ -2967,7 +2957,6 @@
"Failed to fetch your location. Please try again later.": "Nepodařilo se zjistit vaši polohu. Zkuste to prosím později.",
"Could not fetch location": "Nepodařilo se zjistit polohu",
"Automatically send debug logs on decryption errors": "Automaticky odesílat ladící protokoly při chybách dešifrování",
"Show extensible event representation of events": "Zobrazit rozšířené reprezentace událostí",
"was removed %(count)s times|one": "byl(a) odebrán(a)",
"was removed %(count)s times|other": "byli odebráni %(count)s krát",
"were removed %(count)s times|one": "byli odebráni",
@ -3051,7 +3040,6 @@
"Use <arrows/> to scroll": "K pohybu použijte <arrows/>",
"Feedback sent! Thanks, we appreciate it!": "Zpětná vazba odeslána! Děkujeme, vážíme si toho!",
"Thank you for trying the beta, please go into as much detail as you can so we can improve it.": "Děkujeme za vyzkoušení beta verze a prosíme o co nejpodrobnější informace, abychom ji mohli vylepšit.",
"How can I leave the beta?": "Jak mohu opustit beta verzi?",
"%(space1Name)s and %(space2Name)s": "%(space1Name)s a %(space2Name)s",
"%(oneUser)ssent %(count)s hidden messages|one": "%(oneUser)sodeslal(a) skrytou zprávu",
"%(oneUser)ssent %(count)s hidden messages|other": "%(oneUser)s odeslal(a) %(count)s skrytých zpráv",
@ -3222,7 +3210,6 @@
"Beta feature": "Beta funkce",
"Threads are a beta feature": "Vlákna jsou beta funkcí",
"Threads help keep your conversations on-topic and easy to track.": "Vlákna pomáhají udržovat konverzace k tématu a snadno je sledovat.",
"How can I start a thread?": "Jak mohu založit vlákno?",
"Threads help keep conversations on-topic and easy to track. <a>Learn more</a>.": "Vlákna pomáhají udržovat konverzace k tématu a snadno je sledovat. <a>Další informace</a>.",
"Keep discussions organised with threads.": "Diskuse udržovat organizované pomocí vláken.",
"sends hearts": "posílá srdíčka",
@ -3248,8 +3235,6 @@
"Disinvite from room": "Zrušit pozvánku do místnosti",
"Disinvite from space": "Zrušit pozvánku do prostoru",
"<b>Tip:</b> Use “%(replyInThread)s” when hovering over a message.": "<b>Tip:</b> Použijte \"%(replyInThread)s\" při najetí na zprávu.",
"To leave, return to this page and use the “%(leaveTheBeta)s” button.": "Chcete-li odejít, vraťte se na tuto stránku a použijte tlačítko \"%(leaveTheBeta)s\".",
"Use “%(replyInThread)s” when hovering over a message.": "Použijte \"%(replyInThread)s\" při najetí na zprávu.",
"No live locations": "Žádné polohy živě",
"Start messages with <code>/plain</code> to send without markdown and <code>/md</code> to send with.": "Zprávy uvozujte pomocí <code>/plain</code> pro odeslání bez Markdown a <code>/md</code> pro odeslání s Markdown formátováním.",
"Enable Markdown": "Povolit Markdown",
@ -3444,12 +3429,10 @@
"Welcome": "Vítejte",
"Show shortcut to welcome checklist above the room list": "Zobrazit zástupce na uvítací kontrolní seznam nad seznamem místností",
"Send read receipts": "Odesílat potvrzení o přečtení",
"Consider signing out from old sessions (%(inactiveAgeDays)s days or older) you don't use anymore": "Zvažte odhlášení ze starých relací (%(inactiveAgeDays)s dní nebo starších), které již nepoužíváte",
"Inactive sessions": "Neaktivní relace",
"View all": "Zobrazit všechny",
"Verify your sessions for enhanced secure messaging or sign out from those you don't recognize or use anymore.": "Ověřte své relace pro bezpečné zasílání zpráv nebo se odhlaste z těch, které již nepoznáváte nebo nepoužíváte.",
"Unverified sessions": "Neověřené relace",
"Improve your account security by following these recommendations": "Zlepšete zabezpečení svého účtu dodržováním těchto doporučení",
"Security recommendations": "Bezpečnostní doporučení",
"Filter devices": "Filtrovat zařízení",
"Inactive for %(inactiveAgeDays)s days or longer": "Neaktivní po dobu %(inactiveAgeDays)s dní nebo déle",
@ -3503,7 +3486,6 @@
"Video call (Jitsi)": "Videohovor (Jitsi)",
"Live": "Živě",
"Failed to set pusher state": "Nepodařilo se nastavit stav push oznámení",
"%(selectedDeviceCount)s sessions selected": "%(selectedDeviceCount)s vybraných relací",
"Receive push notifications on this session.": "Přijímat push oznámení v této relaci.",
"Toggle push notifications on this session.": "Přepnout push oznámení v této relaci.",
"Push notifications": "Push oznámení",
@ -3544,7 +3526,6 @@
"Have greater visibility and control over all your sessions.": "Získejte větší přehled a kontrolu nad všemi relacemi.",
"New session manager": "Nový správce relací",
"Use new session manager": "Použít nový správce relací",
"Sign out all other sessions": "Odhlásit všechny ostatní relace",
"resume voice broadcast": "obnovit hlasové vysílání",
"pause voice broadcast": "pozastavit hlasové vysílání",
"Underline": "Podtržení",
@ -3645,12 +3626,11 @@
"Rich text editor": "Editor formátovaného textu",
"<w>WARNING:</w> <description/>": "<w>UPOZORNĚNÍ:</w> <description/>",
"Feeling experimental? Try out our latest ideas in development. These features are not finalised; they may be unstable, may change, or may be dropped altogether. <a>Learn more</a>.": "Rádi experimentujete? Vyzkoušejte naše nejnovější nápady ve vývoji. Tyto funkce nejsou dokončeny; mohou být nestabilní, mohou se změnit nebo mohou být zcela vypuštěny. <a>Zjistěte více</a>.",
"Early previews": "První náhledy",
"Early previews": "Předběžné ukázky",
"What's next for %(brand)s? Labs are the best way to get things early, test out new features and help shape them before they actually launch.": "Co se chystá pro %(brand)s? Experimentální funkce jsou nejlepším způsobem, jak se dostat k novým věcem v raném stádiu, vyzkoušet nové funkce a pomoci je formovat ještě před jejich spuštěním.",
"Upcoming features": "Připravované funkce",
"Requires compatible homeserver.": "Vyžaduje kompatibilní domovský server.",
"Low bandwidth mode": "Režim malé šířky pásma",
"Under active development": "V aktivním vývoji",
"Under active development.": "V aktivním vývoji.",
"Favourite Messages": "Oblíbené zprávy",
"Temporary implementation. Locations persist in room history.": "Dočasná implementace. Polohy zůstanou v historii místností.",
@ -3679,5 +3659,50 @@
"For best security and privacy, it is recommended to use Matrix clients that support encryption.": "Pro co nejlepší zabezpečení a ochranu soukromí je doporučeno používat Matrix klienty, které podporují šifrování.",
"This session doesn't support encryption, so it can't be verified.": "Tato relace nepodporuje šifrování, takže ji nelze ověřit.",
"%(senderName)s ended a <a>voice broadcast</a>": "%(senderName)s ukončil(a) <a>hlasové vysílání</a>",
"You ended a <a>voice broadcast</a>": "Ukončili jste <a>hlasové vysílání</a>"
"You ended a <a>voice broadcast</a>": "Ukončili jste <a>hlasové vysílání</a>",
"Threaded messages": "Zprávy ve vláknech",
"Unable to decrypt message": "Nelze dešifrovat zprávu",
"This message could not be decrypted": "Tuto zprávu se nepodařilo dešifrovat",
"Resend key requests": "Opětovně odeslat žádosti o klíč",
"Unfortunately, there are no other verified devices to request decryption keys from. Signing in and verifying other devices may help avoid this situation in the future.": "Bohužel neexistují žádná další ověřená zařízení, ze kterých by si bylo možné vyžádat dešifrovací klíče. Přihlášení a ověření dalších zařízení může pomoci této situaci v budoucnu předejít.",
"Some messages could not be decrypted": "Některé zprávy nebylo možné dešifrovat",
"View your device list": "Zobrazit seznam vašich zařízení",
"This device is requesting decryption keys from your other devices. Opening one of your other devices may speed this up.": "Toto zařízení si vyžádá dešifrovací klíče z ostatních zařízení. Otevření některého z vašich dalších zařízení to může urychlit.",
"Open another device to load encrypted messages": "Otevřete jiné zařízení pro načtení zašifrovaných zpráv",
"You will not be able to access old undecryptable messages, but resetting your keys will allow you to receive new messages.": "Ke starým nedešifrovatelným zprávám nebudete mít přístup, ale resetování klíčů vám umožní přijímat nové zprávy.",
"Reset your keys to prevent future decryption errors": "Resetujte své klíče, abyste předešli budoucím chybám při dešifrování",
"This device was unable to decrypt some messages because it has not been verified yet.": "Toto zařízení nebylo schopno dešifrovat některé zprávy, protože dosud nebylo ověřeno.",
"Verify this device to access all messages": "Ověřte toto zařízení pro přístup ke všem zprávám",
"Please wait as we try to decrypt your messages. This may take a few moments.": "Počkejte prosím, než se pokusíme vaše zprávy dešifrovat. Může to chvíli trvat.",
"Decrypting messages...": "Dešifrování zpráv...",
"%(senderName)s ended a voice broadcast": "%(senderName)s ukončil(a) hlasové vysílání",
"You ended a voice broadcast": "Ukončili jste hlasové vysílání",
"Under active development. Can currently only be enabled via config.json": "V aktivním vývoji. V současné době lze povolit pouze prostřednictvím config.json",
"Rust cryptography implementation": "Implementace kryptografie v jazyce Rust",
"Improve your account security by following these recommendations.": "Zlepšete zabezpečení svého účtu dodržováním těchto doporučení.",
"%(count)s sessions selected|one": "%(count)s vybraná relace",
"%(count)s sessions selected|other": "%(count)s vybraných relací",
"You cant start a call as you are currently recording a live broadcast. Please end your live broadcast in order to start a call.": "Nemůžete zahájit hovor, protože právě nahráváte živé vysílání. Ukončete prosím živé vysílání, abyste mohli zahájit hovor.",
"Cant start a call": "Nelze zahájit hovor",
"Failed to read events": "Nepodařilo se načíst události",
"Failed to send event": "Nepodařilo se odeslat událost",
" in <strong>%(room)s</strong>": " v <strong>%(room)s</strong>",
"Verify your current session for enhanced secure messaging.": "Ověřte svou aktuální relaci pro vylepšené zabezpečené zasílání zpráv.",
"Your current session is ready for secure messaging.": "Vaše aktuální relace je připravena pro bezpečné zasílání zpráv.",
"Mark as read": "Označit jako přečtené",
"Text": "Text",
"Create a link": "Vytvořit odkaz",
"Link": "Odkaz",
"Force 15s voice broadcast chunk length": "Vynutit 15s délku bloku hlasového vysílání",
"Sign out of %(count)s sessions|one": "Odhlásit se z %(count)s relace",
"Sign out of %(count)s sessions|other": "Odhlásit se z %(count)s relací",
"Sign out of all other sessions (%(otherSessionsCount)s)": "Odhlásit se ze všech ostatních relací (%(otherSessionsCount)s)",
"Yes, end my recording": "Ano, ukončit nahrávání",
"If you start listening to this live broadcast, your current live broadcast recording will be ended.": "Jakmile začnete poslouchat toto živé vysílání, aktuální záznam živého vysílání bude ukončen.",
"Listen to live broadcast?": "Poslouchat živé vysílání?",
"Unfortunately we're unable to start a recording right now. Please try again later.": "Bohužel nyní nemůžeme spustit nahrávání. Zkuste to prosím později.",
"Connection error": "Chyba připojení",
"You can't start a voice message as you are currently recording a live broadcast. Please end your live broadcast in order to start recording a voice message.": "Hlasovou zprávu nelze spustit, protože právě nahráváte živé vysílání. Ukončete prosím živé vysílání, abyste mohli začít nahrávat hlasovou zprávu.",
"Can't start voice message": "Nelze spustit hlasovou zprávu",
"Edit link": "Upravit odkaz"
}

View File

@ -114,7 +114,7 @@
"%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s hat das Berechtigungslevel von %(powerLevelDiffText)s geändert.",
"%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s hat den Raumnamen geändert zu %(roomName)s.",
"%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s hat das Thema geändert in \"%(topic)s\".",
"Failed to send request.": "Anfrage konnte nicht gesendet werden.",
"Failed to send request.": "Übertragung der Anfrage fehlgeschlagen.",
"%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s von %(fromPowerLevel)s zu %(toPowerLevel)s",
"%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s hat den Verlauf für alle Raummitglieder ab ihrer Einladung sichtbar gemacht.",
"%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s hat den Verlauf für alle Raummitglieder ab ihrem Betreten sichtbar gemacht.",
@ -402,7 +402,6 @@
"This room is not public. You will not be able to rejoin without an invite.": "Dieser Raum ist nicht öffentlich. Du wirst ihn nicht ohne erneute Einladung betreten können.",
"Failed to remove tag %(tagName)s from room": "Entfernen der Raum-Kennzeichnung %(tagName)s fehlgeschlagen",
"Failed to add tag %(tagName)s to room": "Fehler beim Hinzufügen des \"%(tagName)s\"-Tags an dem Raum",
"Key request sent.": "Schlüsselanfrage gesendet.",
"Submit debug logs": "Fehlerbericht abschicken",
"Code": "Code",
"Opens the Developer Tools dialog": "Öffnet die Entwicklungswerkzeuge",
@ -461,9 +460,9 @@
"Low Priority": "Niedrige Priorität",
"Off": "Aus",
"Event Type": "Eventtyp",
"Event sent!": "Event gesendet!",
"Event sent!": "Ereignis gesendet!",
"View Source": "Rohdaten anzeigen",
"Event Content": "Eventinhalt",
"Event Content": "Ereignisinhalt",
"Thank you!": "Danke!",
"Checking for an update...": "Nach Aktualisierungen suchen …",
"Missing roomId.": "Fehlende Raum-ID.",
@ -944,7 +943,6 @@
"The integration manager is offline or it cannot reach your homeserver.": "Der Integrationsassistent ist außer Betrieb oder kann deinen Heim-Server nicht erreichen.",
"not stored": "nicht gespeichert",
"Backup has a signature from <verify>unknown</verify> user with ID %(deviceId)s": "Die Sicherung hat eine Signatur von <verify>unbekanntem</verify> Nutzer mit ID %(deviceId)s",
"Clear notifications": "Benachrichtigungen löschen",
"Disconnect from the identity server <current /> and connect to <new /> instead?": "Vom Identitäts-Server <current /> trennen, und stattdessen mit <new /> verbinden?",
"The identity server you have chosen does not have any terms of service.": "Der von dir gewählte Identitäts-Server gibt keine Nutzungsbedingungen an.",
"Disconnect identity server": "Verbindung zum Identitäts-Server trennen",
@ -997,10 +995,6 @@
"If disabled, messages from encrypted rooms won't appear in search results.": "Wenn deaktiviert, werden Nachrichten von verschlüsselten Räumen nicht in den Ergebnissen auftauchen.",
"This user has not verified all of their sessions.": "Dieser Benutzer hat nicht alle seine Sitzungen verifiziert.",
"You have verified this user. This user has verified all of their sessions.": "Du hast diesen Nutzer verifiziert. Der Nutzer hat alle seine Sitzungen verifiziert.",
"Your key share request has been sent - please check your other sessions for key share requests.": "Deine Schlüsselanfrage wurde gesendet sieh in deinen anderen Sitzungen nach der Schlüsselanfrage.",
"Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.": "Schlüsselanfragen werden automatisch an deine anderen Sitzungen gesendet. Wenn du sie abgelehnt oder ignoriert hast, klicke hier, um die Schlüssel erneut anzufordern.",
"If your other sessions do not have the key for this message you will not be able to decrypt them.": "Wenn deine anderen Sitzungen nicht über den Schlüssel für diese Nachricht verfügen, kannst du die Nachricht nicht entschlüsseln.",
"<requestLink>Re-request encryption keys</requestLink> from your other sessions.": "<requestLink>Schlüssel aus deinen anderen Sitzungen erneut anfordern</requestLink>.",
"Room %(name)s": "Raum %(name)s",
"Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "Die Aktualisierung dieses Raums deaktiviert die aktuelle Instanz des Raums und erstellt einen aktualisierten Raum mit demselben Namen.",
"%(name)s (%(userId)s) signed in to a new session without verifying it:": "%(name)s (%(userId)s) hat sich zu einer neuen Sitzung angemeldet, ohne sie zu verifizieren:",
@ -1033,7 +1027,6 @@
"Use your account or create a new one to continue.": "Benutze dein Konto oder erstelle ein neues, um fortzufahren.",
"Create Account": "Konto erstellen",
"Show typing notifications": "Tippbenachrichtigungen anzeigen",
"Order rooms by name": "Sortiere Räume nach Name",
"When rooms are upgraded": "Raumaktualisierungen",
"Scan this unique code": "Lese diesen eindeutigen Code ein",
"Compare unique emoji": "Vergleiche einzigartige Emojis",
@ -1119,7 +1112,6 @@
"Sounds": "Töne",
"Upgrade the room": "Raum aktualisieren",
"Enable room encryption": "Raumverschlüsselung aktivieren",
"This message cannot be decrypted": "Diese Nachricht kann nicht entschlüsselt werden",
"Encrypted by an unverified session": "Von einer nicht verifizierten Sitzung verschlüsselt",
"Unencrypted": "Unverschlüsselt",
"Encrypted by a deleted session": "Von einer gelöschten Sitzung verschlüsselt",
@ -1141,7 +1133,6 @@
"Keep a copy of it somewhere secure, like a password manager or even a safe.": "Bewahre eine Kopie an einem sicheren Ort, wie einem Passwort-Manager oder in einem Safe auf.",
"Copy": "Kopieren",
"Sends a message as html, without interpreting it as markdown": "Sendet eine Nachricht als HTML, ohne sie als Markdown darzustellen",
"Show rooms with unread notifications first": "Zeige Räume mit ungelesenen Benachrichtigungen zuerst an",
"Show shortcuts to recently viewed rooms above the room list": "Kürzlich besuchte Räume anzeigen",
"Use Single Sign On to continue": "Einmalanmeldung zum Fortfahren nutzen",
"Confirm adding this email address by using Single Sign On to prove your identity.": "Bestätige die neue E-Mail-Adresse mit Single-Sign-On, um deine Identität nachzuweisen.",
@ -2642,7 +2633,6 @@
"& %(count)s more|one": "und %(count)s weitere",
"Autoplay videos": "Videos automatisch abspielen",
"Autoplay GIFs": "GIFs automatisch abspielen",
"Threaded messaging": "Threads",
"%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s hat eine Nachricht losgelöst. Alle angepinnten Nachrichten anzeigen.",
"%(senderName)s unpinned <a>a message</a> from this room. See all <b>pinned messages</b>.": "%(senderName)s hat <a>eine Nachricht</a> losgeheftet. Alle <b>angehefteten Nachrichten anzeigen</b>.",
"%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s hat eine Nachricht angeheftet. Alle angehefteten Nachrichten anzeigen.",
@ -2934,7 +2924,6 @@
"Automatically send debug logs on decryption errors": "Sende bei Entschlüsselungsfehlern automatisch Protokolle zur Fehlerkorrektur",
"Show join/leave messages (invites/removes/bans unaffected)": "Bei-/Austrittsnachrichten (Einladung/Entfernen/Bann nicht betroffen)",
"Use new room breadcrumbs": "Kürzlich besuchte Räume anzeigen („Breadcrumbs“)",
"Show extensible event representation of events": "Erweiterbare Darstellung von Ereignissen anzeigen",
"Back to thread": "Zurück zum Thread",
"Back to chat": "Zurück zur Unterhaltung",
"Remove, ban, or invite people to your active room, and make you leave": "Entferne, verbanne oder lade andere in deinen aktiven Raum ein und verlasse den Raum selbst",
@ -3046,7 +3035,6 @@
"You do not have permissions to add spaces to this space": "Du hast keine Berechtigung, Spaces zu diesem Space hinzuzufügen",
"Results not as expected? Please <a>give feedback</a>.": "Sind die Ergebnisse nicht wie erwartet? Bitte <a>gib eine Rückmeldung</a>.",
"Feedback sent! Thanks, we appreciate it!": "Rückmeldung gesendet! Danke, wir wissen es zu schätzen!",
"How can I leave the beta?": "Wie kann ich die Beta verlassen?",
"Thank you for trying the beta, please go into as much detail as you can so we can improve it.": "Danke dir für das Ausprobieren der Beta, bitte gehe so sehr ins Detail wie du kannst, damit wir es verbessern können.",
"%(space1Name)s and %(space2Name)s": "%(space1Name)s und %(space2Name)s",
"Use <arrows/> to scroll": "Benutze <arrows/> zum scrollen",
@ -3134,9 +3122,6 @@
"%(value)sd": "%(value)sd",
"Start messages with <code>/plain</code> to send without markdown and <code>/md</code> to send with.": "Beginne Nachrichten mit <code>/plain</code>, um Nachrichten ohne Markdown zu schreiben und mit <code>/md</code>, um sie mit Markdown zu schreiben.",
"Enable Markdown": "Markdown aktivieren",
"To leave, return to this page and use the “%(leaveTheBeta)s” button.": "Zum Verlassen, gehe auf diese Seite zurück und klicke auf „%(leaveTheBeta)s“.",
"Use “%(replyInThread)s” when hovering over a message.": "Klicke auf „%(replyInThread)s“ im Menü einer Nachricht.",
"How can I start a thread?": "Wie kann ich einen Thread starten?",
"Threads help keep conversations on-topic and easy to track. <a>Learn more</a>.": "Threads helfen dir dabei, dass deine Unterhaltungen beim Thema bleiben. <a>Mehr Infos</a>.",
"Keep discussions organised with threads.": "Organisiere Diskussionen mit Threads.",
"Failed to join": "Betreten fehlgeschlagen",
@ -3231,7 +3216,7 @@
"Server Versions": "Server-Versionen",
"Client Versions": "Anwendungsversionen",
"Send custom state event": "Benutzerdefiniertes Status-Event senden",
"Failed to send event!": "Event konnte nicht gesendet werden!",
"Failed to send event!": "Übertragung des Ereignisses fehlgeschlagen!",
"Server info": "Server-Info",
"Explore account data": "Kontodaten erkunden",
"View servers in room": "Zeige Server im Raum",
@ -3354,7 +3339,6 @@
"To view, please enable video rooms in Labs first": "Zum Anzeigen, aktiviere bitte Videoräume in den Laboreinstellungen",
"Use the “+” button in the room section of the left panel.": "Verwende die „+“-Schaltfläche des Räumebereichs der linken Seitenleiste.",
"View all": "Alles anzeigen",
"Improve your account security by following these recommendations": "Verbessere deine Kontosicherheit, indem du diese Empfehlungen beherzigst",
"Security recommendations": "Sicherheitsempfehlungen",
"Filter devices": "Geräte filtern",
"Inactive for %(inactiveAgeDays)s days or longer": "Seit %(inactiveAgeDays)s oder mehr Tagen inaktiv",
@ -3366,7 +3350,6 @@
"No inactive sessions found.": "Keine inaktiven Sitzungen gefunden.",
"No unverified sessions found.": "Keine unverifizierten Sitzungen gefunden.",
"No verified sessions found.": "Keine verifizierten Sitzungen gefunden.",
"Consider signing out from old sessions (%(inactiveAgeDays)s days or older) you don't use anymore": "Erwäge, dich aus alten (%(inactiveAgeDays)s Tage oder mehr), nicht mehr verwendeten Sitzungen abzumelden",
"Inactive sessions": "Inaktive Sitzungen",
"Unverified sessions": "Nicht verifizierte Sitzungen",
"For best security, sign out from any session that you don't recognize or use anymore.": "Für bestmögliche Sicherheit, melde dich von allen Sitzungen ab, die du nicht erkennst oder benutzt.",
@ -3509,7 +3492,6 @@
"Enable notifications for this device": "Aktiviere Benachrichtigungen für dieses Gerät",
"Turn off to disable notifications on all your devices and sessions": "Ausschalten, um Benachrichtigungen auf all deinen Geräten und Sitzungen zu deaktivieren",
"Enable notifications for this account": "Aktiviere Benachrichtigungen für dieses Konto",
"%(selectedDeviceCount)s sessions selected": "%(selectedDeviceCount)s Sitzungen ausgewählt",
"Video call ended": "Videoanruf beendet",
"%(name)s started a video call": "%(name)s hat einen Videoanruf begonnen",
"Record the client name, version, and url to recognise sessions more easily in session manager": "Bezeichnung, Version und URL der Anwendung registrieren, damit diese Sitzung in der Sitzungsverwaltung besser erkennbar ist",
@ -3544,15 +3526,14 @@
"Have greater visibility and control over all your sessions.": "Bessere Übersicht und Kontrolle über all deine Sitzungen.",
"New session manager": "Neue Sitzungsverwaltung",
"Use new session manager": "Neue Sitzungsverwaltung nutzen",
"Sign out all other sessions": "Alle anderen Sitzungen abmelden",
"pause voice broadcast": "Sprachübertragung pausieren",
"resume voice broadcast": "Sprachübertragung fortsetzen",
"Italic": "Kursiv",
"Underline": "Unterstrichen",
"Notifications silenced": "Benachrichtigungen stummgeschaltet",
"Are you sure you want to stop your live broadcast?This will end the broadcast and the full recording will be available in the room.": "Willst du die Sprachübertragung wirklich beenden? Damit endet auch die Aufnahme.",
"Yes, stop broadcast": "Ja, Sprachübertragung beenden",
"Stop live broadcasting?": "Sprachübertragung beenden?",
"Are you sure you want to stop your live broadcast?This will end the broadcast and the full recording will be available in the room.": "Möchtest du die Übertragung wirklich beenden? Dies wird die Übertragung beenden und die vollständige Aufnahme im Raum bereitstellen.",
"Yes, stop broadcast": "Ja, Übertragung beenden",
"Stop live broadcasting?": "Live-Übertragung beenden?",
"Sign in with QR code": "Mit QR-Code anmelden",
"Browser": "Browser",
"Allow a QR code to be shown in session manager to sign in another device (requires compatible homeserver)": "Erlaube es andere Geräte mittels QR-Code in der Sitzungsverwaltung anzumelden (kompatibler Heim-Server benötigt)",
@ -3650,7 +3631,6 @@
"Rich text editor": "Textverarbeitungs-Editor",
"<w>WARNING:</w> <description/>": "<w>WARNUNG:</w> <description/>",
"Requires compatible homeserver.": "Benötigt kompatiblen Heim-Server.",
"Under active development": "In aktiver Entwicklung",
"Under active development.": "In aktiver Entwicklung.",
"Temporary implementation. Locations persist in room history.": "Vorläufige Implementierung: Standorte verbleiben im Raumverlauf.",
"Live Location Sharing": "Echtzeit-Standortfreigabe",
@ -3679,5 +3659,50 @@
"This session doesn't support encryption and thus can't be verified.": "Diese Sitzung unterstützt keine Verschlüsselung und kann deshalb nicht verifiziert werden.",
"This session doesn't support encryption, so it can't be verified.": "Diese Sitzung unterstützt keine Verschlüsselung und kann deshalb nicht verifiziert werden.",
"%(senderName)s ended a <a>voice broadcast</a>": "%(senderName)s beendete eine <a>Sprachübertragung</a>",
"You ended a <a>voice broadcast</a>": "Du hast eine <a>Sprachübertragung</a> beendet"
"You ended a <a>voice broadcast</a>": "Du hast eine <a>Sprachübertragung</a> beendet",
"Unable to decrypt message": "Nachrichten-Entschlüsselung nicht möglich",
"This message could not be decrypted": "Diese Nachricht konnte nicht enschlüsselt werden",
"Resend key requests": "Schlüsselanfrage erneut senden",
"Unfortunately, there are no other verified devices to request decryption keys from. Signing in and verifying other devices may help avoid this situation in the future.": "Bedauerlicherweise gibt es keine anderen Geräte, von denen Entschlüsselungs-Schlüssel angefordert werden können. Andere Geräte anzumelden und zu verifizieren könnte derartige Situationen in Zukunft verhindern.",
"Some messages could not be decrypted": "Einige Nachrichten konnten nicht entschlüsselt werden",
"View your device list": "Deine Geräteliste ansehen",
"This device is requesting decryption keys from your other devices. Opening one of your other devices may speed this up.": "Dieses Gerät fordert Entschlüsselungs-Schlüssel von deinen anderen Geräten an. Die Nutzung deiner anderen Geräte könnte dies beschleunigen.",
"Open another device to load encrypted messages": "Nutze ein anderes verbundenes Gerät, um verschlüsselte Nachrichten zu laden",
"You will not be able to access old undecryptable messages, but resetting your keys will allow you to receive new messages.": "Du wirst nicht in der Lage sein, auf alte nicht entschlüsselbare Nachrichten zuzugreifen, aber durch das Zurücksetzen kannst du neue Nachrichten erhalten.",
"Reset your keys to prevent future decryption errors": "Setze deine Schlüssel zurück, um zukünftige Entschlüsselungsfehler zu vermeiden",
"This device was unable to decrypt some messages because it has not been verified yet.": "Dieses Gerät konnte einige Nachrichten nicht entschlüsseln, da es noch nicht verifiziert wurde.",
"Verify this device to access all messages": "Verifiziere dieses Gerät, um auf alle Nachrichten zugreifen zu können",
"Please wait as we try to decrypt your messages. This may take a few moments.": "Bitte warte, während wir versuchen, deine Nachrichten zu entschlüsseln. Das könnte ein wenig dauern.",
"Decrypting messages...": "Entschlüssele Nachrichten …",
"Under active development. Can currently only be enabled via config.json": "In aktiver Entwicklung. Kann im Moment nur per config.json aktiviert werden",
"Rust cryptography implementation": "Rust-Verschlüsselungsumsetzung",
"Threaded messages": "Threads",
"%(senderName)s ended a voice broadcast": "%(senderName)s beendete eine Sprachübertragung",
"You ended a voice broadcast": "Du hast eine Sprachübertragung beendet",
"You cant start a call as you are currently recording a live broadcast. Please end your live broadcast in order to start a call.": "Du kannst keinen Anruf beginnen, da du im Moment eine Sprachübertragung aufzeichnest. Bitte beende deine Sprachübertragung, um ein Gespräch zu beginnen.",
"Cant start a call": "Kann keinen Anruf beginnen",
"Improve your account security by following these recommendations.": "Verbessere deine Kontosicherheit, indem du diese Empfehlungen beherzigst.",
"%(count)s sessions selected|one": "%(count)s Sitzung ausgewählt",
"%(count)s sessions selected|other": "%(count)s Sitzungen ausgewählt",
"Failed to read events": "Lesen der Ereignisse fehlgeschlagen",
"Failed to send event": "Übertragung des Ereignisses fehlgeschlagen",
" in <strong>%(room)s</strong>": " in <strong>%(room)s</strong>",
"Verify your current session for enhanced secure messaging.": "Verifiziere deine aktuelle Sitzung für besonders sichere Kommunikation.",
"Your current session is ready for secure messaging.": "Deine aktuelle Sitzung ist für sichere Kommunikation bereit.",
"Mark as read": "Als gelesen markieren",
"Text": "Text",
"Create a link": "Link erstellen",
"Link": "Link",
"Force 15s voice broadcast chunk length": "Die Chunk-Länge der Sprachübertragungen auf 15 Sekunden erzwingen",
"Sign out of %(count)s sessions|one": "Von %(count)s Sitzung abmelden",
"Sign out of %(count)s sessions|other": "Von %(count)s Sitzungen abmelden",
"Sign out of all other sessions (%(otherSessionsCount)s)": "Von allen anderen Sitzungen abmelden (%(otherSessionsCount)s)",
"Listen to live broadcast?": "Echtzeitübertragung anhören?",
"Yes, end my recording": "Ja, beende meine Aufzeichnung",
"If you start listening to this live broadcast, your current live broadcast recording will be ended.": "Wenn du beginnst, diese Echtzeitübertragung anzuhören, wird deine aktuelle Echtzeitübertragungsaufzeichnung beendet.",
"Unfortunately we're unable to start a recording right now. Please try again later.": "Leider ist es aktuell nicht möglich eine Aufnahme zu starten. Bitte versuche es später erneut.",
"Connection error": "Verbindungsfehler",
"Can't start voice message": "Kann Sprachnachricht nicht beginnen",
"You can't start a voice message as you are currently recording a live broadcast. Please end your live broadcast in order to start recording a voice message.": "Du kannst keine Sprachnachricht beginnen, da du im Moment eine Echtzeitübertragung aufzeichnest. Bitte beende deine Sprachübertragung, um ein Gespräch zu beginnen.",
"Edit link": "Link bearbeiten"
}

View File

@ -876,7 +876,6 @@
"Your message was sent": "Το μήνυμά σας στάλθηκε",
"Encrypting your message...": "Κρυπτογράφηση του μηνύματός σας...",
"Sending your message...": "Αποστολή του μηνύματός σας...",
"This message cannot be decrypted": "Αυτό το μήνυμα δεν μπορεί να αποκρυπτογραφηθεί",
"Edit message": "Επεξεργασία μηνύματος",
"Everyone in this room is verified": "Όλοι σε αυτό το δωμάτιο έχουν επαληθευτεί",
"This room is end-to-end encrypted": "Αυτό το δωμάτιο έχει κρυπτογράφηση από άκρο σε άκρο",
@ -907,7 +906,6 @@
"There was an error loading your notification settings.": "Παρουσιάστηκε σφάλμα κατά τη φόρτωση των ρυθμίσεων ειδοποιήσεων σας.",
"Mentions & keywords": "Αναφορές & λέξεις-κλειδιά",
"New keyword": "Νέα λέξη-κλειδί",
"Clear notifications": "Εκκαθάριση ειδοποιήσεων",
"Enable audible notifications for this session": "Ενεργοποιήστε τις ηχητικές ειδοποιήσεις για αυτήν τη συνεδρία",
"Show message in desktop notification": "Εμφάνιση του μηνύματος στην ειδοποίηση στον υπολογιστή",
"Enable desktop notifications for this session": "Ενεργοποιήστε τις ειδοποιήσεις στον υπολογιστή για αυτήν τη συνεδρία",
@ -961,7 +959,6 @@
"Enable message search in encrypted rooms": "Ενεργοποίηση αναζήτησης μηνυμάτων σε κρυπτογραφημένα δωμάτια",
"Show hidden events in timeline": "Εμφάνιση κρυφών συμβάντων στη γραμμή χρόνου",
"Show shortcuts to recently viewed rooms above the room list": "Εμφάνιση συντομεύσεων σε δωμάτια που προβλήθηκαν πρόσφατα πάνω από τη λίστα δωματίων",
"Show rooms with unread notifications first": "Εμφάνιση δωματίων με μη αναγνωσμένες ειδοποιήσεις πρώτα",
"Never send encrypted messages to unverified sessions in this room from this session": "Μη στέλνετε ποτέ κρυπτογραφημένα μηνύματα σε μη επαληθευμένες συνεδρίες σε αυτό το δωμάτιο από αυτή τη συνεδρία",
"Never send encrypted messages to unverified sessions from this session": "Μη στέλνετε ποτέ κρυπτογραφημένα μηνύματα σε μη επαληθευμένες συνεδρίες από αυτήν τη συνεδρία",
"Send analytics data": "Αποστολή δεδομένων αναλυτικών στοιχείων",
@ -1164,12 +1161,10 @@
"Media omitted - file size limit exceeded": "Τα μέσα παραλείφθηκαν - υπέρβαση του ορίου μεγέθους αρχείου",
"Show info about bridges in room settings": "Εμφάνιση πληροφοριών σχετικά με τις γέφυρες στις ρυθμίσεις δωματίου",
"Show current avatar and name for users in message history": "Εμφάνιση τρέχοντος avatar και ονόματος για τους χρήστες στο ιστορικό μηνυμάτων",
"Show extensible event representation of events": "Εμφάνιση επεκτάσιμης αναπαράστασης συμβάντων",
"Show message previews for reactions in all rooms": "Εμφάνιση προεπισκοπήσεων μηνυμάτων για αντιδράσεις σε όλα τα δωμάτια",
"Show message previews for reactions in DMs": "Εμφάνιση προεπισκοπήσεων μηνυμάτων για αντιδράσεις σε DM",
"Support adding custom themes": "Υποστήριξη προσθήκης προσαρμοσμένων θεμάτων",
"Render simple counters in room header": "Απόδοση απλών μετρητών στην κεφαλίδα δωματίου",
"Threaded messaging": "Μηνύματα με νήματα εκτέλεσης",
"Let moderators hide messages pending moderation.": "Επιτρέψτε στους επόπτες να αποκρύψουν μηνύματα που βρίσκονται σε εκκρεμότητα.",
"Developer": "Προγραμματιστής",
"Experimental": "Πειραματικό",
@ -1254,8 +1249,6 @@
"Enable Emoji suggestions while typing": "Ενεργοποιήστε τις προτάσεις Emoji κατά την πληκτρολόγηση",
"Jump to date (adds /jumptodate and jump to date headers)": "Μετάβαση στην ημερομηνία (προσθέτει /μετάβαση στην ημερομηνία και μετάβαση στις κεφαλίδες ημερομηνίας)",
"Thank you for trying the beta, please go into as much detail as you can so we can improve it.": "Σας ευχαριστούμε που δοκιμάσατε την έκδοση beta, παρακαλούμε να αναφέρετε όσο περισσότερες λεπτομέρειες μπορείτε για να τη βελτιώσουμε.",
"How can I leave the beta?": "Πώς μπορώ να φύγω από την beta έκδοση;",
"Order rooms by name": "Ορίστε τη σειρά των δωματίων κατά το όνομά τους",
"Media omitted": "Τα μέσα παραλείφθηκαν",
"Enable widget screenshots on supported widgets": "Ενεργοποίηση στιγμιότυπων οθόνης μικροεφαρμογών σε υποστηριζόμενες μικροεφαρμογές",
"Enable URL previews by default for participants in this room": "Ενεργοποιήστε τις προεπισκοπήσεις URL από προεπιλογή για τους συμμετέχοντες σε αυτό το δωμάτιο",
@ -1842,11 +1835,6 @@
"Encrypted by an unverified session": "Κρυπτογραφήθηκε από μια μη επαληθευμένη συνεδρία",
"Copy link to thread": "Αντιγραφή συνδέσμου στο νήμα εκτέλεσης",
"View in room": "Δείτε στο δωμάτιο",
"<requestLink>Re-request encryption keys</requestLink> from your other sessions.": "<requestLink>Ζητήστε ξανά κλειδιά κρυπτογράφησης</requestLink> από τις άλλες σας συνεδρίες.",
"Key request sent.": "Το αίτημα κλειδιού στάλθηκε.",
"If your other sessions do not have the key for this message you will not be able to decrypt them.": "Εάν οι άλλες συνεδρίες σας δεν έχουν το κλειδί για αυτό το μήνυμα, δεν θα μπορείτε να τις αποκρυπτογραφήσετε.",
"Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.": "Τα αιτήματα κοινής χρήσης κλειδιών αποστέλλονται αυτόματα στις άλλες συνεδρίες σας. Εάν απορρίψατε ή απδεσμεύσατε το αίτημα κοινής χρήσης κλειδιού στις άλλες συνεδρίες σας, κάντε κλικ εδώ για να ζητήσετε ξανά τα κλειδιά για αυτήν τη συνεδρία.",
"Your key share request has been sent - please check your other sessions for key share requests.": "Το αίτημά σας για κοινή χρήση κλειδιού έχει σταλεί - ελέγξτε τις άλλες συνεδρίες σας για αιτήματα κοινής χρήσης κλειδιού.",
"This event could not be displayed": "Δεν ήταν δυνατή η εμφάνιση αυτού του συμβάντος",
"From a thread": "Από ένα νήμα εκτέλεσης",
"Continuing without email": "Συνέχεια χωρίς email",
@ -3208,7 +3196,6 @@
"Currently indexing: %(currentRoom)s": "Γίνεται ευρετηρίαση: %(currentRoom)s",
"Force complete": "Εξαναγκασμός ολοκλήρωσης",
"Close dialog or context menu": "Κλείσιμο διαλόγου ή μενού περιβάλλοντος",
"How can I start a thread?": "Πώς μπορώ να ξεκινήσω ένα νήμα;",
"Threads help keep conversations on-topic and easy to track. <a>Learn more</a>.": "Τα νήματα βοηθούν στην καλύτερη οργάνωση των συζητήσεων και στην εύκολη παρακολούθηση. <a>Μάθετε περισσότερα</a>.",
"Keep discussions organised with threads.": "Διατηρήστε τις συζητήσεις οργανωμένες σε νήματα.",
"Threads are a beta feature": "Τα νήματα είναι μια δοκιμαστική δυνατότητα",
@ -3285,8 +3272,6 @@
"Do you want to enable threads anyway?": "Θέλετε να ενεργοποιήσετε τα νήματα ούτως ή άλλως;",
"Enable hardware acceleration": "Ενεργοποίηση επιτάχυνσης υλικού",
"Start messages with <code>/plain</code> to send without markdown and <code>/md</code> to send with.": "Ξεκινήστε τα μηνύματα με <code>/plain</code> για αποστολή χωρίς σήμανση markdown και <code>/md</code> για αποστολή με markdown.",
"To leave, return to this page and use the “%(leaveTheBeta)s” button.": "Για να αποχωρήσετε, επιστρέψτε σε αυτή τη σελίδα και χρησιμοποιήστε το κουμπί “%(leaveTheBeta)s”.",
"Use “%(replyInThread)s” when hovering over a message.": "Χρησιμοποιήστε την “%(replyInThread)s” όταν τοποθετείτε το δείκτη του ποντικιού πάνω από ένα μήνυμα.",
"Yes, the chat timeline is displayed alongside the video.": "Ναι, το χρονοδιάγραμμα της συνομιλίας εμφανίζεται δίπλα στο βίντεο.",
"Can I use text chat alongside the video call?": "Μπορώ να χρησιμοποιήσω τη συνομιλία κειμένου παράλληλα με τη βιντεοκλήση;",
"Use the “+” button in the room section of the left panel.": "Χρησιμοποιήστε το κουμπί “+” στην ενότητα δωματίων του αριστερού πάνελ.",

View File

@ -938,7 +938,6 @@
"Show message previews for reactions in DMs": "Show message previews for reactions in DMs",
"Show message previews for reactions in all rooms": "Show message previews for reactions in all rooms",
"Offline encrypted messaging using dehydrated devices": "Offline encrypted messaging using dehydrated devices",
"Show extensible event representation of events": "Show extensible event representation of events",
"Show current avatar and name for users in message history": "Show current avatar and name for users in message history",
"Show HTML representation of room topics": "Show HTML representation of room topics",
"Show info about bridges in room settings": "Show info about bridges in room settings",
@ -2127,6 +2126,7 @@
"%(count)s reply|other": "%(count)s replies",
"%(count)s reply|one": "%(count)s reply",
"Open thread": "Open thread",
"Unable to decrypt message": "Unable to decrypt message",
"Jump to first unread message.": "Jump to first unread message.",
"Unable to access your microphone": "Unable to access your microphone",
"We were unable to access your microphone. Please check your browser settings and try again.": "We were unable to access your microphone. Please check your browser settings and try again.",
@ -2137,6 +2137,7 @@
"Underline": "Underline",
"Code": "Code",
"Link": "Link",
"Edit link": "Edit link",
"Create a link": "Create a link",
"Text": "Text",
"Message Actions": "Message Actions",
@ -2311,7 +2312,6 @@
"Last month": "Last month",
"The beginning of the room": "The beginning of the room",
"Jump to date": "Jump to date",
"Unable to decrypt message": "Unable to decrypt message",
"Downloading": "Downloading",
"Decrypting": "Decrypting",
"Download": "Download",
@ -2688,6 +2688,8 @@
"Uncheck if you also want to remove system messages on this user (e.g. membership change, profile change…)": "Uncheck if you also want to remove system messages on this user (e.g. membership change, profile change…)",
"Remove %(count)s messages|other": "Remove %(count)s messages",
"Remove %(count)s messages|one": "Remove 1 message",
"Can't start voice message": "Can't start voice message",
"You can't start a voice message as you are currently recording a live broadcast. Please end your live broadcast in order to start recording a voice message.": "You can't start a voice message as you are currently recording a live broadcast. Please end your live broadcast in order to start recording a voice message.",
"Unable to load commit detail: %(msg)s": "Unable to load commit detail: %(msg)s",
"Unavailable": "Unavailable",
"Changelog": "Changelog",

View File

@ -459,7 +459,6 @@
"Failed to send logs: ": "Malsukcesis sendi protokolon: ",
"Preparing to send logs": "Pretigante sendon de protokolo",
"Send analytics data": "Sendi statistikajn datumojn",
"Key request sent.": "Peto de ŝlosilo sendita.",
"Permission Required": "Necesas permeso",
"Missing roomId.": "Mankas identigilo de la ĉambro.",
"%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s agordis la ĉefan adreson por la ĉambro al %(address)s.",
@ -741,7 +740,7 @@
"Rejecting invite …": "Rifuzante inviton …",
"Join the conversation with an account": "Aliĝu al la interparolo per konto",
"Sign Up": "Registriĝi",
"Sign In": "Saluti",
"Sign In": "Ensaluti",
"Reason: %(reason)s": "Kialo: %(reason)s",
"Forget this room": "Forgesi ĉi tiun ĉambron",
"Re-join": "Re-aliĝi",
@ -1116,7 +1115,6 @@
"My Ban List": "Mia listo de forbaroj",
"This is your list of users/servers you have blocked - don't leave the room!": "Ĉi tio estas la listo de uzantoj/serviloj, kiujn vi blokis ne eliru el la ĉambro!",
"Decline (%(counter)s)": "Malakcepti (%(counter)s)",
"Clear notifications": "Vakigi sciigojn",
"Error subscribing to list": "Eraris abono al listo",
"Error removing ignored user/server": "Eraris forigo de la malatentata uzanto/servilo",
"Error unsubscribing from list": "Eraris malabono de la listo",
@ -1164,7 +1162,6 @@
"Backup has a <validity>invalid</validity> signature from this user": "Savkopio havas <validity>nevalidan</validity> subskribon de ĉi tiu uzanto",
"This room is end-to-end encrypted": "Ĉi tiu ĉambro uzas tutvojan ĉifradon",
"Everyone in this room is verified": "Ĉiu en la ĉambro estas kontrolita",
"This message cannot be decrypted": "Ĉi tiun mesaĝon ne eblas malĉifri",
"Unencrypted": "Neĉifrita",
"Send a reply…": "Sendi respondon…",
"Send a message…": "Sendi mesaĝon…",
@ -1210,8 +1207,6 @@
"Show typing notifications": "Montri sciigojn pri tajpado",
"Never send encrypted messages to unverified sessions from this session": "Neniam sendi ĉifritajn mesaĝojn al nekontrolitaj salutaĵoj de ĉi tiu salutaĵo",
"Never send encrypted messages to unverified sessions in this room from this session": "Neniam sendi ĉifritajn mesaĝojn al nekontrolitaj salutaĵoj en ĉi tiu ĉambro de ĉi tiu salutaĵo",
"Order rooms by name": "Ordigi ĉambrojn laŭ nomo",
"Show rooms with unread notifications first": "Montri ĉambrojn kun nelegitaj sciigoj unue",
"Show shortcuts to recently viewed rooms above the room list": "Montri tujirilojn al freŝe rigarditaj ĉambroj super la listo de ĉambroj",
"Enable message search in encrypted rooms": "Ŝalti serĉon de mesaĝoj en ĉifritaj ĉambroj",
"How fast should messages be downloaded.": "Kiel rapide elŝuti mesaĝojn.",
@ -1325,10 +1320,6 @@
"You have not verified this user.": "Vi ne kontrolis tiun ĉi uzanton.",
"You have verified this user. This user has verified all of their sessions.": "Vi kontrolis tiun ĉi uzanton. Ĝi kontrolis ĉiomon da siaj salutaĵoj.",
"Someone is using an unknown session": "Iu uzas nekonatan salutaĵon",
"Your key share request has been sent - please check your other sessions for key share requests.": "Via peto pri havigo de ŝlosilo estas sendita bonvolu kontroli viajn aliajn salutaĵojn je petojn pri havigo de ŝlosiloj.",
"Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.": "Petoj pri havigo de ŝlosiloj estas memage sendataj al viaj aliaj salutaĵoj. Se vi rifuzis aŭ forigis la peton pri havigo de ŝlosiloj en aliaj viaj salutaĵoj, klaku ĉi tien por ree peti ŝlosilojn por ĉi tiu salutaĵo.",
"If your other sessions do not have the key for this message you will not be able to decrypt them.": "Se viaj aliaj salutaĵoj ne havas la ŝlosilon por ĉi tiu mesaĝo, vi ne povos ilin malĉifri.",
"<requestLink>Re-request encryption keys</requestLink> from your other sessions.": "<requestLink>Repeti viajn ĉifrajn ŝlosilojn</requestLink> de ceteraj viaj salutaĵoj.",
"Encrypted by an unverified session": "Ĉifrita de nekontrolita salutaĵo",
"Encrypted by a deleted session": "Ĉifrita de forigita salutaĵo",
"Close preview": "Fermi antaŭrigardon",
@ -2635,7 +2626,6 @@
"To join a space you'll need an invite.": "Por aliĝi al aro, vi bezonas inviton.",
"Autoplay videos": "Memage ludi filmojn",
"Autoplay GIFs": "Memage ludi GIF-ojn",
"Threaded messaging": "Mesaĝaj fadenoj",
"%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s malfiksis mesaĝon de ĉi tiu ĉambro. Vidu ĉiujn fiksitajn mesaĝojn.",
"%(senderName)s unpinned <a>a message</a> from this room. See all <b>pinned messages</b>.": "%(senderName)s malfiksis <a>mesaĝon</a> de ĉi tiu ĉambro. Vidu ĉiujn <b>fiksitajn mesaĝojn</b>.",
"%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s fiksis mesaĝon al ĉi tiu ĉambro. Vidu ĉiujn fiksitajn mesaĝojn.",
@ -2680,5 +2670,244 @@
"We were unable to understand the given date (%(inputDate)s). Try using the format YYYY-MM-DD.": "Ni ne povis kompreni la donitan daton (%(inputDate)s). Penu uzi la aranĝo JJJJ-MM-TT.",
"Pin to sidebar": "Fiksi al flanka breto",
"Keyboard": "Klavaro",
"Quick settings": "Rapidaj agordoj"
"Quick settings": "Rapidaj agordoj",
"You cant start a call as you are currently recording a live broadcast. Please end your live broadcast in order to start a call.": "Vi ne povas komenci vokon ĉar vi nuntempe registras vivan elsendon. Bonvolu fini vian vivan elsendon por komenci vokon.",
"Cant start a call": "Ne povas komenci vokon",
"Someone else is already recording a voice broadcast. Wait for their voice broadcast to end to start a new one.": "Iu alia jam registras voĉan elsendon. Atendu, ke ilia voĉa elsendo finiĝos por komenci novan.",
"You don't have the required permissions to start a voice broadcast in this room. Contact a room administrator to upgrade your permissions.": "Vi ne havas la bezonatajn permesojn por komenci voĉan elsendon en ĉi tiu ĉambro. Kontaktu ĉambran administranton por ĝisdatigi viajn permesojn.",
"You are already recording a voice broadcast. Please end your current voice broadcast to start a new one.": "Vi jam registras voĉan elsendon. Bonvolu fini vian nunan voĉelsendon por komenci novan.",
"Can't start a new voice broadcast": "Ne povas komenci novan voĉan elsendon",
"The above, but in <Room /> as well": "La supre, sed ankaŭ en <Room />",
"Are you sure you want to end this poll? This will show the final results of the poll and stop people from being able to vote.": "Ĉu vi certas, ke vi volas fini ĉi tiun balotenketon? Ĉi tio montros la finajn rezultojn de la balotenketo kaj malhelpos personojn povi voĉdoni.",
"End Poll": "Finu Balotenketon",
"Sorry, the poll did not end. Please try again.": "Pardonu, la balotenketo ne finiĝis. Bonvolu reprovi.",
"Failed to end poll": "Malsukcesis fini balotenketon",
"The poll has ended. Top answer: %(topAnswer)s": "La balotado finiĝis. Plej alta respondo: %(topAnswer)s",
"The poll has ended. No votes were cast.": "La balotenketo finiĝis. Neniuj voĉoj estis ĵetitaj.",
"Results are only revealed when you end the poll": "Rezultoj estas malkaŝitaj nur kiam vi finas la balotenketo",
"What is your poll question or topic?": "Kio estas via balotenketo demando aŭ temo?",
"Poll type": "Balotspeco",
"Sorry, the poll you tried to create was not posted.": "Pardonu, la balotenketo, kiun vi provis krei, ne estis afiŝita.",
"Failed to post poll": "Malsukcesis afiŝi balotenketon",
"Edit poll": "Redaktu balotenketon",
"Create poll": "Krei balotenketon",
"Create Poll": "Krei Balotenketon",
"Results will be visible when the poll is ended": "Rezultoj estos videblaj kiam la balotenketo finiĝos",
"Sorry, you can't edit a poll after votes have been cast.": "Pardonu, vi ne povas redakti balotenketon post voĉdonado.",
"Can't edit poll": "Ne povas redakti balotenketon",
"Poll": "Balotenketo",
"Light high contrast": "Malpeza alta kontrasto",
"%(senderName)s has ended a poll": "%(senderName)s finis balotenketon",
"%(senderName)s has started a poll - %(pollQuestion)s": "%(senderName)s komencis balotenketon - %(pollQuestion)s",
"%(senderName)s has shared their location": "%(senderName)s dividis sian lokon",
"%(senderName)s has updated the room layout": "%(senderName)s ĝisdatigis la aranĝon de ĉambro",
"%(senderDisplayName)s sent a sticker.": "%(senderDisplayName)s sendis glumarkon.",
"%(senderDisplayName)s changed who can join this room.": "%(senderDisplayName)s ŝanĝis, kiu povas aliĝi al ĉi tiu ĉambro.",
"%(senderDisplayName)s changed who can join this room. <a>View settings</a>.": "%(senderDisplayName)s ŝanĝis, kiu povas aliĝi al ĉi tiu ĉambro. <a>Rigardu agordojn</a>.",
"%(senderDisplayName)s changed the room avatar.": "%(senderDisplayName)s ŝanĝis la profilbildon de ĉambro.",
"Video call started in %(roomName)s. (not supported by this browser)": "Videovoko komenciĝis en %(roomName)s. (ne subtenata de ĉi tiu retumilo)",
"Video call started in %(roomName)s.": "Videovoko komenciĝis en %(roomName)s.",
"No active call in this room": "Neniu aktiva voko en ĉi tiu ĉambro",
"Failed to read events": "Malsukcesis legi okazojn",
"Failed to send event": "Malsukcesis sendi okazon",
"You need to be able to kick users to do that.": "Vi devas povi piedbati uzantojn por fari tion.",
"Empty room (was %(oldName)s)": "Malplena ĉambro (estis %(oldName)s)",
"Inviting %(user)s and %(count)s others|one": "Invitante %(user)s kaj 1 alian",
"Inviting %(user)s and %(count)s others|other": "Invitante %(user)s kaj %(count)s aliajn",
"Inviting %(user1)s and %(user2)s": "Invitante %(user1)s kaj %(user2)s",
"%(user)s and %(count)s others|one": "%(user)s and 1 alia",
"%(user)s and %(count)s others|other": "%(user)s kaj %(count)s aliaj",
"%(user1)s and %(user2)s": "%(user1)s kaj %(user2)s",
"Connectivity to the server has been lost": "Konektebleco al la servilo estas perdita",
"Enable notifications for this account": "Ŝalti sciigojn por ĉi tiu konto",
"Updating spaces... (%(progress)s out of %(count)s)|one": "Ĝisdatigante aro...",
"Updating spaces... (%(progress)s out of %(count)s)|other": "Ĝisdatigante arojn... (%(progress)s el %(count)s)",
"Sending invites... (%(progress)s out of %(count)s)|one": "Sendante inviton...",
"Sending invites... (%(progress)s out of %(count)s)|other": "Sendante invitojn... (%(progress)s el %(count)s)",
"Loading new room": "Ŝarĝante novan ĉambron",
"Upgrading room": "Altgradiga ĉambro",
"Stop live broadcasting?": "Ĉu ĉesi rekta elsendo?",
"%(senderName)s ended a voice broadcast": "%(senderName)s finis voĉan elsendon",
"You ended a voice broadcast": "Vi finis voĉan elsendon",
"%(senderName)s ended a <a>voice broadcast</a>": "%(senderName)s finis <a>voĉan elsendon</a>",
"You ended a <a>voice broadcast</a>": "Vi finis <a>voĉan elsendon</a>",
"Voice broadcast": "Voĉan elsendo",
"Live": "Vivi",
"play voice broadcast": "ludu voĉan elsendon",
"Scroll down in the timeline": "Rulumu malsupren en la historio",
"Scroll up in the timeline": "Rulumu supren en la historio",
"Toggle webcam on/off": "Ŝaltigu/malŝaltu retfilmilon",
"Toggle space panel": "Ŝaltigu panelon de aroj",
"Toggle hidden event visibility": "Ŝaltu la videblecon de kaŝita okazoj",
"Jump to first message": "Saltu al la unua mesaĝo",
"Jump to last message": "Saltu al la lasta mesaĝo",
"Undo edit": "Malfari redakton",
"Previous recently visited room or space": "Antaŭa lastatempe vizitita ĉambro aŭ aro",
"Redo edit": "Refari redakton",
"Next recently visited room or space": "Poste lastatempe vizitita ĉambro aŭ aro",
"Switch to space by number": "Ŝanĝu al aro per nombro",
"Open user settings": "Malfermu uzantajn agordojn",
"Change input device": "Ŝanĝu enigan aparaton",
"pause voice broadcast": "paŭzi voĉan elsendon",
"resume voice broadcast": "rekomenci voĉan elsendon",
"Go live": "Iru vivi",
"Yes, end my recording": "Jes, ĉesigu mian registradon",
"If you start listening to this live broadcast, your current live broadcast recording will be ended.": "Se vi komencas aŭskulti ĉi tiun vivan elsendon, via nuna viva elsendo registrado estos finita.",
"Listen to live broadcast?": "Aŭskulti vivan elsendon?",
"Yes, stop broadcast": "Jes, ĉesu elsendon",
"The above, but in any room you are joined or invited to as well": "La supre, sed en iu ajn ĉambro vi estas kunigita aŭ invitata ankaŭ al",
"Remove, ban, or invite people to your active room, and make you leave": "Forigu, forbaru aŭ invitu personojn al via aktiva ĉambro, kaj foriru vin",
"Remove, ban, or invite people to this room, and make you leave": "Forigu, forbaru aŭ invitu personojn al ĉi tiu ĉambro, kaj foriru vin",
"Reset bearing to north": "Restarigu la lagron norden",
"Mapbox logo": "Mapbox-emblemo",
"Location not available": "Loko ne havebla",
"Find my location": "Trovu mian lokon",
"Exit fullscreen": "Eliru plenekrano",
"Enter fullscreen": "Plenekrano",
"This homeserver is not configured correctly to display maps, or the configured map server may be unreachable.": "Ĉi tiu hejmservilo ne estas agordita ĝuste por montri mapojn, aŭ la agordita mapservilo povas esti neatingebla.",
"This homeserver is not configured to display maps.": "Ĉi tiu hejmservilo ne estas agordita por montri mapojn.",
"The user's homeserver does not support the version of the space.": "La hejmservilo de la uzanto ne subtenas la version de la aro.",
"User may or may not exist": "Uzanto povas aŭ ne ekzisti",
"User does not exist": "Uzanto ne ekzistas",
"User is already in the room": "Uzanto jam estas en la ĉambro",
"User is already in the space": "Uzanto jam estas en la aro",
"User is already invited to the room": "Uzanto jam estas invitita al la ĉambro",
"User is already invited to the space": "Uzanto jam estas invitita al la aro",
"You do not have permission to invite people to this space.": "Vi ne havas permeson inviti personojn al ĉi tiu aro.",
"In %(spaceName)s and %(count)s other spaces.|one": "En %(spaceName)s kaj %(count)s alia aro.",
"In %(spaceName)s and %(count)s other spaces.|zero": "En aro %(spaceName)s.",
"In %(spaceName)s and %(count)s other spaces.|other": "En %(spaceName)s kaj %(count)s aliaj aroj.",
"%(spaceName)s and %(count)s others|one": "%(spaceName)s kaj %(count)s alia",
"%(spaceName)s and %(count)s others|zero": "%(spaceName)s",
"%(spaceName)s and %(count)s others|other": "%(spaceName)s kaj %(count)s aliaj",
"In spaces %(space1Name)s and %(space2Name)s.": "En aroj %(space1Name)s kaj %(space2Name)s.",
"%(space1Name)s and %(space2Name)s": "%(space1Name)s kaj %(space2Name)s",
"30s forward": "30s. antaŭen",
"30s backward": "30s. reen",
"%(senderName)s removed %(targetName)s": "%(senderName)s forigis %(targetName)s",
"%(senderName)s removed %(targetName)s: %(reason)s": "%(senderName)s forigis %(targetName)s: %(reason)s",
"Developer command: Discards the current outbound group session and sets up new Olm sessions": "Komando de programisto: Forĵetas la nunan eliran grupsesion kaj starigas novajn Olm-salutaĵojn",
"Command error: Unable to handle slash command.": "Komanda eraro: Ne eblas trakti oblikvan komandon.",
"%(minutes)sm %(seconds)ss": "%(minutes)sm. %(seconds)ss.",
"%(hours)sh %(minutes)sm %(seconds)ss": "%(hours)sh. %(minutes)sm. %(seconds)ss.",
"%(hours)sh %(minutes)sm %(seconds)ss left": "%(hours)sh. %(minutes)sm. %(seconds)ss. restas",
"%(minutes)sm %(seconds)ss left": "%(minutes)sm. %(seconds)ss. restas",
"%(value)sd": "%(value)st.",
"%(value)sh": "%(value)sh.",
"%(value)sm": "%(value)sm.",
"%(value)ss": "%(value)ss.",
"%(days)sd %(hours)sh %(minutes)sm %(seconds)ss": "%(days)st. %(hours)sh. %(minutes)sm. %(seconds)ss.",
"What location type do you want to share?": "Kiel vi volas kunhavigi vian lokon?",
"My live location": "Mia realtempa loko",
"My current location": "Mia nuna loko",
"%(brand)s could not send your location. Please try again later.": "%(brand)s ne povis sendi vian lokon. Bonvolu reprovi poste.",
"We couldn't send your location": "Ni ne povis sendi vian lokon",
"Timed out trying to fetch your location. Please try again later.": "Tempo elĉerpita akiri vian lokon. Bonvolu reprovi poste.",
"Failed to fetch your location. Please try again later.": "Via loko ne eblis akiri. Bonvolu reprovi poste.",
"Share location": "Kunhavigi lokon",
"Could not fetch location": "Loko ne eblis akiri",
"Location": "Loko",
"Shared a location: ": "Kunhavis lokon: ",
"Shared their location: ": "Kunhavis sian lokon: ",
"Live Location Sharing": "Viva Loka Kundivido",
"Reset password": "Restarigu vian pasvorton",
"Reset your password": "Restarigu vian pasvorton",
"Confirm new password": "Konfirmu novan pasvorton",
"Sign out of all devices": "Elsaluti en ĉiuj aparatoj",
"You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device.": "Vi estis elsalutita el ĉiuj aparatoj kaj ne plu ricevos puŝajn sciigojn. Por reŝalti sciigojn, ensalutu denove sur ĉiu aparato.",
"Someone already has that username, please try another.": "Iu jam havas tiun uzantnomon, bonvolu provi alian.",
"That e-mail address or phone number is already in use.": "Tiu retpoŝtadreso aŭ telefonnumero jam estas uzataj.",
"Proceed with reset": "Procedu por restarigi",
"Verify with Security Key or Phrase": "Kontrolu per Sekureca ŝlosilo aŭ frazo",
"Verify with Security Key": "Kontrolu per Sekureca ŝlosilo",
"Verify with another device": "Kontrolu per alia aparato",
"Your new device is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Via nova aparato nun estas kontrolita. Ĝi havas aliron al viaj ĉifritaj mesaĝoj, kaj aliaj vidos ĝin kiel fidinda.",
"Your new device is now verified. Other users will see it as trusted.": "Via nova aparato nun estas kontrolita. Aliaj vidos ĝin kiel fidinda.",
"Without verifying, you won't have access to all your messages and may appear as untrusted to others.": "Sen kontrolado, vi ne havos aliron al ĉiuj viaj mesaĝoj kaj povas aperi kiel nefidinda al aliaj.",
"I'll verify later": "Kontrolu poste",
"Please only proceed if you're sure you've lost all of your other devices and your security key.": "Bonvolu daŭrigi nur se vi certas, ke vi perdis ĉiujn viajn aliajn aparatojn kaj vian Sekurecan ŝlosilon.",
"Follow the instructions sent to <b>%(email)s</b>": "Sekvu la instrukciojn senditajn al <b>%(email)s</b>",
"Wrong email address?": "Ĉu malĝusta retpoŝtadreso?",
"Did not receive it?": "Ĉu vi ne ricevis?",
"Re-enter email address": "Reenigu retpoŝtadreson",
"Verification link email resent!": "Retpoŝto de konfirmligo resendita!",
"Send email": "Sendu retpoŝton",
"Enter your email to reset password": "Enigu vian retpoŝtadreson por restarigi pasvorton",
"<b>%(homeserver)s</b> will send you a verification link to let you reset your password.": "<b>%(homeserver)s</b> sendos al vi konfirman ligilon por permesi al vi restarigi vian pasvorton.",
"The email address doesn't appear to be valid.": "La retpoŝtadreso ŝajnas ne valida.",
"Sign in instead": "Aliĝu anstataŭe",
"Verify your email to continue": "Kontrolu vian retpoŝtadreson por daŭrigi",
"We need to know its you before resetting your password.\n Click the link in the email we just sent to <b>%(email)s</b>": "Ni devas scii, ke ĝi estas vi antaŭ ol restarigi vian pasvorton.\n Alklaku la ligilon en la retpoŝto, kiun ni ĵus sendis al <b>%(email)s</b>",
"Store your Security Key somewhere safe, like a password manager or a safe, as it's used to safeguard your encrypted data.": "Konservu vian Sekurecan ŝlosilon ie sekure, kiel pasvortadministranto aŭ monŝranko, ĉar ĝi estas uzata por protekti viajn ĉifritajn datumojn.",
"We'll generate a Security Key for you to store somewhere safe, like a password manager or a safe.": "Ni generos Sekurecan ŝlosilon por ke vi stoku ie sekura, kiel pasvort-administranto aŭ monŝranko.",
"Enter a security phrase only you know, as it's used to safeguard your data. To be secure, you shouldn't re-use your account password.": "Enigu sekurecan frazon nur vi konas, ĉar ĝi estas uzata por protekti viajn datumojn. Por esti sekura, vi ne devus reuzi vian konton pasvorton.",
"%(downloadButton)s or %(copyButton)s": "%(downloadButton)s aŭ %(copyButton)s",
"Accessibility": "Alirebleco",
"Toggle Code Block": "Ŝaltigu kodblokon",
"Toggle Link": "Ŝaltigu la formatadon de ligilo",
"Next unread room or DM": "Sekva nelegita konversacio",
"Previous unread room or DM": "Antaŭa nelegita konversacio",
"Next room or DM": "Sekva konversacio",
"Previous room or DM": "Antaŭa konversacio",
"Unfortunately we're unable to start a recording right now. Please try again later.": "Bedaŭrinde ni ne povas komenci registradon nun. Bonvolu reprovi poste.",
"Connection error": "eraro de konekto",
"You have unverified sessions": "Vi havas nekontrolitajn salutaĵojn",
"Export chat": "Eksporti babilejon",
"Files": "Dosieroj",
"Other sessions": "Aliaj salutaĵoj",
"Verified sessions": "Kontrolitaj salutaĵoj",
"Renaming sessions": "Renomi salutaĵojn",
"Sessions": "Salutaĵoj",
"Close sidebar": "Fermu la flanka kolumno",
"Sidebar": "Flanka kolumno",
"Sign out of %(count)s sessions|one": "Elsaluti el %(count)s salutaĵo",
"Sign out of %(count)s sessions|other": "Elsaluti el %(count)s salutaĵoj",
"%(count)s sessions selected|one": "%(count)s salutaĵo elektita",
"%(count)s sessions selected|other": "%(count)s salutaĵoj elektitaj",
"No sessions found.": "Neniuj salutaĵoj trovitaj.",
"No inactive sessions found.": "Neniuj neaktivaj salutaĵoj trovitaj.",
"No unverified sessions found.": "Neniuj nekontrolitaj salutaĵoj trovitaj.",
"No verified sessions found.": "Neniuj kontrolitaj salutaĵoj trovitaj.",
"Consider signing out from old sessions (%(inactiveAgeDays)s days or older) you don't use anymore.": "Konsideru elsaluti de malnovaj salutaĵoj (%(inactiveAgeDays)s tagoj aŭ pli malnovaj), kiujn vi ne plu uzas.",
"Verify your sessions for enhanced secure messaging or sign out from those you don't recognize or use anymore.": "Por plia sekura komunikado, kontrolu viajn salutaĵojn aŭ elsalutu el ili se vi ne plu rekonas ilin.",
"Removing inactive sessions improves security and performance, and makes it easier for you to identify if a new session is suspicious.": "Forigi neaktivajn salutaĵojn plibonigas sekurecon, rendimenton kaj detekton de dubindaj novaj salutaĵoj.",
"Inactive sessions are sessions you have not used in some time, but they continue to receive encryption keys.": "Neaktivaj salutaĵoj estas salutaĵoj, kiujn vi ne uzis dum kelka tempo, sed ili daŭre ricevas ĉifrajn ŝlosilojn.",
"Inactive sessions": "Neaktivaj salutaĵoj",
"Unverified sessions": "Nekontrolitaj salutaĵoj",
"From the beginning": "De la komenco",
"Current Timeline": "Nuna historio",
"Use rich text instead of Markdown in the message composer. Plain text mode coming soon.": "Uzu riĉan tekston anstataŭ Markdown en la mesaĝkomponilo. Plata teksta reĝimo baldaŭ venos.",
"Plain Text": "Plata Teksto",
"Show HTML representation of room topics": "Montru HTML-prezenton de ĉambrotemoj",
"Creating HTML...": "Kreante HTML...",
"HTML": "HTML",
"Doesn't look like valid JSON.": "Ŝajnas ne esti valida JSON.",
"JSON": "JSON",
"Include Attachments": "Inkluzivi Aldonaĵojn",
"Size Limit": "Grandeca Limo",
"Media omitted - file size limit exceeded": "Amaskomunikilaro preterlasis - dosiero tro granda",
"Select from the options below to export chats from your timeline": "Elektu el la subaj elektoj por eksporti babilojn el via historio",
"Public rooms": "Publikajn ĉambrojn",
"<a>Give feedback</a>": "<a>Doni komentojn</a>",
"Results not as expected? Please <a>give feedback</a>.": "Rezultoj ne kiel atenditaj? Bonvolu <a>doni komentojn</a>.",
"Show details": "Montri detalojn",
"Hide details": "Kaŝi detalojn",
"Sign out of this session": "Eliri el ĉi tiu salutaĵo",
"Receive push notifications on this session.": "Ricevi puŝajn sciigojn pri ĉi tiu sesio.",
"Push notifications": "Puŝaj sciigoj",
"IP address": "IP-adreso",
"Browser": "Retumilo",
"Are you sure you want to stop your live broadcast?This will end the broadcast and the full recording will be available in the room.": "Ĉu vi certas, ke vi volas fini la elsendon? Ĉi tio finos la transdonon kaj provizos la plenan registradon en la ĉambro.",
"Add privileged users": "Aldoni rajtigitan uzanton",
"Number of messages": "Nombro da mesaĝoj",
"Number of messages can only be a number between %(min)s and %(max)s": "Nombro da mesaĝoj povas esti nur nombro inter %(min)s kaj %(max)s",
"Specify a number of messages": "Indiki kelkajn mesaĝojn",
"New ways to ignore people": "Novaj manieroj ignori personojn",
"Currently experimental.": "Nuntempe eksperimenta.",
"Jump to date (adds /jumptodate and jump to date headers)": "Salti ĝis nun (aldonas /jumptodate kaj saltu ĝis nun kapliniojn)",
"Send read receipts": "Sendi legitajn kvitanojn",
"New group call experience": "La nova grupvoka sperto",
"Favourite Messages": "Ŝatataj Mesaĝoj",
"Use new session manager": "Uzi nova administrado de salutaĵoj",
"New session manager": "Nova administrado de salutaĵoj"
}

View File

@ -241,14 +241,14 @@
"Thu": "jue.",
"Fri": "vie.",
"Sat": "sáb.",
"Jan": "en.",
"Jan": "ene.",
"Feb": "febr.",
"Mar": "mzo.",
"Apr": "abr.",
"May": "my.",
"May": "may.",
"Jun": "jun.",
"Jul": "jul.",
"Aug": "ag.",
"Aug": "ago.",
"Call Failed": "Llamada fallida",
"Sep": "sept.",
"Oct": "oct.",
@ -351,7 +351,6 @@
"Enable widget screenshots on supported widgets": "Activar capturas de pantalla de accesorios en los accesorios que lo permitan",
"Drop file here to upload": "Suelta aquí el archivo para enviarlo",
"This event could not be displayed": "No se ha podido mostrar este evento",
"Key request sent.": "Solicitud de clave enviada.",
"Demote yourself?": "¿Quitarte permisos a ti mismo?",
"You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.": "No podrás deshacer este cambio ya que estás quitándote permisos a ti mismo, si eres el último usuario con privilegios de la sala te resultará imposible recuperarlos.",
"Demote": "Quitar permisos",
@ -855,9 +854,9 @@
"You'll lose access to your encrypted messages": "Perderás acceso a tus mensajes cifrados",
"Are you sure you want to sign out?": "¿Estás seguro de que quieres salir?",
"Message edits": "Ediciones del mensaje",
"Please fill why you're reporting.": "Por favor, explica por qué estás reportando.",
"Report Content to Your Homeserver Administrator": "Reportar contenido al administrador de tu servidor base",
"Send report": "Enviar reporte",
"Please fill why you're reporting.": "Por favor, explica por qué estás denunciando.",
"Report Content to Your Homeserver Administrator": "Denunciar contenido al administrador de tu servidor base",
"Send report": "Enviar denuncia",
"Room Settings - %(roomName)s": "Configuración de la sala - %(roomName)s",
"Upgrading this room requires closing down the current instance of the room and creating a new room in its place. To give room members the best possible experience, we will:": "Actualizar esta sala requiere cerrar la instancia actual de esta sala y crear una nueva sala en su lugar. Para dar a los miembros de la sala la mejor experiencia, haremos lo siguiente:",
"Upgrade private room": "Actualizar sala privada",
@ -994,7 +993,6 @@
"Backup is not signed by any of your sessions": "La copia de seguridad no está firmada por ninguna de tus sesiones",
"This backup is trusted because it has been restored on this session": "Esta copia de seguridad es de confianza porque ha sido restaurada en esta sesión",
"Your keys are <b>not being backed up from this session</b>.": "<b>No se está haciendo una copia de seguridad de tus claves en esta sesión</b>.",
"Clear notifications": "Limpiar notificaciones",
"Enable desktop notifications for this session": "Activa las notificaciones de escritorio para esta sesión",
"Enable audible notifications for this session": "Activar notificaciones sonoras para esta sesión",
"Checking server": "Comprobando servidor",
@ -1025,7 +1023,7 @@
"Could not find user in room": "No se ha encontrado el usuario en la sala",
"Please supply a widget URL or embed code": "Por favor, proporciona la URL del accesorio o un código de incrustación",
"Displays information about a user": "Muestra información sobre un usuario",
"Send a bug report with logs": "Envíe un informe de errores con los registros",
"Send a bug report with logs": "Enviar un informe de errores con los registros",
"%(senderDisplayName)s changed the room name from %(oldRoomName)s to %(newRoomName)s.": "%(senderDisplayName)s cambió el nombre de la sala %(oldRoomName)s a %(newRoomName)s.",
"%(senderName)s added the alternative addresses %(addresses)s for this room.|other": "%(senderName)s añadió las direcciones alternativas %(addresses)s para esta sala.",
"%(senderName)s added the alternative addresses %(addresses)s for this room.|one": "%(senderName)s añadió la dirección alternativa %(addresses)s para esta sala.",
@ -1042,8 +1040,6 @@
"Done": "Listo",
"Support adding custom themes": "Soporta la adición de temas personalizados",
"Show info about bridges in room settings": "Incluir información sobre puentes en la configuración de las salas",
"Order rooms by name": "Ordenar las salas por nombre",
"Show rooms with unread notifications first": "Colocar primero las salas con notificaciones no leídas",
"Show shortcuts to recently viewed rooms above the room list": "Incluir encima de la lista de salas unos atajos a las últimas salas que hayas visto",
"Manually verify all remote sessions": "Verificar manualmente todas las sesiones remotas",
"Cancelling…": "Anulando…",
@ -1136,9 +1132,6 @@
"Everyone in this room is verified": "Todos los participantes en esta sala están verificados",
"Edit message": "Editar mensaje",
"Mod": "Mod",
"Your key share request has been sent - please check your other sessions for key share requests.": "La solicitud de intercambio de claves ha sido enviada. Por favor, comprueba en sus otras sesiones si hay solicitudes de intercambio de claves.",
"Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.": "Solicitudes para compartir claves son enviadas a sus otras sesiones de forma automática. Si ha rechazado o descartado la solicitud de compartir claves en sus otras sesiones, haga clic aquí para solicitar de nuevo las claves de esta sesión.",
"If your other sessions do not have the key for this message you will not be able to decrypt them.": "Si tus otras sesiones no tienen la clave para este mensaje no podrán descifrarlo.",
"Rotate Right": "Girar a la derecha",
"Language Dropdown": "Lista selección de idiomas",
"%(severalUsers)smade no changes %(count)s times|other": "%(severalUsers)s %(count)s veces no efectuarion cambios",
@ -1220,8 +1213,6 @@
"<userName/> invited you": "<userName/> te ha invitado",
"You're previewing %(roomName)s. Want to join it?": "Esto es una vista previa de %(roomName)s. ¿Te quieres unir?",
"%(roomName)s can't be previewed. Do you want to join it?": "La sala %(roomName)s no permite previsualización. ¿Quieres unirte?",
"<requestLink>Re-request encryption keys</requestLink> from your other sessions.": "<requestLink>Solicitar otra vez las claves de cifrado</requestLink> de tus otras sesiones.",
"This message cannot be decrypted": "Este mensaje no puede ser descifrado",
"Encrypted by an unverified session": "Cifrado por una sesión no verificada",
"Unencrypted": "Sin cifrar",
"Encrypted by a deleted session": "Cifrado por una sesión eliminada",
@ -1380,7 +1371,7 @@
"Confirm this user's session by comparing the following with their User Settings:": "Confirma la sesión de este usuario comparando lo siguiente con su configuración:",
"If they don't match, the security of your communication may be compromised.": "Si no coinciden, la seguridad de su comunicación puede estar comprometida.",
"Your homeserver doesn't seem to support this feature.": "Tu servidor base no parece soportar esta funcionalidad.",
"Reporting this message will send its unique 'event ID' to the administrator of your homeserver. If messages in this room are encrypted, your homeserver administrator will not be able to read the message text or view any files or images.": "Reportar este mensaje enviará su único «event ID al administrador de tu servidor base. Si los mensajes en esta sala están cifrados, el administrador de tu servidor no podrá leer el texto del mensaje ni ver ningún archivo o imagen.",
"Reporting this message will send its unique 'event ID' to the administrator of your homeserver. If messages in this room are encrypted, your homeserver administrator will not be able to read the message text or view any files or images.": "Denunciar este mensaje enviará su único «event ID al administrador de tu servidor base. Si los mensajes en esta sala están cifrados, el administrador de tu servidor no podrá leer el texto del mensaje ni ver ningún archivo o imagen.",
"Command Help": "Ayuda del comando",
"Verification Request": "Solicitud de verificación",
"Restoring keys from backup": "Restaurando las claves desde copia de seguridad",
@ -1395,7 +1386,7 @@
"<b>Warning</b>: you should only set up key backup from a trusted computer.": "<b>Advertencia</b>: deberías configurar la copia de seguridad de claves solamente usando un ordenador de confianza.",
"<b>Warning</b>: You should only set up key backup from a trusted computer.": "<b>Advertencia</b>: Configura la copia de seguridad de claves solo si estás usando un ordenador de confianza.",
"Resend %(unsentCount)s reaction(s)": "Reenviar %(unsentCount)s reacción(es)",
"Report Content": "Reportar contenido",
"Report Content": "Denunciar contenido",
"Remove for everyone": "Eliminar para todos",
"This homeserver would like to make sure you are not a robot.": "A este servidor le gustaría asegurarse de que no eres un robot.",
"Country Dropdown": "Seleccione país",
@ -2424,13 +2415,13 @@
"%(senderName)s changed the <a>pinned messages</a> for the room.": "%(senderName)s cambió los <a>mensajes fijados</a> de la sala.",
"Disagree": "No estoy de acuerdo",
"[number]": "[número]",
"Report": "Reportar",
"Report": "Denunciar",
"Collapse reply thread": "Ocultar respuestas",
"Show preview": "Mostrar vista previa",
"View source": "Ver código fuente",
"Forward": "Reenviar",
"Settings - %(spaceName)s": "Ajustes - %(spaceName)s",
"Report the entire room": "Reportar la sala entera",
"Report the entire room": "Denunciar la sala entera",
"Spam or propaganda": "Publicidad no deseada o propaganda",
"Illegal Content": "Contenido ilegal",
"Toxic Behaviour": "Comportamiento tóxico",
@ -2633,7 +2624,6 @@
"Are you sure you want to make this encrypted room public?": "¿Seguro que quieres activar el cifrado en esta sala pública?",
"To avoid these issues, create a <a>new encrypted room</a> for the conversation you plan to have.": "Para evitar estos problemas, crea una <a>nueva sala cifrada</a> para la conversación que quieras tener.",
"Are you sure you want to add encryption to this public room?": "¿Seguro que quieres activar el cifrado en esta sala pública?",
"Threaded messaging": "Mensajes en hilos",
"Thread": "Hilo",
"The above, but in any room you are joined or invited to as well": "Lo de arriba, pero en cualquier sala en la que estés o te inviten",
"The above, but in <Room /> as well": "Lo de arriba, pero también en <Room />",
@ -2924,7 +2914,7 @@
"Verify this device by confirming the following number appears on its screen.": "Verifica este dispositivo confirmando que el siguiente número aparece en pantalla.",
"Confirm the emoji below are displayed on both devices, in the same order:": "Confirma que los siguientes emojis aparecen en los dos dispositivos y en el mismo orden:",
"Automatically send debug logs on decryption errors": "Enviar los registros de depuración automáticamente de fallos al descifrar",
"Show join/leave messages (invites/removes/bans unaffected)": "Mostrar mensajes de entrada y salida de la sala (seguirás viendo invitaciones/gente quitada/vetos)",
"Show join/leave messages (invites/removes/bans unaffected)": "Mostrar mensajes de entrada y salida de la sala (seguirás viendo invitaciones, gente quitada y vetos)",
"Room members": "Miembros de la sala",
"Back to chat": "Volver a la conversación",
"Remove, ban, or invite people to your active room, and make you leave": "Quitar, vetas o invitar personas a tu sala activa, y hacerte salir",
@ -2989,7 +2979,6 @@
"%(senderName)s has ended a poll": "%(senderName)s ha terminado una encuesta",
"%(senderName)s has started a poll - %(pollQuestion)s": "%(senderName)s ha empezado una encuesta %(pollQuestion)s",
"%(senderName)s has shared their location": "%(senderName)s ha compartido su ubicación",
"Show extensible event representation of events": "Mostrar una representación extensible de los eventos",
"Let moderators hide messages pending moderation.": "Permitir a los moderadores ocultar mensajes a la espera de revisión.",
"Redo edit": "Rehacer edición",
"Force complete": "Forzar a que termine",
@ -3039,7 +3028,6 @@
"You don't have permission to view messages from before you joined.": "No tienes permisos para ver mensajes enviados antes de que te unieras.",
"You don't have permission to view messages from before you were invited.": "No tienes permisos para ver mensajes enviados antes de que te invitaran.",
"Click for more info": "Haz clic para más info.",
"How can I leave the beta?": "¿Cómo salgo de la beta?",
"%(space1Name)s and %(space2Name)s": "%(space1Name)s y %(space2Name)s",
"Scroll up in the timeline": "Subir en la línea de tiempo",
"Navigate to previous message in composer history": "Ir al anterior mensaje en el historial del editor",
@ -3202,7 +3190,6 @@
"View older version of %(spaceName)s.": "Ver versión antigua de %(spaceName)s.",
"Upgrade this space to the recommended room version": "Actualiza la versión de este espacio a la recomendada",
"Threads help keep conversations on-topic and easy to track. <a>Learn more</a>.": "Los hilos ayudan a mantener las conversaciones centradas en un único tema y las hace fáciles de seguir. <a>Más información</a>.",
"How can I start a thread?": "¿Cómo puedo empezar un hilo?",
"The person who invited you has already left.": "La persona que te invitó ya no está aquí.",
"Sorry, your homeserver is too old to participate here.": "Lo siento, tu servidor base es demasiado antiguo. No puedes participar aquí.",
"There was an error joining.": "Ha ocurrido un error al entrar.",
@ -3275,8 +3262,6 @@
"Audio devices": "Dispositivos de audio",
"Start messages with <code>/plain</code> to send without markdown and <code>/md</code> to send with.": "Empieza los mensajes con <code>/plain</code> para enviarlos sin Markdown, y <code>/md</code> para enviarlos con Markdown.",
"Enable Markdown": "Activar Markdown",
"To leave, return to this page and use the “%(leaveTheBeta)s” button.": "Pasa salir, vuelve a esta página y dale al botón de «%(leaveTheBeta)s».",
"Use “%(replyInThread)s” when hovering over a message.": "Usa «%(replyInThread)s» al pasar el ratón sobre un mensaje.",
"Keep discussions organised with threads.": "Mantén tus conversaciones organizadas con los hilos.",
"If you want to retain access to your chat history in encrypted rooms, set up Key Backup or export your message keys from one of your other devices before proceeding.": "Si deseas mantener acceso a tu historial de conversación en salas encriptadas, configura copia de llaves o exporta tus claves de mensaje desde uno de tus otros dispositivos antes de proceder.",
"Signing out your devices will delete the message encryption keys stored on them, making encrypted chat history unreadable.": "Cerrar sesión en tus dispositivos causará que las claves de encriptado almacenadas en ellas se eliminen, haciendo que el historial de la conversación encriptada sea imposible de leer.",
@ -3358,7 +3343,7 @@
"Toggle attribution": "Mostrar/ocultar fuente",
"You need to have the right permissions in order to share locations in this room.": "Debes tener el permiso correspondiente para compartir ubicaciones en esta sala.",
"Stop and close": "Parar y cerrar",
"You can't disable this later. The room will be encrypted but the embedded call will not.": "No lo podrás desactivar después. Esta sala se cifrará, pero la llamada integrada no.",
"You can't disable this later. The room will be encrypted but the embedded call will not.": "No lo podrás desactivar después. Esta sala se cifrará, pero no así la llamada integrada.",
"Online community members": "Miembros de comunidades online",
"Coworkers and teams": "Compañeros de trabajo y equipos",
"Friends and family": "Familia y amigos",
@ -3428,7 +3413,6 @@
"Interactively verify by emoji": "Verificar interactivamente usando emojis",
"Manually verify by text": "Verificar manualmente usando un texto",
"View all": "Ver todas",
"Improve your account security by following these recommendations": "Mejora la seguridad de tu cuenta siguiendo estas recomendaciones",
"Security recommendations": "Consejos de seguridad",
"Filter devices": "Filtrar dispositivos",
"Inactive for %(inactiveAgeDays)s days or longer": "Inactiva durante %(inactiveAgeDays)s días o más",
@ -3440,7 +3424,6 @@
"No inactive sessions found.": "No se ha encontrado ninguna sesión inactiva.",
"No unverified sessions found.": "No se ha encontrado ninguna sesión sin verificar.",
"No verified sessions found.": "No se ha encontrado ninguna sesión verificada.",
"Consider signing out from old sessions (%(inactiveAgeDays)s days or older) you don't use anymore": "Considera cerrar las sesiones antiguas (usadas hace más de %(inactiveAgeDays)s)",
"Inactive sessions": "Sesiones inactivas",
"Verify your sessions for enhanced secure messaging or sign out from those you don't recognize or use anymore.": "Verifica tus sesiones para una mensajería más segura, o cierra las que no reconozcas o hayas dejado de usar.",
"Unverified sessions": "Sesiones sin verificar",
@ -3484,7 +3467,6 @@
"Failed to set pusher state": "Fallo al establecer el estado push",
"Sign out of this session": "Cerrar esta sesión",
"Receive push notifications on this session.": "Recibir notificaciones push en esta sesión.",
"Sign out all other sessions": "Cerrar el resto de sesiones",
"You do not have sufficient permissions to change this.": "No tienes suficientes permisos para cambiar esto.",
"%(brand)s is end-to-end encrypted, but is currently limited to smaller numbers of users.": "%(brand)s está cifrado de extremo a extremo, pero actualmente está limitado a unos pocos participantes.",
"Enable %(brand)s as an additional calling option in this room": "Activar %(brand)s como una opción para las llamadas de esta sala",
@ -3516,7 +3498,6 @@
"Ongoing call": "Llamada en curso",
"Video call (%(brand)s)": "Videollamada (%(brand)s)",
"Video call (Jitsi)": "Videollamada (Jitsi)",
"%(selectedDeviceCount)s sessions selected": "%(selectedDeviceCount)s sesiones seleccionadas",
"Unknown session type": "Sesión de tipo desconocido",
"Web session": "Sesión web",
"Mobile session": "Sesión móvil",
@ -3602,5 +3583,76 @@
"Noise suppression": "Supresión de ruido",
"Echo cancellation": "Cancelación de eco",
"When enabled, the other party might be able to see your IP address": "Si lo activas, la otra parte podría ver tu dirección IP",
"Go live": "Empezar directo"
"Go live": "Empezar directo",
"Right panel stays open": "El panel derecho se mantiene abierto",
"Currently experimental.": "Actualmente en fase experimental.",
"New ways to ignore people": "Nuevas maneras de ignorar a otras personas",
"Use rich text instead of Markdown in the message composer. Plain text mode coming soon.": "Usar el editor de texto enriquecido en lugar de Markdown en la barra de escritura. El modo de texto plano estará disponible próximamente.",
"Rich text editor": "Editor de texto enriquecido",
"Threaded messages": "Hilos de mensajes",
"In rooms that support moderation, the “Report” button will let you report abuse to room moderators.": "En las salas que sean compatible con la moderación, el botón de «Denunciar» avisará a los moderadores de la sala.",
"Report to moderators": "Denunciar ante los moderadores",
"Verification link email resent!": "Email con enlace de verificación reenviado.",
"Did not receive it?": "¿No lo has recibido?",
"Re-enter email address": "Volver a escribir dirección de email",
"Wrong email address?": "¿Dirección de email equivocada?",
"Follow the instructions sent to <b>%(email)s</b>": "Sigue las instrucciones enviadas a <b>%(email)s</b>",
"Sign out of all devices": "Cerrar sesión en todos los dispositivos",
"Too many attempts in a short time. Retry after %(timeout)s.": "Demasiados intentos en poco tiempo. Inténtalo de nuevo en %(timeout)s.",
"Too many attempts in a short time. Wait some time before trying again.": "Demasiados intentos en poco tiempo. Espera un poco antes de volverlo a intentar.",
"Thread root ID: %(threadRootId)s": "ID del hilo raíz: %(threadRootId)s",
"Mark as read": "Marcar como leído",
"<w>WARNING:</w> <description/>": "<w>ADVERTENCIA :</w> <description/>",
"Unable to decrypt message": "No se ha podido descifrar el mensaje",
"We were unable to start a chat with the other user.": "No se ha podido iniciar una conversación con el otro usuario.",
"Error starting verification": "Error al empezar la verificación",
"Text": "Texto",
"Create a link": "Crear un enlace",
"Link": "Enlace",
"Change layout": "Cambiar disposición",
"This message could not be decrypted": "No se ha podido descifrar este mensaje",
" in <strong>%(room)s</strong>": " en <strong>%(room)s</strong>",
"Resend key requests": "Volver a solicitar las claves",
"Unfortunately, there are no other verified devices to request decryption keys from. Signing in and verifying other devices may help avoid this situation in the future.": "Desgraciadamente, no hay ningún otro dispositivo verificado al que solicitarle las claves para descifrar. En el futuro, inicia sesión y verifica otros dispositivos para evitar esta situación.",
"Some messages could not be decrypted": "No se han podido descifrar algunos mensajes",
"View your device list": "Ver la lista de tus dispositivos",
"This device is requesting decryption keys from your other devices. Opening one of your other devices may speed this up.": "Este dispositivo está solicitando a tus otros dispositivos las claves para descifrar los mensajes. Usa otro dispositivo para acelerar el proceso.",
"Open another device to load encrypted messages": "Usa otro dispositivo para cargar los mensajes cifrados",
"This device was unable to decrypt some messages because it has not been verified yet.": "Este dispositivo no pudo descifrar algunos mensajes, porque todavía no ha sido verificado.",
"Verify this device to access all messages": "Verifica este dispositivo para acceder a todos tus mensajes",
"Please wait as we try to decrypt your messages. This may take a few moments.": "Por favor, espera mientras intentamos descifrar tus mensajes. Esto puede tardar unos instantes.",
"Decrypting messages...": "Descifrando mensajes…",
"Improve your account security by following these recommendations.": "Mejora la seguridad de tu cuenta siguiendo estas recomendaciones.",
"Sign out of %(count)s sessions|one": "Cerrar %(count)s sesión",
"Sign out of %(count)s sessions|other": "Cerrar %(count)s sesiones",
"%(count)s sessions selected|one": "%(count)s sesión seleccionada",
"%(count)s sessions selected|other": "%(count)s sesiones seleccionadas",
"Show details": "Mostrar detalles",
"Hide details": "Ocultar detalles",
"Sign out of all other sessions (%(otherSessionsCount)s)": "Cerrar el resto de sesiones (%(otherSessionsCount)s)",
"Feeling experimental? Try out our latest ideas in development. These features are not finalised; they may be unstable, may change, or may be dropped altogether. <a>Learn more</a>.": "¿Te apetece probar cosas experimentales? Aquí encontrarás nuestras ideas en desarrollo. No están terminadas, pueden ser inestables, cambiar o dejar de estar disponibles. <a>Más información</a>.",
"What's next for %(brand)s? Labs are the best way to get things early, test out new features and help shape them before they actually launch.": "¿Qué novedades se esperan en %(brand)s? La sección de experimentos es la mejor manera de ver las cosas antes de que se publiquen, probar nuevas funcionalidades y ayudar a mejorarlas antes de su lanzamiento.",
"Upcoming features": "Funcionalidades futuras",
"Apply": "Aplicar",
"Search users in this room…": "Buscar usuarios en esta sala…",
"Give one or multiple users in this room more privileges": "Otorga a uno o más usuarios privilegios especiales en esta sala",
"Add privileged users": "Añadir usuarios privilegiados",
"Requires compatible homeserver.": "Es necesario que el servidor base sea compatible.",
"Low bandwidth mode": "Modo de bajo ancho de banda",
"Hide notification dot (only display counters badges)": "Ocultar el punto indicador de notificaciones (solo mostrar un indicador con número)",
"Under active development. Can currently only be enabled via config.json": "Actulamente en desarrollo. Solo se puede activar editando config.json",
"Rust cryptography implementation": "Implementación de la criptografía en Rust",
"Under active development.": "Funcionalidad en desarrollo.",
"Favourite Messages": "Mensajes favoritos",
"Temporary implementation. Locations persist in room history.": "Implementación temporal. Las ubicaciones persisten en el historial de la sala.",
"Live Location Sharing": "Compartir ubicación en tiempo real",
"Under active development, cannot be disabled.": "En desarrollo, no se puede desactivar.",
"Sliding Sync mode": "Modo de sincronización progresiva",
"%(minutes)sm %(seconds)ss": "%(minutes)sm %(seconds)ss",
"%(hours)sh %(minutes)sm %(seconds)ss": "%(hours)sh %(minutes)sm %(seconds)ss",
"%(days)sd %(hours)sh %(minutes)sm %(seconds)ss": "%(days)sd %(hours)sh %(minutes)sm %(seconds)ss",
"You cant start a call as you are currently recording a live broadcast. Please end your live broadcast in order to start a call.": "No puedes empezar una llamada, porque estás grabando una retransmisión en directo. Por favor, finaliza tu retransmisión en directo para empezar la llamada.",
"Cant start a call": "No se ha podido empezar la llamada",
"Failed to read events": "No se han podido leer los eventos",
"Failed to send event": "No se ha podido enviar el evento"
}

View File

@ -31,8 +31,6 @@
"Changes your avatar in all rooms": "Muuda oma tunnuspilti kõikides jututubades",
"Ask this user to verify their session, or manually verify it below.": "Palu nimetatud kasutajal verifitseerida see sessioon või tee seda alljärgnevaga käsitsi.",
"Enable big emoji in chat": "Kasuta vestlustes suuri emoji'sid",
"Order rooms by name": "Järjesta jututoad nime alusel",
"Show rooms with unread notifications first": "Järjesta lugemata teadetega jututoad esimesena",
"Show shortcuts to recently viewed rooms above the room list": "Näita viimati külastatud jututubade viiteid jututubade loendi kohal",
"Enable message search in encrypted rooms": "Võta kasutusele sõnumite otsing krüptitud jututubades",
"When rooms are upgraded": "Kui jututubasid uuendatakse",
@ -368,8 +366,6 @@
"This room is end-to-end encrypted": "See jututuba on läbivalt krüptitud",
"Everyone in this room is verified": "Kõik kasutajad siin nututoas on verifitseeritud",
"Edit message": "Muuda sõnumit",
"Your key share request has been sent - please check your other sessions for key share requests.": "Sinu krüptovõtme jagamise päring on saadetud - palun oma teisi sessioone krüptovõtme jagamise päringu osas.",
"This message cannot be decrypted": "Seda sõnumit ei sa dekrüptida",
"Unencrypted": "Krüptimata",
"Encrypted by a deleted session": "Krüptitud kustutatud sessiooni poolt",
"Scroll to most recent messages": "Mine viimaste sõnumite juurde",
@ -1179,7 +1175,6 @@
"not found": "pole leitavad",
"Manage": "Halda",
"Enable": "Võta kasutusele",
"Clear notifications": "Eemalda kõik teavitused",
"Failed to change power level": "Õiguste muutmine ei õnnestunud",
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "Sa ei saa seda muudatust hiljem tagasi pöörata, sest annad teisele kasutajale samad õigused, mis sinul on.",
"Deactivate user?": "Kas deaktiveerime kasutajakonto?",
@ -1326,10 +1321,6 @@
"Discovery options will appear once you have added an email above.": "Otsinguvõimaluste loend kuvatakse, kui oled ülale sisestanud e-posti aadressi.",
"Discovery options will appear once you have added a phone number above.": "Otsinguvõimaluste loend kuvatakse, kui oled ülale sisestanud telefoninumbri.",
"Mod": "Moderaator",
"Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.": "Võtmete jagamise päringud saadetakse sinu teistele sessioonidele automaatselt. Kui sa oled mõnes muus sessioonis võtmete jagamise päringud tagasi lükanud või tühistanud, siis klõpsi siia võtmete uuesti pärimiseks selle sessiooni jaoks.",
"If your other sessions do not have the key for this message you will not be able to decrypt them.": "Kui sinu muudel sesioonidel pole selle sõnumi jaoks võtmeid, siis nad ei suuda ka sõnumit dekrüptida.",
"Key request sent.": "Võtmete jagamise päring on saadetud.",
"<requestLink>Re-request encryption keys</requestLink> from your other sessions.": "Küsi oma muudest sessioonidest <requestLink>krüptimisvõtmed uuesti</requestLink>.",
"The authenticity of this encrypted message can't be guaranteed on this device.": "Selle krüptitud sõnumi autentsus pole selles seadmes tagatud.",
"and %(count)s others...|other": "ja %(count)s muud...",
"and %(count)s others...|one": "ja üks muu...",
@ -2631,7 +2622,6 @@
"Message bubbles": "Jutumullid",
"Surround selected text when typing special characters": "Erimärkide sisestamisel märgista valitud tekst",
"Thread": "Jutulõng",
"Threaded messaging": "Sõnumid jutulõngana",
"Autoplay videos": "Esita automaatselt videosid",
"Autoplay GIFs": "Esita automaatselt liikuvaid pilte",
"The above, but in any room you are joined or invited to as well": "Ülaltoodu, aga samuti igas jututoas, millega oled liitunud või kuhu oled kutsutud",
@ -3048,7 +3038,6 @@
"Use <arrows/> to scroll": "Kerimiseks kasuta <arrows/>",
"Feedback sent! Thanks, we appreciate it!": "Tagasiside on saadetud. Täname, sellest on loodetavasti kasu!",
"Thank you for trying the beta, please go into as much detail as you can so we can improve it.": "Täname, et liitusid testprogrammiga. Et me saaksime võimalikult asjakohaseid täiendusi teha, palun jaga nii detailset teavet kui võimalik.",
"How can I leave the beta?": "Kuidas ma saan testimisprogrammist lahkuda?",
"%(space1Name)s and %(space2Name)s": "%(space1Name)s ja %(space2Name)s",
"%(oneUser)ssent %(count)s hidden messages|one": "%(oneUser)s saatis ühe peidetud sõnumi",
"%(oneUser)ssent %(count)s hidden messages|other": "%(oneUser)s saatis %(count)s peidetud sõnumit",
@ -3175,7 +3164,6 @@
"Force complete": "Sunni lõpetama",
"<%(count)s spaces>|zero": "<tühi string>",
"Call": "Helista",
"Show extensible event representation of events": "Näita laiendatavat sündmuste kujutamist",
"%(errcode)s was returned while trying to access the room or space. If you think you're seeing this message in error, please <issueLink>submit a bug report</issueLink>.": "Astumisel jututuppa või liitumisel kogukonnaga tekkis viga %(errcode)s. Kui sa arvad, et sellise põhjusega viga ei tohiks tekkida, siis palun <issueLink>koosta veateade</issueLink>.",
"Try again later, or ask a room or space admin to check if you have access.": "Proovi hiljem uuesti või küsi jututoa või kogukonna haldurilt, kas sul on ligipääs olemas.",
"This room or space is not accessible at this time.": "See jututuba või kogukond pole hetkel ligipääsetav.",
@ -3222,7 +3210,6 @@
"%(featureName)s Beta feedback": "%(featureName)s beetaversiooni tagasiside",
"Beta feature. Click to learn more.": "Beetafunktsionaalsus. Lisateabe lugemiseks klõpsi siin.",
"Beta feature": "Beetafunktsionaalsus",
"How can I start a thread?": "Kuidas ma alustan jutulõnga?",
"Threads help keep conversations on-topic and easy to track. <a>Learn more</a>.": "Jutulõngad aitavad hoida vestlusi teemakohastena ja jälgitavatena. <a>Lisateavet leiad siit</a>.",
"Keep discussions organised with threads.": "Halda vestlusi jutulõngadena.",
"sends hearts": "saadame südameid",
@ -3248,8 +3235,6 @@
"Disinvite from room": "Eemalda kutse jututuppa",
"Disinvite from space": "Eemalda kutse kogukonda",
"<b>Tip:</b> Use “%(replyInThread)s” when hovering over a message.": "<b>Soovitus:</b> Sõnumi kohal avanevast valikust kasuta „%(replyInThread)s“ võimalust.",
"To leave, return to this page and use the “%(leaveTheBeta)s” button.": "Lahkumiseks ava sama vaade ning klõpsi nuppu „%(leaveTheBeta)s“.",
"Use “%(replyInThread)s” when hovering over a message.": "Sõnumi kohal avanevast valikust kasuta „%(replyInThread)s“ võimalust.",
"No live locations": "Reaalajas asukohad puuduvad",
"Start messages with <code>/plain</code> to send without markdown and <code>/md</code> to send with.": "Kui sa ei soovi sõnumis kasutada Markdown-süntaksit, siis kirjuta algusesse <code>/plain</code>, vastasel juhul alusta sõnumit nii: <code>/md</code>.",
"Enable Markdown": "Kasuta Markdown-süntaksit",
@ -3441,11 +3426,9 @@
"Unverified": "Verifitseerimata",
"Verified": "Verifitseeritud",
"Verify your sessions for enhanced secure messaging or sign out from those you don't recognize or use anymore.": "Turvalise sõnumvahetuse nimel verifitseeri kõik oma sessioonid ning logi neist välja, mida sa enam ei kasuta või ei tunne enam ära.",
"Consider signing out from old sessions (%(inactiveAgeDays)s days or older) you don't use anymore": "Kui sa ei kasuta oma vanu sessioone (vanemad kui %(inactiveAgeDays)s päeva), siis logi need võrgust välja",
"Inactive sessions": "Mitteaktiivsed sessioonid",
"View all": "Näita kõiki",
"Unverified sessions": "Verifitseerimata sessioonid",
"Improve your account security by following these recommendations": "Kui järgid neid soovitusi, siis sa parandad oma kasutajakonto turvalisust",
"Security recommendations": "Turvalisusega seotud soovitused",
"Wed appreciate any feedback on how youre finding %(brand)s.": "Meile meeldiks, kui sa saadad meile oma arvamuse %(brand)s'i kohta.",
"How are you finding %(brand)s so far?": "Mis mulje sulle %(brand)s seni on jätnud?",
@ -3505,7 +3488,6 @@
"Ongoing call": "Kõne on pooleli",
"Video call (Jitsi)": "Videokõne (Jitsi)",
"Failed to set pusher state": "Tõuketeavituste teenuse oleku määramine ei õnnestunud",
"%(selectedDeviceCount)s sessions selected": "%(selectedDeviceCount)s sessioni valitud",
"Receive push notifications on this session.": "Võta tõuketeavitused selles sessioonis kasutusele.",
"Push notifications": "Tõuketeavitused",
"Toggle push notifications on this session.": "Lülita tõuketeavitused selles sessioonis sisse/välja.",
@ -3544,7 +3526,6 @@
"pause voice broadcast": "peata ringhäälingukõne",
"Underline": "Allajoonitud tekst",
"Italic": "Kaldkiri",
"Sign out all other sessions": "Logi välja kõikidest oma muudest sessioonidest",
"Our new sessions manager provides better visibility of all your sessions, and greater control over them including the ability to remotely toggle push notifications.": "Uues sessioonihalduris saad parema ülevaate kõikidest oma sessioonidest ning rohkem võimalusi neid hallata, sealhulgas tõuketeavituste sisse- ja väljalülitamine.",
"Have greater visibility and control over all your sessions.": "Sellega saad parema ülevaate oma sessioonidest ja võimaluse neid mugavasti hallata.",
"New session manager": "Uus sessioonihaldur",
@ -3658,7 +3639,6 @@
"Upcoming features": "Tulevikus lisanduvad funktsionaalsused",
"Requires compatible homeserver.": "Eeldab, et koduserver toetab sellist funktsionaalsust.",
"Low bandwidth mode": "Vähese ribalaiusega režiim",
"Under active development": "Aktiivselt arendamisel",
"Under active development.": "Aktiivselt arendamisel.",
"Favourite Messages": "Lemmiksõnumid",
"Temporary implementation. Locations persist in room history.": "Tegemist on ajutise ja esialgse lahendusega: asukohad on jututoa ajaloos näha.",
@ -3679,5 +3659,49 @@
"This session doesn't support encryption and thus can't be verified.": "Seda sessiooni ei saa verifitseerida, sest seal puudub krüptimise tugi.",
"This session doesn't support encryption, so it can't be verified.": "Seda sessiooni ei saa verifitseerida, sest seal puudub krüptimise tugi.",
"%(senderName)s ended a <a>voice broadcast</a>": "%(senderName)s lõpetas <a>ringhäälingukõne</a>",
"You ended a <a>voice broadcast</a>": "Sa lõpetasid <a>ringhäälingukõne</a>"
"You ended a <a>voice broadcast</a>": "Sa lõpetasid <a>ringhäälingukõne</a>",
"Threaded messages": "Sõnumid jutulõngana",
"Unable to decrypt message": "Sõnumi dekrüptimine ei õnnestunud",
"This message could not be decrypted": "Seda sõnumit ei õnnestunud dekrüptida",
"Resend key requests": "Saada võtmete päring uuesti",
"Unfortunately, there are no other verified devices to request decryption keys from. Signing in and verifying other devices may help avoid this situation in the future.": "Kahjuks pole sul teisi verifitseeritud seadmeid, mida saaks kasutada puuduvate krüptovõtmete laadimiseks. Kui sa logid veel mõnda seadmesse ja verifitseerid need, siis saad sa tulevikus sellist olukorda vältida.",
"Some messages could not be decrypted": "Mõnda sõnumit ei õnnestunud dekrüptida",
"View your device list": "Vaata oma seadmete loendit",
"This device is requesting decryption keys from your other devices. Opening one of your other devices may speed this up.": "See seade pärib krüptovõtmeid mõnest muust sinu seadmest. Kui see rakendus on teises seadmes kasutusel, siis võib päring toimuda kiiremini.",
"Open another device to load encrypted messages": "Krüptitud sõnumite laadimiseks kasuta mõnda muud oma seadet",
"You will not be able to access old undecryptable messages, but resetting your keys will allow you to receive new messages.": "Seeläbi sa ei saa lugeda vanu dekrüptimata sõnumeid, kuid krüptovõtmete lähtestamine võimaldab lugeda uusi sõnumeid.",
"Reset your keys to prevent future decryption errors": "Tulevaste dekrüptimisvigade vältimiseks palun lähtesta oma krüptovõtmed",
"This device was unable to decrypt some messages because it has not been verified yet.": "Kuna osa või kõik verifitseerimistest on tegemata, siis see seade ei suutnud kõiki sõnumeid dekrüptida.",
"Verify this device to access all messages": "Kõikide sõnumite lugemiseks palun verifitseeri see seade",
"Please wait as we try to decrypt your messages. This may take a few moments.": "Palun oota hetke kuni me dekrüptime sõnumeid. Natuke võib kuluda aega.",
"Decrypting messages...": "Sõnumid on dekrüptimisel...",
"Under active development. Can currently only be enabled via config.json": "Aktiivselt arendamisel. Hetkel saab muuta vaid config.json failist",
"Rust cryptography implementation": "Rust'is teostatud krüptolahendus",
"%(senderName)s ended a voice broadcast": "%(senderName)s lõpetas ringhäälingukõne",
"You ended a voice broadcast": "Sa lõpetasid ringhäälingukõne",
"You cant start a call as you are currently recording a live broadcast. Please end your live broadcast in order to start a call.": "Kuna sa hetkel salvestad ringhäälingukõnet, siis tavakõne algatamine ei õnnestu. Kõne alustamiseks palun lõpeta ringhäälingukõne.",
"Cant start a call": "Kõne algatamine ei õnnestu",
"Improve your account security by following these recommendations.": "Kui järgid neid soovitusi, siis sa parandad oma kasutajakonto turvalisust.",
"%(count)s sessions selected|one": "%(count)s sessioon valitud",
"%(count)s sessions selected|other": "%(count)s sessiooni valitud",
"Failed to read events": "Päringu või sündmuse lugemine ei õnnestunud",
"Failed to send event": "Päringu või sündmuse saatmine ei õnnestunud",
" in <strong>%(room)s</strong>": " <strong>%(room)s</strong> jututoas",
"Mark as read": "Märgi loetuks",
"Verify your current session for enhanced secure messaging.": "Turvalise sõnumivahetuse nimel palun verifitseeri oma praegune sessioon.",
"Your current session is ready for secure messaging.": "Sinu praegune sessioon on valmis turvaliseks sõnumivahetuseks.",
"Text": "Tekst",
"Create a link": "Tee link",
"Force 15s voice broadcast chunk length": "Kasuta ringhäälingusõnumi puhul 15-sekundilist blokipikkust",
"Link": "Link",
"Sign out of %(count)s sessions|one": "Logi %(count)s'st sessioonist välja",
"Sign out of %(count)s sessions|other": "Logi %(count)s'st sessioonist välja",
"Sign out of all other sessions (%(otherSessionsCount)s)": "Logi kõikidest ülejäänud sessioonidest välja: %(otherSessionsCount)s sessioon(i)",
"Yes, end my recording": "Jah, lõpeta salvestamine",
"If you start listening to this live broadcast, your current live broadcast recording will be ended.": "Kui hakkad kuulama seda ringhäälingukõnet, siis hetkel toimuv ringhäälingukõne salvestamine lõppeb.",
"Listen to live broadcast?": "Kas soovid kuulata ringhäälingukõnet?",
"Unfortunately we're unable to start a recording right now. Please try again later.": "Kahjuks me ei saa hetkel salvestamist alustada. Palun proovi hiljem uuesti.",
"Connection error": "Ühenduse viga",
"You can't start a voice message as you are currently recording a live broadcast. Please end your live broadcast in order to start recording a voice message.": "Kuna sa hetkel salvestad ringhäälingukõnet, siis häälsõnumi salvestamine või esitamine ei õnnestu. Selleks palun lõpeta ringhäälingukõne.",
"Can't start voice message": "Häälsõnumi salvestamine või esitamine ei õnnestu"
}

View File

@ -402,7 +402,6 @@
"<a>In reply to</a> <pill>": "<a>honi erantzunez:</a> <pill>",
"Failed to remove tag %(tagName)s from room": "Huts egin du %(tagName)s etiketa gelatik kentzean",
"Failed to add tag %(tagName)s to room": "Huts egin du %(tagName)s etiketa gelara gehitzean",
"Key request sent.": "Gako eskaria bidalita.",
"Code": "Kodea",
"Submit debug logs": "Bidali arazte-egunkariak",
"Opens the Developer Tools dialog": "Garatzailearen tresnen elkarrizketa-koadroa irekitzen du",
@ -1118,7 +1117,6 @@
"Connecting to integration manager...": "Integrazio kudeatzailera konektatzen…",
"Cannot connect to integration manager": "Ezin da integrazio kudeatzailearekin konektatu",
"The integration manager is offline or it cannot reach your homeserver.": "Integrazio kudeatzailea lineaz kanpo dago edo ezin du zure hasiera-zerbitzaria atzitu.",
"Clear notifications": "Garbitu jakinarazpenak",
"Manage integrations": "Kudeatu integrazioak",
"Ignored/Blocked": "Ezikusia/Blokeatuta",
"Error adding ignored user/server": "Errorea ezikusitako erabiltzaile edo zerbitzaria gehitzean",
@ -1202,7 +1200,6 @@
"Backup has a <validity>invalid</validity> signature from this user": "Babes-kopiak erabiltzaile honen <validity>baliogabeko</validity> sinadura bat du",
"Backup has a signature from <verify>unknown</verify> user with ID %(deviceId)s": "Babes-kopiak %(deviceId)s ID-a duen erabiltzaile <verify>ezezagun</verify> baten sinadura du",
"Cross-signing": "Zeharkako sinadura",
"This message cannot be decrypted": "Mezu hau ezin da deszifratu",
"Unencrypted": "Zifratu gabe",
"Close preview": "Itxi aurrebista",
"<userName/> wants to chat": "<userName/> erabiltzaileak txateatu nahi du",
@ -1345,10 +1342,6 @@
"Securely cache encrypted messages locally for them to appear in search results.": "Gorde zifratutako mezuak cachean modu seguruan bilaketen emaitzetan agertu daitezen.",
"You have verified this user. This user has verified all of their sessions.": "Erabiltzaile hau egiaztatu duzu. Erabiltzaile honek bere saio guztiak egiaztatu ditu.",
"Mod": "Moderatzailea",
"Your key share request has been sent - please check your other sessions for key share requests.": "Zure gako partekatze eskaria bidali da, egiaztatu zure beste saioak gako partekatze eskaera jaso duten.",
"Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.": "Automatikoki bidaltzen dira gako partekatze eskaerak zure beste saioetara. Beste saioetan gako partekatze eskaera ukatu edo baztertu baduzu, sakatu hemen saio honentzat gakoak berriro eskatzeko.",
"If your other sessions do not have the key for this message you will not be able to decrypt them.": "Zure beste saioek ez badute mezu honen gakoa ezin izango duzu deszifratu.",
"<requestLink>Re-request encryption keys</requestLink> from your other sessions.": "<requestLink>Eskatu berriro zifratze gakoak</requestLink> zure beste saioei.",
"Waiting for %(displayName)s to accept…": "%(displayName)s(e)k onartu bitartean zain…",
"Your messages are secured and only you and the recipient have the unique keys to unlock them.": "Zuen mezuak babestuta daude eta soilik zuk eta hartzaileak dituzue hauek desblokeatzeko gakoak.",
"One of the following may be compromised:": "Hauetakoren bat konprometituta egon daiteke:",
@ -1361,8 +1354,6 @@
"Sign In or Create Account": "Hasi saioa edo sortu kontua",
"Use your account or create a new one to continue.": "Erabili zure kontua edo sortu berri bat jarraitzeko.",
"Create Account": "Sortu kontua",
"Order rooms by name": "Ordenatu gelak izenez",
"Show rooms with unread notifications first": "Erakutsi irakurri gabeko jakinarazpenak dituztenak aurretik",
"Show shortcuts to recently viewed rooms above the room list": "Erakutsi ikusitako azken geletara lasterbideak gelen zerrendaren goialdean",
"Cancelling…": "Ezeztatzen…",
"Your homeserver does not support cross-signing.": "Zure hasiera-zerbitzariak ez du zeharkako sinatzea onartzen.",

View File

@ -1305,7 +1305,6 @@
"Encrypted by a deleted session": "با یک نشست حذف شده رمزگذاری شده است",
"Unencrypted": "رمزگذاری نشده",
"Encrypted by an unverified session": "توسط یک نشست تأیید نشده رمزگذاری شده است",
"This message cannot be decrypted": "این پیام نمی‌تواند رمزگشایی شود",
"Export room keys": "استخراج کلیدهای اتاق",
"%(nameList)s %(transitionList)s": "%(nameList)s.%(transitionList)s",
"This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.": "این فرآیند به شما این امکان را می‌دهد تا کلیدهایی را که برای رمزگشایی پیام‌هایتان در اتاق‌های رمزشده نیاز دارید، در قالب یک فایل محلی استخراج کنید. بعد از آن می‌توانید این فایل را در هر کلاینت دیگری وارد (Import) کرده و قادر به رمزگشایی و مشاهده‌ی پیام‌های رمزشده‌ی مذکور باشید.",
@ -1417,7 +1416,6 @@
"You declined": "شما رد کردید",
"%(name)s accepted": "%(name)s پذیرفت",
"You accepted": "پذیرفتید",
"<requestLink>Re-request encryption keys</requestLink> from your other sessions.": "<requestLink>درخواست مجدد کلید‌های رمزنگاری</requestLink> از نشست‌های دیگر شما.",
"Mod": "معاون",
"Hint: Begin your message with <code>//</code> to start it with a slash.": "نکته: پیام خود را با <code>//</code> شروع کنید تا با یک اسلش شروع شود.",
"You can use <code>/help</code> to list available commands. Did you mean to send this as a message?": "برای لیست کردن دستورات موجود می توانید از <code>/help</code> استفاده کنید. آیا قصد داشتید این پیام را به عنوان متم ارسال کنید؟",
@ -1571,7 +1569,6 @@
"Enable audible notifications for this session": "فعال‌سازی اعلان‌های صدادار برای این نشست",
"Show message in desktop notification": "پیام‌ها را در اعلان دسکتاپ نشان بده",
"Enable desktop notifications for this session": "فعال‌سازی اعلان‌های دسکتاپ برای این نشست",
"Clear notifications": "پاک‌کردن اعلان‌ها",
"The integration manager is offline or it cannot reach your homeserver.": "مدیر یکپارچه‌سازی‌ یا آفلاین است و یا نمی‌تواند به سرور شما متصل شود.",
"Cannot connect to integration manager": "امکان اتصال به مدیر یکپارچه‌سازی‌ها وجود ندارد",
"Connecting to integration manager...": "در حال اتصال به مدیر پکپارچه‌سازی...",
@ -1815,8 +1812,6 @@
"Show previews/thumbnails for images": "پیش‌نمایش تصاویر را نشان بده",
"Show hidden events in timeline": "نمایش رخدادهای مخفی در گفتگو‌ها",
"Show shortcuts to recently viewed rooms above the room list": "نمایش میانبر در بالای لیست اتاق‌ها برای مشاهده‌ی اتاق‌هایی که اخیرا باز کرده‌اید",
"Show rooms with unread notifications first": "اتاق‌های با پیام‌های خوانده‌نشده را ابتدا نشان بده",
"Order rooms by name": "مرتب‌کردن اتاق‌ها بر اساس نام",
"Prompt before sending invites to potentially invalid matrix IDs": "قبل از ارسال دعوت‌نامه برای کاربری که شناسه‌ی او احتمالا معتبر نیست، هشدا بده",
"Enable widget screenshots on supported widgets": "فعال‌سازی امکان اسکرین‌شات برای ویجت‌های پشتیبانی‌شده",
"Enable URL previews by default for participants in this room": "امکان پیش‌نمایش URL را به صورت پیش‌فرض برای اعضای این اتاق فعال کن",
@ -1867,10 +1862,6 @@
"Activate selected button": "دکمه انتخاب شده را فعال کنید",
"Toggle right panel": "پانل سمت راست را تغییر دهید",
"Go to Home View": "به صفحه اصلی بروید",
"Key request sent.": "درخواست کلید ارسال شد.",
"If your other sessions do not have the key for this message you will not be able to decrypt them.": "اگر بقیه‌ی نشست‌های شما نیز کلید این پیام را نداشته باشند، امکان رمزگشایی و مشاهده‌ی آن برای شما وجود نخواهد داشت.",
"Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.": "درخواست‌های به اشتراک‌گذاری کلید برای بقیه‌ی نشست‌های شما به صورت خودکار ارسال می‌شود. اگر این درخواست‌ها را بر روی سایر نشست‌هایتان رد کرده‌اید، اینجا کلیک کنید تا درخواست به اشتراک‌گذاری کلیدها برای این نشست مجدد ارسال شود.",
"Your key share request has been sent - please check your other sessions for key share requests.": "درخواست به اشتراک‌گذاری کلید ارسال شد - لطفا بقیه‌ی نشست‌های خود را برای درخواست به اشتراک‌گذاری کلید بررسی کنید.",
"This event could not be displayed": "امکان نمایش این رخداد وجود ندارد",
"Edit message": "ویرایش پیام",
"Send as message": "ارسال به عنوان پیام",

View File

@ -715,7 +715,6 @@
"Enable widget screenshots on supported widgets": "Ota sovelmien kuvankaappaukset käyttöön tuetuissa sovelmissa",
"Legal": "Lakitekstit",
"This event could not be displayed": "Tätä tapahtumaa ei voitu näyttää",
"Key request sent.": "Avainpyyntö lähetetty.",
"Demote yourself?": "Alenna itsesi?",
"You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.": "Et voi perua tätä muutosta, koska olet alentamassa itseäsi. Jos olet viimeinen oikeutettu henkilö tässä huoneessa, oikeuksia ei voi enää saada takaisin.",
"Demote": "Alenna",
@ -1172,7 +1171,6 @@
"%(senderName)s placed a voice call. (not supported by this browser)": "%(senderName)s soitti äänipuhelun. (selaimesi ei tue äänipuheluita)",
"%(senderName)s placed a video call.": "%(senderName)s soitti videopuhelun.",
"%(senderName)s placed a video call. (not supported by this browser)": "%(senderName)s soitti videopuhelun (selaimesi ei tue videopuheluita)",
"Clear notifications": "Tyhjennä ilmoitukset",
"Error upgrading room": "Virhe päivitettäessä huonetta",
"Double check that your server supports the room version chosen and try again.": "Tarkista, että palvelimesi tukee valittua huoneversiota ja yritä uudelleen.",
"%(senderName)s removed the rule banning users matching %(glob)s": "%(senderName)s poisti porttikiellon käyttäjiltä, jotka täsmäsivät sääntöön %(glob)s",
@ -1203,7 +1201,6 @@
"Backup has a <validity>valid</validity> signature from this user": "Varmuuskopiossa on <validity>kelvollinen</validity> allekirjoitus tältä käyttäjältä",
"Backup has a <validity>invalid</validity> signature from this user": "Varmuuskopiossa on <validity>epäkelpo</validity> allekirjoitus tältä käyttäjältä",
"Backup has a signature from <verify>unknown</verify> user with ID %(deviceId)s": "Varmuuskopiossa on <verify>tuntematon</verify> allekirjoitus käyttäjältä, jonka ID on %(deviceId)s",
"This message cannot be decrypted": "Tätä viestiä ei voida avata luettavaksi",
"Unencrypted": "Suojaamaton",
"Close preview": "Sulje esikatselu",
"<userName/> wants to chat": "<userName/> haluaa keskustella",
@ -1271,7 +1268,6 @@
"To report a Matrix-related security issue, please read the Matrix.org <a>Security Disclosure Policy</a>.": "Raportoidaksesi Matrixiin liittyvän tietoturvaongelman, lue Matrix.orgin <a>tietoturvaongelmien julkaisukäytäntö</a>.",
"Message search": "Viestihaku",
"This room is bridging messages to the following platforms. <a>Learn more.</a>": "Tämä huone siltaa viestejä seuraaville alustoille. <a>Lue lisää.</a>",
"<requestLink>Re-request encryption keys</requestLink> from your other sessions.": "<requestLink>Pyydä uudelleen salausavaimia </requestLink> muista istunnoistasi.",
"Mark all as read": "Merkitse kaikki luetuiksi",
"Accepting…": "Hyväksytään…",
"One of the following may be compromised:": "Jokin seuraavista saattaa olla vaarantunut:",
@ -1310,7 +1306,6 @@
"%(num)s days from now": "%(num)s päivää sitten",
"Never send encrypted messages to unverified sessions from this session": "Älä koskaan lähetä salattuja viestejä vahvistamattomiin istuntoihin tästä istunnosta",
"Never send encrypted messages to unverified sessions in this room from this session": "Älä lähetä salattuja viestejä vahvistamattomiin istuntoihin tässä huoneessa tässä istunnossa",
"Order rooms by name": "Järjestä huoneet nimellä",
"Setting up keys": "Otetaan avaimet käyttöön",
"Verifies a user, session, and pubkey tuple": "Varmentaa käyttäjän, istunnon ja julkiset avaimet",
"WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and session %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "VAROITUS: AVAIMEN VARMENTAMINEN EPÄONNISTUI! Käyttäjän %(userId)s ja laitteen %(deviceId)s istunnon allekirjoitusavain on ”%(fprint)s”, mikä ei täsmää annettuun avaimeen ”%(fingerprint)s”. Tämä voi tarkoittaa, että viestintäänne siepataan!",
@ -1326,7 +1321,6 @@
"%(senderName)s changed the addresses for this room.": "%(senderName)s muutti tämän huoneen osoitteita.",
"%(name)s (%(userId)s) signed in to a new session without verifying it:": "%(name)s (%(userId)s) kirjautui uudella istunnolla varmentamatta sitä:",
"Support adding custom themes": "Tue mukaututettujen teemojen lisäämistä",
"Show rooms with unread notifications first": "Näytä ensin huoneet, joissa on lukemattomia viestejä",
"Show shortcuts to recently viewed rooms above the room list": "Näytä oikotiet viimeksi katsottuihin huoneisiin huoneluettelon yläpuolella",
"Enable message search in encrypted rooms": "Ota viestihaku salausta käyttävissä huoneissa käyttöön",
"How fast should messages be downloaded.": "Kuinka nopeasti viestit pitäisi ladata.",
@ -1465,9 +1459,6 @@
"You have verified this user. This user has verified all of their sessions.": "Olet varmentanut tämän käyttäjän. Tämä käyttäjä on varmentanut kaikki istuntonsa.",
"This room is end-to-end encrypted": "Tämä huone käyttää päästä päähän -salausta",
"Everyone in this room is verified": "Kaikki tämän huoneen käyttäjät on varmennettu",
"Your key share request has been sent - please check your other sessions for key share requests.": "Avainten jakopyyntösi on lähetetty. Tarkista muut istuntosi avainten jakopyyntöjen varalta.",
"Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.": "Avainten jakopyynnöt lähetetään muille istunnoillesi automaattisesti. Jos hylkäsit tai jätit huomiotta avainten jakopyynnön toisessa istunnossasi, napsauta tästä pyytääksesi avaimia uudelleen.",
"If your other sessions do not have the key for this message you will not be able to decrypt them.": "Jos muissa laitteissasi ei ole avainta tämän viestin purkamiseen, niillä istunnoilla ei voi lukea tätä viestiä.",
"Encrypted by an unverified session": "Salattu varmentamattoman istunnon toimesta",
"Encrypted by a deleted session": "Salattu poistetun istunnon toimesta",
"Reject & Ignore user": "Hylkää ja sivuuta käyttäjä",
@ -2310,7 +2301,6 @@
"Your camera is turned off": "Kamerasi on pois päältä",
"%(sharerName)s is presenting": "%(sharerName)s esittää",
"You are presenting": "Esität parhaillaan",
"Threaded messaging": "Säikeistetty viestittely",
"Set up Secure Backup": "Määritä turvallinen varmuuskopio",
"Error fetching file": "Virhe tiedostoa noutaessa",
"Review to ensure your account is safe": "Katselmoi varmistaaksesi, että tilisi on turvassa",
@ -2344,7 +2334,7 @@
"Enable encryption in settings.": "Ota salaus käyttöön asetuksissa.",
"A private space for you and your teammates": "Yksityinen avaruus sinulle ja tiimikavereille",
"A private space to organise your rooms": "Yksityinen avaruus, jolla voit järjestää huoneesi",
"Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites.": "Yksityiset viestisi ovat normaalisti salattu, mutta tämä huone ei ole. Yleensä tämä johtuu ei tuetusta laitteesta tai käytetystä tavasta, kuten sähköpostikutsuista.",
"Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites.": "Yksityiset viestisi salataan normaalisti, mutta tämä huone ei ole salattu. Yleensä tämä johtuu laitteesta, jota ei tueta, tai käytetystä tavasta, kuten sähköpostikutsuista.",
"End-to-end encryption isn't enabled": "Päästä päähän -salaus ei ole käytössä",
"You have no visible notifications.": "Sinulla ei ole näkyviä ilmoituksia.",
"Add space": "Lisää avaruus",
@ -2724,7 +2714,6 @@
"This room isn't bridging messages to any platforms. <a>Learn more.</a>": "Tämä huone ei siltaa viestejä millekään alustalle. <a>Lue lisää.</a>",
"Sign out devices|one": "Kirjaa laite ulos",
"Sign out devices|other": "Kirjaa laitteet ulos",
"How can I leave the beta?": "Miten poistun beetasta?",
"Creating HTML...": "Luodaan HTML:ää...",
"No virtual room for this room": "Tällä huoneella ei ole virtuaalihuonetta",
"Switches to this room's virtual room, if it has one": "Vaihtaa tämän huoneen virtuaalihuoneeseen, mikäli huoneella sellainen on",
@ -2911,7 +2900,6 @@
"Start messages with <code>/plain</code> to send without markdown and <code>/md</code> to send with.": "Lähetä ilman markdownia laittamalla viestin alkuun <code>/plain</code> ja lähetä markdownilla laittamalla viestin alkuun <code>/md</code>.",
"Enable Markdown": "Ota Markdown käyttöön",
"Insert a trailing colon after user mentions at the start of a message": "Lisää kaksoispiste käyttäjän maininnan perään viestin alussa",
"To leave, return to this page and use the “%(leaveTheBeta)s” button.": "Poistuaksesi palaa tälle sivulle ja paina %(leaveTheBeta)s-painiketta.",
"Developer": "Kehittäjä",
"Connection lost": "Yhteys menetettiin",
"Sorry, your homeserver is too old to participate here.": "Kotipalvelimesi on liian vanha osallistumaan tänne.",
@ -2974,8 +2962,6 @@
"Search %(spaceName)s": "Etsi %(spaceName)s",
"Call": "Soita",
"Dial": "Yhdistä",
"Use “%(replyInThread)s” when hovering over a message.": "Käytä “%(replyInThread)s” kun siirryt viestin päälle.",
"How can I start a thread?": "Miten aloitan ketjun?",
"Messaging": "Viestintä",
"Back to thread": "Takaisin ketjuun",
"The user's homeserver does not support the version of the space.": "Käyttäjän kotipalvelin ei tue avaruuden versiota.",
@ -3067,7 +3053,7 @@
"Show: Matrix rooms": "Näytä: Matrix-huoneet",
"Show: %(instance)s rooms (%(server)s)": "Näytä: %(instance)s-huoneet (%(server)s)",
"Add new server…": "Lisää uusi palvelin…",
"Remove server “%(roomServer)s”": "Poista palvelin %(roomServer)s”",
"Remove server “%(roomServer)s”": "Poista palvelin %(roomServer)s”",
"Minimise": "Pienennä",
"This room or space is not accessible at this time.": "Tämä huone tai avaruus ei ole käytettävissä juuri tällä hetkellä.",
"Video rooms are a beta feature": "Videohuoneet ovat beetaominaisuus",
@ -3132,11 +3118,9 @@
"Video call (%(brand)s)": "Videopuhelu (%(brand)s)",
"Video call (Jitsi)": "Videopuhelu (Jitsi)",
"View all": "Näytä kaikki",
"Improve your account security by following these recommendations": "Paranna tilisi turvallisuutta seuraamalla näitä suosituksia",
"Security recommendations": "Turvallisuussuositukset",
"Show QR code": "Näytä QR-koodi",
"Sign in with QR code": "Kirjaudu sisään QR-koodilla",
"%(selectedDeviceCount)s sessions selected": "%(selectedDeviceCount)s istuntoa valittu",
"Show": "Näytä",
"Filter devices": "Suodata laitteita",
"Inactive for %(inactiveAgeDays)s days or longer": "Passiivinen %(inactiveAgeDays)s päivää tai pidempään",
@ -3177,7 +3161,6 @@
"Last activity": "Viimeisin toiminta",
"Rename session": "Nimeä istunto uudelleen",
"Current session": "Nykyinen istunto",
"Sign out all other sessions": "Kirjaudu ulos muista istunnoista",
"You do not have sufficient permissions to change this.": "Oikeutesi eivät riitä tämän muuttamiseen.",
"Group all your people in one place.": "Ryhmitä kaikki ihmiset yhteen paikkaan.",
"For best security, verify your sessions and sign out from any session that you don't recognize or use anymore.": "Parhaan turvallisuuden varmistamiseksi vahvista istuntosi ja kirjaudu ulos istunnoista, joita et tunnista tai et enää käytä.",
@ -3265,12 +3248,11 @@
"Use new session manager": "Käytä uutta istuntohallintaa",
"New group call experience": "Uusi ryhmäpuhelukokemus",
"Yes, the chat timeline is displayed alongside the video.": "Kyllä, keskustelun aikajana esitetään videon yhteydessä.",
"Use the “+” button in the room section of the left panel.": "Käytä +”-painiketta vasemman paneelin huoneosiossa.",
"Use the “+” button in the room section of the left panel.": "Käytä +”-painiketta vasemman paneelin huoneosiossa.",
"A new way to chat over voice and video in %(brand)s.": "Uusi tapa keskustella äänen ja videon välityksellä %(brand)sissä.",
"In %(spaceName)s and %(count)s other spaces.|one": "Avaruudessa %(spaceName)s ja %(count)s muussa avaruudessa.",
"In %(spaceName)s and %(count)s other spaces.|other": "Avaruudessa %(spaceName)s ja %(count)s muussa avaruudessa.",
"Get notifications as set up in your <a>settings</a>": "Vastaanota ilmoitukset <a>asetuksissa</a> määrittämälläsi tavalla",
"Consider signing out from old sessions (%(inactiveAgeDays)s days or older) you don't use anymore": "Kirjaudu ulos vanhoista istunnoista (%(inactiveAgeDays)s päivää tai vanhemmat), joita et enää käytä",
"PRO TIP: If you start a bug, please submit <debugLogsLink>debug logs</debugLogsLink> to help us track down the problem.": "Vinkki: Jos teet virheilmoituksen, lähetä <debugLogsLink>vianjäljityslokit</debugLogsLink> jotta ongelman ratkaiseminen helpottuu.",
"Please view <existingIssuesLink>existing bugs on Github</existingIssuesLink> first. No match? <newIssueLink>Start a new one</newIssueLink>.": "Katso ensin <existingIssuesLink>aiemmin raportoidut virheet Githubissa</existingIssuesLink>. Eikö samanlaista virhettä löydy? <newIssueLink>Tee uusi ilmoitus virheestä</newIssueLink>.",
"Search for": "Etsittävät kohteet",
@ -3355,5 +3337,98 @@
"Echo cancellation": "Kaiunpoisto",
"When enabled, the other party might be able to see your IP address": "Kun käytössä, toinen osapuoli voi mahdollisesti nähdä IP-osoitteesi",
"30s forward": "30 s eteenpäin",
"30s backward": "30 s taaksepäin"
"30s backward": "30 s taaksepäin",
"We need to know its you before resetting your password.\n Click the link in the email we just sent to <b>%(email)s</b>": "Meidän tulee varmistaa, että se olet sinä, ennen kuin salasanasi voidaan nollata.\n Napsauta osoitteeseen <b>%(email)s</b> lähetettyä linkkiä",
"Verify your email to continue": "Vahvista sähköpostiosoitteesi jatkaaksesi",
"<b>%(homeserver)s</b> will send you a verification link to let you reset your password.": "<b>%(homeserver)s</b> lähettää sinulle vahvistuslinkin, jotta voit nollata salasanasi.",
"Enter your email to reset password": "Kirjoita sähköpostiosoitteesi nollataksesi salasanasi",
"Send email": "Lähetä sähköpostia",
"Verification link email resent!": "Vahvistuslinkin sisältävä sähköposti lähetetty uudelleen!",
"Did not receive it?": "Etkö vastaanottanut viestiä?",
"Re-enter email address": "Kirjoita sähköpostiosoite uudestaan",
"Wrong email address?": "Väärä sähköpostiosoite?",
"Follow the instructions sent to <b>%(email)s</b>": "Seuraa osoitteeseen <b>%(email)s</b> lähetettyjä ohjeita",
"Sign out of all devices": "Kirjaudu ulos kaikista laitteista",
"Confirm new password": "Vahvista uusi salasana",
"Reset your password": "Nollaa salasanasi",
"Reset password": "Nollaa salasana",
"<w>WARNING:</w> <description/>": "<w>VAROITUS:</w> <description/>",
"Unable to decrypt message": "Viestin salauksen purkaminen ei onnistu",
"Change layout": "Vaihda asettelua",
"This message could not be decrypted": "Tämän viestin salausta ei voitu purkaa",
"Some messages could not be decrypted": "Joidenkin viestien salausta ei voitu purkaa",
"View your device list": "Näytä laiteluettelo",
"Verify this device to access all messages": "Vahvista tämä laite saadaksesi pääsyn kaikkiin viesteihisi",
"Please wait as we try to decrypt your messages. This may take a few moments.": "Odota, kun yritämme purkaa viestiesi salausta. Tämä saattaa kestää hetken.",
"Decrypting messages...": "Puretaan viestien salausta...",
"Improve your account security by following these recommendations.": "Paranna tilisi tietoturvaa seuraamalla näitä suosituksia.",
"%(count)s sessions selected|one": "%(count)s istunto valittu",
"%(count)s sessions selected|other": "%(count)s istuntoa valittu",
"This session doesn't support encryption and thus can't be verified.": "Tämä istunto ei tue salausta, joten sitä ei voi vahvistaa.",
"For best security and privacy, it is recommended to use Matrix clients that support encryption.": "Parhaan tietoturvan ja yksityisyyden vuoksi on suositeltavaa käyttää salausta tukevia Matrix-asiakasohjelmistoja.",
"Upcoming features": "Tulevat ominaisuudet",
"Apply": "Toteuta",
"Search users in this room…": "Etsi käyttäjiä tästä huoneesta…",
"Requires compatible homeserver.": "Vaatii yhteensopivan kotipalvelimen.",
"Under active development.": "Aktiivisen kehityksen kohteena.",
"Favourite Messages": "Suosikkiviestit",
"Threaded messages": "Ketjutetut viestit",
"You have unverified sessions": "Sinulla on vahvistamattomia istuntoja",
"Buffering…": "Puskuroidaan…",
"Change input device": "Vaihda sisääntulolaitetta",
"Cant start a call": "Puhelua ei voi aloittaa",
"Failed to read events": "Tapahtumien lukeminen epäonnistui",
"Failed to send event": "Tapahtuman lähettäminen epäonnistui",
"%(minutes)sm %(seconds)ss": "%(minutes)s min %(seconds)s s",
"%(minutes)sm %(seconds)ss left": "%(minutes)s min %(seconds)s s jäljellä",
"Too many attempts in a short time. Retry after %(timeout)s.": "Liikaa yrityksiä lyhyessä ajassa. Yritä uudelleen, kun %(timeout)s on kulunut.",
"Too many attempts in a short time. Wait some time before trying again.": "Liikaa yrityksiä lyhyessä ajassa. Odota hetki, ennen kuin yrität uudelleen.",
"You're all caught up": "Olet ajan tasalla",
"Unread email icon": "Lukemattoman sähköpostin kuvake",
"Proxy URL": "Välityspalvelimen URL-osoite",
"Proxy URL (optional)": "Välityspalvelimen URL-osoite (valinnainen)",
"Sliding Sync configuration": "Liukuvan synkronoinnin asetukset",
"Ban them from everything I'm able to": "Anna porttikielto kaikkeen, mihin pystyn",
"Unban them from everything I'm able to": "Poista porttikielto kaikesta, mihin pystyn",
"Underline": "Alleviivaus",
"Italic": "Kursivointi",
"This invite was sent to %(email)s which is not associated with your account": "Tämä kutsu lähetettiin sähköpostiosoitteeseen %(email)s, joka ei ole yhteydessä tiliisi",
"Spotlight": "Valokeila",
"Freedom": "Vapaus",
"There's no one here to call": "Täällä ei ole ketään, jolle voisi soittaa",
"Consider signing out from old sessions (%(inactiveAgeDays)s days or older) you don't use anymore.": "Harkitse vanhoista (%(inactiveAgeDays)s tai useamman päivän ikäisistä), käyttämättömistä istunnoista uloskirjautumista.",
"You won't be able to participate in rooms where encryption is enabled when using this session.": "Käyttäessäsi tätä istuntoa et voi osallistua huoneisiin, joissa salaus on käytössä.",
"Enable %(brand)s as an additional calling option in this room": "Ota %(brand)s käyttöön puheluiden lisävaihtoehtona tässä huoneessa",
"<b>It's not recommended to add encryption to public rooms.</b> Anyone can find and join public rooms, so anyone can read messages in them. You'll get none of the benefits of encryption, and you won't be able to turn it off later. Encrypting messages in a public room will make receiving and sending messages slower.": "<b>Julkisten huoneiden salaamista ei suositella.</b> Kuka vain voi löytää julkisen huoneen ja liittyä siihen, joten kuka vain voi lukea sen viestejä. Salauksesta ei ole hyötyä eikä sitä voi poistaa käytöstä myöhemmin. Julkisen huoneen viestien salaaminen hidastaa viestien vastaanottamista ja lähettämistä.",
"Early previews": "Ennakot",
"If you want to retain access to your chat history in encrypted rooms you should first export your room keys and re-import them afterwards.": "Jos haluat säilyttää pääsyn keskusteluhistoriaasi salausta käyttävissä huoneissa, vie ensin huoneen avaimesi ja tuo ne sen jälkeen takaisin.",
"You made it!": "Onnistui!",
"Allow fallback call assist server (turn.matrix.org)": "Salli varalla puhelun apupalvelin (turn.matrix.org)",
"Noise suppression": "Kohinanvaimennus",
"Allow Peer-to-Peer for 1:1 calls": "Salli vertaisyhteydet kahdenvälisissä puheluissa",
"Rust cryptography implementation": "Rust-kryptografiatoteutus",
"Temporary implementation. Locations persist in room history.": "Tilapäinen toteutus. Sijainnit säilyvät huoneen historiassa.",
"Sliding Sync mode": "Liukuvan synkronoinnin tila",
"Defaults to room member list.": "Oletusarvoisesti huoneen jäsenluettelo.",
"Show HTML representation of room topics": "Näytä huoneiden aiheiden HTML-esitys",
"In rooms that support moderation, the “Report” button will let you report abuse to room moderators.": "Moderointia tukevissa huoneissa väärinkäytökset voi ilmoittaa Ilmoita-painikkeella huoneen moderaattoreille.",
"Report to moderators": "Ilmoita moderaattoreille",
"Let moderators hide messages pending moderation.": "Anna moderaattorien piilottaa moderointia odottavia viestejä.",
"%(hours)sh %(minutes)sm %(seconds)ss left": "%(hours)s h %(minutes)s m %(seconds)s s jäljellä",
"View List": "Näytä luettelo",
"View list": "Näytä luettelo",
"Mark as read": "Merkitse luetuksi",
"Interactively verify by emoji": "Vahvista vuorovaikutteisesti emojilla",
"Manually verify by text": "Vahvista manuaalisesti tekstillä",
"Text": "Teksti",
"Link": "Linkki",
"Create a link": "Luo linkki",
" in <strong>%(room)s</strong>": " huoneessa <strong>%(room)s</strong>",
"Sign out of %(count)s sessions|one": "Kirjaudu ulos %(count)s istunnosta",
"Sign out of %(count)s sessions|other": "Kirjaudu ulos %(count)s istunnosta",
"Your current session is ready for secure messaging.": "Nykyinen istuntosi on valmis turvalliseen viestintään.",
"Sign out of all other sessions (%(otherSessionsCount)s)": "Kirjaudu ulos kaikista muista istunnoista (%(otherSessionsCount)s)",
"You did it!": "Teit sen!",
"Right panel stays open": "Oikea paneeli pysyy avoinna",
"Currently experimental.": "Tällä hetkellä kokeellinen."
}

View File

@ -402,7 +402,6 @@
"<a>In reply to</a> <pill>": "<a>En réponse à</a> <pill>",
"Failed to remove tag %(tagName)s from room": "Échec de la suppression de létiquette %(tagName)s du salon",
"Failed to add tag %(tagName)s to room": "Échec de lajout de létiquette %(tagName)s au salon",
"Key request sent.": "Demande de clé envoyée.",
"Code": "Code",
"Submit debug logs": "Envoyer les journaux de débogage",
"Opens the Developer Tools dialog": "Ouvre la fenêtre des outils de développeur",
@ -1173,10 +1172,8 @@
"%(senderName)s placed a voice call. (not supported by this browser)": "%(senderName)s a passé un appel audio. (non pris en charge par ce navigateur)",
"%(senderName)s placed a video call.": "%(senderName)s a passé un appel vidéo.",
"%(senderName)s placed a video call. (not supported by this browser)": "%(senderName)s a passé un appel vidéo. (non pris en charge par ce navigateur)",
"Clear notifications": "Vider les notifications",
"Error upgrading room": "Erreur lors de la mise à niveau du salon",
"Double check that your server supports the room version chosen and try again.": "Vérifiez que votre serveur prend en charge la version de salon choisie et réessayez.",
"This message cannot be decrypted": "Ce message ne peut pas être déchiffré",
"Unencrypted": "Non chiffré",
"Upgrade private room": "Mettre à niveau le salon privé",
"Upgrade public room": "Mettre à niveau le salon public",
@ -1338,10 +1335,6 @@
"You have verified this user. This user has verified all of their sessions.": "Vous avez vérifié cet utilisateur. Cet utilisateur a vérifié toutes ses sessions.",
"Someone is using an unknown session": "Quelquun utilise une session inconnue",
"Mod": "Modérateur",
"Your key share request has been sent - please check your other sessions for key share requests.": "Votre demande de partage de clé a été envoyée vérifiez les demandes de partage de clé sur vos autres sessions.",
"Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.": "Les demandes de partage de clé sont envoyées à vos autres sessions automatiquement. Si vous avez rejeté ou ignoré la demande de partage de clé sur vos autres sessions, cliquez ici pour redemander les clés pour cette session.",
"If your other sessions do not have the key for this message you will not be able to decrypt them.": "Si vos autres sessions nont pas la clé pour ce message vous ne pourrez pas le déchiffrer.",
"<requestLink>Re-request encryption keys</requestLink> from your other sessions.": "<requestLink>Redemander les clés de chiffrement</requestLink> à vos autres sessions.",
"Encrypted by an unverified session": "Chiffré par une session non vérifiée",
"Encrypted by a deleted session": "Chiffré par une session supprimée",
"%(count)s sessions|other": "%(count)s sessions",
@ -1396,8 +1389,6 @@
"Sign In or Create Account": "Se connecter ou créer un compte",
"Use your account or create a new one to continue.": "Utilisez votre compte ou créez en un pour continuer.",
"Create Account": "Créer un compte",
"Order rooms by name": "Trier les salons par nom",
"Show rooms with unread notifications first": "Afficher en premier les salons avec des notifications non lues",
"Show shortcuts to recently viewed rooms above the room list": "Afficher les raccourcis vers les salons vus récemment au-dessus de la liste des salons",
"Displays information about a user": "Affiche des informations à propos de lutilisateur",
"To report a Matrix-related security issue, please read the Matrix.org <a>Security Disclosure Policy</a>.": "Pour signaler un problème de sécurité lié à Matrix, consultez <a>la politique de divulgation de sécurité</a> de Matrix.org.",
@ -2636,7 +2627,6 @@
"Cross-signing is ready but keys are not backed up.": "La signature croisée est prête mais les clés ne sont pas sauvegardées.",
"Autoplay videos": "Jouer automatiquement les vidéos",
"Autoplay GIFs": "Jouer automatiquement les GIFs",
"Threaded messaging": "Fils de discussion",
"The above, but in <Room /> as well": "Comme ci-dessus, mais également dans <Room />",
"The above, but in any room you are joined or invited to as well": "Comme ci-dessus, mais également dans tous les salons dans lesquels vous avez été invité ou que vous avez rejoint",
"Currently, %(count)s spaces have access|one": "Actuellement, un espace a accès",
@ -2989,7 +2979,6 @@
"Remove from %(roomName)s": "Expulser de %(roomName)s",
"Keyboard": "Clavier",
"Automatically send debug logs on decryption errors": "Envoyer automatiquement les journaux de débogage en cas derreurs de déchiffrement",
"Show extensible event representation of events": "Afficher une représentation extensible des évènements",
"Let moderators hide messages pending moderation.": "Permettre aux modérateurs de cacher des messages en attente de modération.",
"%(senderName)s has ended a poll": "%(senderName)s a terminé un sondage",
"%(senderName)s has started a poll - %(pollQuestion)s": "%(senderName)s a démarré un sondage %(pollQuestion)s",
@ -3048,7 +3037,6 @@
"Show current avatar and name for users in message history": "Afficher l'avatar et le nom actuels des utilisateurs dans l'historique des messages",
"%(space1Name)s and %(space2Name)s": "%(space1Name)s et %(space2Name)s",
"Thank you for trying the beta, please go into as much detail as you can so we can improve it.": "Merci d'avoir essayé la bêta, veuillez donner le plus de détails possible pour que nous puissions l'améliorer.",
"How can I leave the beta?": "Comment puis-je quitter la bêta ?",
"No virtual room for this room": "Aucun salon virtuel pour ce salon",
"Switches to this room's virtual room, if it has one": "Bascule dans le salon virtuel de ce salon, s'il en a un",
"Automatically send debug logs when key backup is not functioning": "Envoyer automatiquement les journaux de débogage lorsque la sauvegarde des clés ne fonctionne pas",
@ -3210,7 +3198,6 @@
"Upgrade this space to the recommended room version": "Mettre à niveau cet espace vers la version recommandée",
"sends hearts": "envoie des cœurs",
"Sends the given message with hearts": "Envoie le message donné avec des cœurs",
"How can I start a thread?": "Comment démarrer un fil de discussion ?",
"Threads help keep conversations on-topic and easy to track. <a>Learn more</a>.": "Les fils de discussion aident à recentrer les conversations et les rends faciles à suivre. <a>En savoir plus</a>.",
"Keep discussions organised with threads.": "Gardez vos conversations organisées avec les fils de discussion.",
"Failed to join": "Impossible de rejoindre",
@ -3281,8 +3268,6 @@
"Audio devices": "Périphériques audio",
"Start messages with <code>/plain</code> to send without markdown and <code>/md</code> to send with.": "Commencer les messages avec <code>/plain</code> pour les envoyer sans markdown et <code>/md</code> pour les envoyer avec.",
"Enable Markdown": "Activer Markdown",
"To leave, return to this page and use the “%(leaveTheBeta)s” button.": "Pour quitter, revenez à cette page et utilisez le bouton « %(leaveTheBeta)s ».",
"Use “%(replyInThread)s” when hovering over a message.": "Utilisez « %(replyInThread)s » en survolant un message.",
"%(members)s and more": "%(members)s et plus",
"Your message wasn't sent because this homeserver has been blocked by its administrator. Please <a>contact your service administrator</a> to continue using the service.": "Votre message na pas été envoyé car ce serveur daccueil a été bloqué par son administrateur. Veuillez <a>contacter ladministrateur de votre service</a> pour continuer à lutiliser.",
"An error occurred while stopping your live location": "Une erreur sest produite lors de larrêt de votre position en continu",
@ -3433,19 +3418,17 @@
"Interactively verify by emoji": "Vérifier de façon interactive avec des émojis",
"Manually verify by text": "Vérifier manuellement avec un texte",
"View all": "Tout voir",
"Improve your account security by following these recommendations": "Améliorez la sécurité de votre compte à laide de ces recommandations",
"Security recommendations": "Recommandations de sécurité",
"Filter devices": "Filtrer les appareils",
"Inactive for %(inactiveAgeDays)s days or longer": "Inactif depuis au moins %(inactiveAgeDays)s jours",
"Inactive for %(inactiveAgeDays)s days or longer": "Inactive depuis au moins %(inactiveAgeDays)s jours",
"Inactive": "Inactive",
"Not ready for secure messaging": "Pas prêt pour une messagerie sécurisée",
"Ready for secure messaging": "Prêt pour une messagerie sécurisée",
"Not ready for secure messaging": "Messagerie non sécurisée",
"Ready for secure messaging": "Messagerie sécurisée",
"All": "Tout",
"No sessions found.": "Aucune session na été trouvée.",
"No inactive sessions found.": "Aucune session inactive na été trouvée.",
"No unverified sessions found.": "Aucune session non vérifiée na été trouvée.",
"No verified sessions found.": "Aucune session vérifiée na été trouvée.",
"Consider signing out from old sessions (%(inactiveAgeDays)s days or older) you don't use anymore": "Pensez à déconnectez les anciennes sessions (%(inactiveAgeDays)s jours ou plus) que vous nutilisez plus",
"Inactive sessions": "Sessions inactives",
"Verify your sessions for enhanced secure messaging or sign out from those you don't recognize or use anymore.": "Vérifiez vos sessions pour améliorer la sécurité de votre messagerie, ou déconnectez celles que vous ne connaissez pas ou nutilisez plus.",
"Unverified sessions": "Sessions non vérifiées",
@ -3506,7 +3489,6 @@
"Toggle push notifications on this session.": "Activer/désactiver les notifications push pour cette session.",
"New group call experience": "Nouvelle expérience dappel de groupe",
"Live": "Direct",
"%(selectedDeviceCount)s sessions selected": "%(selectedDeviceCount)s sessions sélectionnées",
"Enable notifications for this device": "Activer les notifications sur cet appareil",
"Turn off to disable notifications on all your devices and sessions": "Désactiver pour ne plus afficher les notifications sur tous vos appareils et sessions",
"Enable notifications for this account": "Activer les notifications pour ce compte",
@ -3544,7 +3526,6 @@
"Have greater visibility and control over all your sessions.": "Ayez une meilleur visibilité et plus de contrôle sur toutes vos sessions.",
"New session manager": "Nouveau gestionnaire de sessions",
"Use new session manager": "Utiliser le nouveau gestionnaire de session",
"Sign out all other sessions": "Déconnecter toutes les autres sessions",
"resume voice broadcast": "continuer la diffusion audio",
"pause voice broadcast": "mettre en pause la diffusion audio",
"Underline": "Souligné",
@ -3587,7 +3568,7 @@
"Are you sure you want to sign out of %(count)s sessions?|other": "Voulez-vous vraiment déconnecter %(count)s de vos sessions ?",
"Show formatting": "Afficher le formatage",
"Consider signing out from old sessions (%(inactiveAgeDays)s days or older) you don't use anymore.": "Pensez à déconnecter les anciennes sessions (%(inactiveAgeDays)s jours ou plus) que vous nutilisez plus.",
"Removing inactive sessions improves security and performance, and makes it easier for you to identify if a new session is suspicious.": "Supprimer les sessions inactives améliore la sécurité et les performance, et vous permets plus facilement didentifier une nouvelle session suspicieuse.",
"Removing inactive sessions improves security and performance, and makes it easier for you to identify if a new session is suspicious.": "Supprimer les sessions inactives améliore la sécurité et les performances, et vous permets plus facilement didentifier une nouvelle session suspicieuse.",
"Inactive sessions are sessions you have not used in some time, but they continue to receive encryption keys.": "Les sessions inactives sont des sessions que vous navez pas utilisées depuis un certain temps, mais elles reçoivent toujours les clés de chiffrement.",
"You should make especially certain that you recognise these sessions as they could represent an unauthorised use of your account.": "Vous devriez vous tout particulièrement vous assurer que vous connaissez ces sessions, car elles peuvent représenter un usage frauduleux de votre compte.",
"Unverified sessions are sessions that have logged in with your credentials but have not been cross-verified.": "Les sessions non vérifiées se sont identifiées avec vos identifiants mais nont pas fait de vérification croisée.",
@ -3649,7 +3630,6 @@
"Upcoming features": "Fonctionnalités à venir",
"Requires compatible homeserver.": "Nécessite un serveur daccueil compatible.",
"Low bandwidth mode": "Mode faible bande passante",
"Under active development": "En cours de développement",
"Under active development.": "En cours de développement.",
"Favourite Messages": "Messages favoris",
"Temporary implementation. Locations persist in room history.": "Implémentation temporaire. Les positions sont persistantes dans lhistorique du salon.",
@ -3679,5 +3659,49 @@
"Give one or multiple users in this room more privileges": "Donne plus de privilèges à un ou plusieurs utilisateurs de ce salon",
"Add privileged users": "Ajouter des utilisateurs privilégiés",
"%(senderName)s ended a <a>voice broadcast</a>": "%(senderName)s a terminé une <a>diffusion audio</a>",
"You ended a <a>voice broadcast</a>": "Vous avez terminé une <a>diffusion audio</a>"
"You ended a <a>voice broadcast</a>": "Vous avez terminé une <a>diffusion audio</a>",
"Unable to decrypt message": "Impossible de déchiffrer le message",
"This message could not be decrypted": "Ce message na pas pu être déchiffré",
"Resend key requests": "Ré-envoyer les demandes de clés",
"Unfortunately, there are no other verified devices to request decryption keys from. Signing in and verifying other devices may help avoid this situation in the future.": "Malheureusement, il ny a aucun autre appareil vérifié auquel demander les clés de déchiffrement. La connexion et la vérification depuis dautres appareils pourraient éviter ce genre de souci à lavenir.",
"Some messages could not be decrypted": "Certains messages nont pas pu être déchiffrés",
"View your device list": "Voir la liste de vos appareils",
"This device is requesting decryption keys from your other devices. Opening one of your other devices may speed this up.": "Cet appareil a demandé les clés de déchiffrement à vos autres appareils. Lutilisation dun de vos autres appareils peut accélérer cette demande.",
"Open another device to load encrypted messages": "Utiliser un autre appareil pour charger les messages chiffrés",
"You will not be able to access old undecryptable messages, but resetting your keys will allow you to receive new messages.": "Vous ne pourrez pas accéder à vos anciens messages indéchiffrables, mais la réinitialisation de vos clés vous permettra de recevoir de nouveaux messages.",
"Reset your keys to prevent future decryption errors": "Réinitialiser vos clés pour éviter dautres erreurs de déchiffrement",
"This device was unable to decrypt some messages because it has not been verified yet.": "Cet appareil na pas pu déchiffrer certains messages parce quil na pas encore été vérifié.",
"Verify this device to access all messages": "Vérifier cet appareil pour accéder à tous les messages",
"Please wait as we try to decrypt your messages. This may take a few moments.": "Veuillez patienter pendant que nous essayons de déchiffrer vos messages. Cela peut prendre un peu de temps.",
"Decrypting messages...": "Déchiffrement des messages…",
"Threaded messages": "Fils de discussion",
"Under active development. Can currently only be enabled via config.json": "En cours de développement. Ne peut pour linstant être activé que via config.json",
"Rust cryptography implementation": "Implémentation cryptographique en Rust",
"%(senderName)s ended a voice broadcast": "%(senderName)s a terminé une diffusion audio",
"You ended a voice broadcast": "Vous avez terminé une diffusion audio",
"Improve your account security by following these recommendations.": "Améliorez la sécurité de votre compte à laide de ces recommandations.",
"%(count)s sessions selected|one": "%(count)s session sélectionnée",
"%(count)s sessions selected|other": "%(count)s sessions sélectionnées",
"You cant start a call as you are currently recording a live broadcast. Please end your live broadcast in order to start a call.": "Vous ne pouvez pas démarrer un appel car vous êtes en train denregistrer une diffusion en direct. Veuillez terminer cette diffusion pour démarrer un appel.",
"Cant start a call": "Impossible de démarrer un appel",
"Failed to read events": "Échec de la lecture des évènements",
"Failed to send event": "Échec de lenvoi de lévènement",
" in <strong>%(room)s</strong>": " dans <strong>%(room)s</strong>",
"Verify your current session for enhanced secure messaging.": "Vérifiez cette session pour renforcer la sécurité de votre messagerie.",
"Your current session is ready for secure messaging.": "Votre session actuelle est prête pour une messagerie sécurisée.",
"Mark as read": "Marquer comme lu",
"Text": "Texte",
"Create a link": "Crée un lien",
"Link": "Lien",
"Force 15s voice broadcast chunk length": "Forcer la diffusion audio à utiliser des morceaux de 15s",
"Sign out of %(count)s sessions|one": "Déconnecter %(count)s session",
"Sign out of %(count)s sessions|other": "Déconnecter %(count)s sessions",
"Sign out of all other sessions (%(otherSessionsCount)s)": "Déconnecter toutes les autres sessions (%(otherSessionsCount)s)",
"Yes, end my recording": "Oui, terminer mon enregistrement",
"If you start listening to this live broadcast, your current live broadcast recording will be ended.": "En commençant à écouter cette diffusion en direct, votre enregistrement de diffusion en direct actuel sera interrompu.",
"Listen to live broadcast?": "Écouter la diffusion en direct ?",
"Unfortunately we're unable to start a recording right now. Please try again later.": "Malheureusement, nous ne pouvons pas démarrer lenregistrement pour le moment. Veuillez réessayer plus tard.",
"Connection error": "Erreur de connexion",
"You can't start a voice message as you are currently recording a live broadcast. Please end your live broadcast in order to start recording a voice message.": "Vous ne pouvez pas commencer un message vocal car vous êtes en train denregistrer une diffusion en direct. Veuillez terminer cette diffusion pour commencer un message vocal.",
"Can't start voice message": "Impossible de commencer un message vocal"
}

View File

@ -694,7 +694,6 @@
"Server may be unavailable, overloaded, or you hit a bug.": "Dfhéadfadh nach mbeadh an freastalaí ar fáil, ró-ualaithe, nó fuair tú fabht.",
"Rooms and spaces": "Seomraí agus spásanna",
"Collapse reply thread": "Cuir na freagraí i bhfolach",
"Threaded messaging": "Teachtaireachtaí i snáitheanna",
"Thread": "Snáithe",
"Low priority": "Tosaíocht íseal",
"Start chat": "Tosaigh comhrá",

View File

@ -401,7 +401,6 @@
"This room is not public. You will not be able to rejoin without an invite.": "Esta sala non é pública. Non poderá volver a ela sen un convite.",
"Failed to remove tag %(tagName)s from room": "Fallo ao eliminar a etiqueta %(tagName)s da sala",
"Failed to add tag %(tagName)s to room": "Fallo ao engadir a etiqueta %(tagName)s a sala",
"Key request sent.": "Petición de chave enviada.",
"Deops user with given id": "Degrada usuaria co id proporcionado",
"Code": "Código",
"Submit debug logs": "Enviar informes de depuración",
@ -776,8 +775,6 @@
"Never send encrypted messages to unverified sessions from this session": "Non enviar nunca desde esta sesión mensaxes cifradas a sesións non verificadas",
"Never send encrypted messages to unverified sessions in this room from this session": "Non enviar mensaxes cifradas desde esta sesión a sesións non verificadas nesta sala",
"Prompt before sending invites to potentially invalid matrix IDs": "Avisar antes de enviar convites a IDs de Matrix potencialmente incorrectos",
"Order rooms by name": "Ordenar salas por nome",
"Show rooms with unread notifications first": "Mostrar primeiro as salas que teñen notificacións sen ler",
"Show shortcuts to recently viewed rooms above the room list": "Mostrar atallos a salas vistas recentemente enriba da lista de salas",
"Show hidden events in timeline": "Mostrar na cronoloxía eventos ocultos",
"Straight rows of keys are easy to guess": "Palabras de letras contiguas son doadas de adiviñar",
@ -935,7 +932,6 @@
"Your keys are <b>not being backed up from this session</b>.": "As túas chaves <b>non están a ser copiadas desde esta sesión</b>.",
"Back up your keys before signing out to avoid losing them.": "Fai unha copia de apoio das chaves antes de saír para evitar perdelas.",
"Start using Key Backup": "Fai unha Copia de apoio das chaves",
"Clear notifications": "Eliminar notificacións",
"Enable desktop notifications for this session": "Activa as notificacións de escritorio para esta sesión",
"Enable audible notifications for this session": "Activa as notificacións por son para esta sesión",
"<a>Upgrade</a> to your own domain": "<a>Mellora</a> e usa un dominio propio",
@ -1102,15 +1098,10 @@
"Everyone in this room is verified": "Todas nesta sala están verificadas",
"Edit message": "Editar mensaxe",
"Mod": "Mod",
"Your key share request has been sent - please check your other sessions for key share requests.": "Enviouse a solicitude de compartir chave - comproba as túas outras sesións para solicitudes de compartir chave.",
"Light": "Claro",
"Dark": "Escuro",
"Customise your appearance": "Personaliza o aspecto",
"Appearance Settings only affect this %(brand)s session.": "Os axustes da aparencia só lle afectan a esta sesión %(brand)s.",
"Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.": "As solicitudes de compartir Chave envíanse ás outras túas sesións abertas. Se rexeitaches ou obviaches a solicitude nas outras sesións, preme aquí para volver a facer a solicitude.",
"If your other sessions do not have the key for this message you will not be able to decrypt them.": "Se as túas outras sesións non teñen a chave para esta mensaxe non poderás descifrala.",
"<requestLink>Re-request encryption keys</requestLink> from your other sessions.": "<requestLink>Volta a solicitar chaves de cifrado</requestLink> desde as outras sesións.",
"This message cannot be decrypted": "Esta mensaxe non pode descifrarse",
"Encrypted by an unverified session": "Cifrada por unha sesión non verificada",
"Unencrypted": "Non cifrada",
"Encrypted by a deleted session": "Cifrada por unha sesión eliminada",
@ -2638,7 +2629,6 @@
"& %(count)s more|one": "e %(count)s máis",
"Autoplay GIFs": "Reprod. automática GIFs",
"Autoplay videos": "Reprod. automática vídeo",
"Threaded messaging": "Mensaxes fiadas",
"The above, but in <Room /> as well": "O de arriba, pero tamén en <Room />",
"The above, but in any room you are joined or invited to as well": "O de enriba, pero en calquera sala á que te uniches ou foches convidada",
"%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s desafixou unha mensaxe desta sala. Mira tódalas mensaxes fixadas.",
@ -2984,7 +2974,6 @@
"Show join/leave messages (invites/removes/bans unaffected)": "Mostrar unirse/saír (convites/eliminacións/vetos non afectados)",
"Jump to date (adds /jumptodate and jump to date headers)": "Ir á data (engade cabeceiras /vaiadata e vai á data)",
"Use new room breadcrumbs": "Usar atallos para nova sala",
"Show extensible event representation of events": "Mostrar representación tipo evento extensible dos eventos",
"Let moderators hide messages pending moderation.": "Permitir que a moderación agoche mensaxes pendentes de moderar.",
"Back to thread": "Volver ao fío",
"Room members": "Membros da sala",
@ -3050,7 +3039,6 @@
"Use <arrows/> to scroll": "Usa <arrows/> para desprazarte",
"Feedback sent! Thanks, we appreciate it!": "Opinión enviada! Moitas grazas!",
"Thank you for trying the beta, please go into as much detail as you can so we can improve it.": "Grazas por probar a beta, entra en detalles canto queiras para así axudarnos a mellorala.",
"How can I leave the beta?": "Como podo saír da beta?",
"%(space1Name)s and %(space2Name)s": "%(space1Name)s e %(space2Name)s",
"Maximise": "Maximizar",
"<%(count)s spaces>|zero": "<cadea baleira>",
@ -3216,7 +3204,6 @@
"New room": "Nova sala",
"View older version of %(spaceName)s.": "Ver versión anterior de %(spaceName)s.",
"Upgrade this space to the recommended room version": "Actualiza este espazo á última versión recomendada da sala",
"How can I start a thread?": "Como abrir un fío?",
"Threads help keep conversations on-topic and easy to track. <a>Learn more</a>.": "Os fíos axudan a centrar a conversa nun tema e facilitan o seguimento. <a>Coñece máis</a>.",
"Keep discussions organised with threads.": "Marter as conversas organizadas en fíos.",
"Failed to join": "Non puideches entrar",
@ -3270,8 +3257,6 @@
"Audio devices": "Dispositivos de audio",
"Start messages with <code>/plain</code> to send without markdown and <code>/md</code> to send with.": "Comeza as mensaxes con <code>/plain</code> para enviar sen markdown e <code>/md</code> para enviar usándoo.",
"Enable Markdown": "Activar Markdown",
"To leave, return to this page and use the “%(leaveTheBeta)s” button.": "Para saír, volve a esta páxina e usa o botón \"%(leaveTheBeta)s\".",
"Use “%(replyInThread)s” when hovering over a message.": "Usa \"%(replyInThread)s\" ao poñerte sobre unha mensaxe.",
"You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device.": "Pechaches a sesión en tódolos dispositivos e non recibirás notificacións push. Para reactivalas notificacións volve a acceder en cada dispositivo.",
"If you want to retain access to your chat history in encrypted rooms, set up Key Backup or export your message keys from one of your other devices before proceeding.": "Se queres manter o acceso ao historial de conversas en salas cifradas, configura a Copia de Apoio das Chaves ou exporta as chaves das mensaxes desde un dos teus dispositivos antes de continuar.",
"Signing out your devices will delete the message encryption keys stored on them, making encrypted chat history unreadable.": "Ao pechar sesión nos teus dispositivos eliminarás as chaves de cifrado de mensaxes gardadas neles, facendo ilexible o historial de conversas cifrado.",
@ -3445,7 +3430,6 @@
"Interactively verify by emoji": "Verificar interactivamente usando emoji",
"Manually verify by text": "Verificar manualmente con texto",
"View all": "Ver todo",
"Improve your account security by following these recommendations": "Mellora a seguridade da túa conta seguindo estas recomendacións",
"Security recommendations": "Recomendacións de seguridade",
"Show": "Mostar",
"Filter devices": "Filtrar dispositivos",
@ -3458,7 +3442,6 @@
"No inactive sessions found.": "Non hai sesións inactivas.",
"No unverified sessions found.": "Non se atopan sesións sen verificar.",
"No verified sessions found.": "Non hai sesións sen verificar.",
"Consider signing out from old sessions (%(inactiveAgeDays)s days or older) you don't use anymore": "Considera pechar sesións antigas (máis de %(inactiveAgeDays)s días) que xa non uses",
"Inactive sessions": "Sesións inactivas",
"Verify your sessions for enhanced secure messaging or sign out from those you don't recognize or use anymore.": "Verifica as túas sesións para ter maior seguridade nas comunicacións e desconecta aquelas que non recoñezas ou uses.",
"Unverified sessions": "Sesións non verificadas",

Some files were not shown because too many files have changed in this diff Show More