Merge remote-tracking branch 'origin/develop' into feat/add-message-edition-wysiwyg-composer
This commit is contained in:
commit
e77f333fb6
124 changed files with 4370 additions and 1039 deletions
140
cypress/e2e/composer/composer.spec.ts
Normal file
140
cypress/e2e/composer/composer.spec.ts
Normal file
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
/// <reference types="cypress" />
|
||||
|
||||
import { SynapseInstance } from "../../plugins/synapsedocker";
|
||||
import { SettingLevel } from "../../../src/settings/SettingLevel";
|
||||
|
||||
describe("Composer", () => {
|
||||
let synapse: SynapseInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
cy.startSynapse("default").then(data => {
|
||||
synapse = data;
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cy.stopSynapse(synapse);
|
||||
});
|
||||
|
||||
describe("CIDER", () => {
|
||||
beforeEach(() => {
|
||||
cy.initTestUser(synapse, "Janet").then(() => {
|
||||
cy.createRoom({ name: "Composing Room" });
|
||||
});
|
||||
cy.viewRoomByName("Composing Room");
|
||||
});
|
||||
|
||||
it("sends a message when you click send or press Enter", () => {
|
||||
// Type a message
|
||||
cy.get('div[contenteditable=true]').type('my message 0');
|
||||
// It has not been sent yet
|
||||
cy.contains('.mx_EventTile_body', 'my message 0').should('not.exist');
|
||||
|
||||
// Click send
|
||||
cy.get('div[aria-label="Send message"]').click();
|
||||
// It has been sent
|
||||
cy.contains('.mx_EventTile_body', 'my message 0');
|
||||
|
||||
// Type another and press Enter afterwards
|
||||
cy.get('div[contenteditable=true]').type('my message 1{enter}');
|
||||
// It was sent
|
||||
cy.contains('.mx_EventTile_body', 'my message 1');
|
||||
});
|
||||
|
||||
it("can write formatted text", () => {
|
||||
cy.get('div[contenteditable=true]').type('my bold{ctrl+b} message');
|
||||
cy.get('div[aria-label="Send message"]').click();
|
||||
// Note: both "bold" and "message" are bold, which is probably surprising
|
||||
cy.contains('.mx_EventTile_body strong', 'bold message');
|
||||
});
|
||||
|
||||
describe("when Ctrl+Enter is required to send", () => {
|
||||
beforeEach(() => {
|
||||
cy.setSettingValue("MessageComposerInput.ctrlEnterToSend", null, SettingLevel.ACCOUNT, true);
|
||||
});
|
||||
|
||||
it("only sends when you press Ctrl+Enter", () => {
|
||||
// Type a message and press Enter
|
||||
cy.get('div[contenteditable=true]').type('my message 3{enter}');
|
||||
// It has not been sent yet
|
||||
cy.contains('.mx_EventTile_body', 'my message 3').should('not.exist');
|
||||
|
||||
// Press Ctrl+Enter
|
||||
cy.get('div[contenteditable=true]').type('{ctrl+enter}');
|
||||
// It was sent
|
||||
cy.contains('.mx_EventTile_body', 'my message 3');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("WYSIWYG", () => {
|
||||
beforeEach(() => {
|
||||
cy.enableLabsFeature("feature_wysiwyg_composer");
|
||||
cy.initTestUser(synapse, "Janet").then(() => {
|
||||
cy.createRoom({ name: "Composing Room" });
|
||||
});
|
||||
cy.viewRoomByName("Composing Room");
|
||||
});
|
||||
|
||||
it("sends a message when you click send or press Enter", () => {
|
||||
// Type a message
|
||||
cy.get('div[contenteditable=true]').type('my message 0');
|
||||
// It has not been sent yet
|
||||
cy.contains('.mx_EventTile_body', 'my message 0').should('not.exist');
|
||||
|
||||
// Click send
|
||||
cy.get('div[aria-label="Send message"]').click();
|
||||
// It has been sent
|
||||
cy.contains('.mx_EventTile_body', 'my message 0');
|
||||
|
||||
// Type another
|
||||
cy.get('div[contenteditable=true]').type('my message 1');
|
||||
// Press enter. Would be nice to just use {enter} but we can't because Cypress
|
||||
// does not trigger an insertParagraph when you do that.
|
||||
cy.get('div[contenteditable=true]').trigger('input', { inputType: "insertParagraph" });
|
||||
// It was sent
|
||||
cy.contains('.mx_EventTile_body', 'my message 1');
|
||||
});
|
||||
|
||||
it("can write formatted text", () => {
|
||||
cy.get('div[contenteditable=true]').type('my {ctrl+b}bold{ctrl+b} message');
|
||||
cy.get('div[aria-label="Send message"]').click();
|
||||
cy.contains('.mx_EventTile_body strong', 'bold');
|
||||
});
|
||||
|
||||
describe("when Ctrl+Enter is required to send", () => {
|
||||
beforeEach(() => {
|
||||
cy.setSettingValue("MessageComposerInput.ctrlEnterToSend", null, SettingLevel.ACCOUNT, true);
|
||||
});
|
||||
|
||||
it("only sends when you press Ctrl+Enter", () => {
|
||||
// Type a message and press Enter
|
||||
cy.get('div[contenteditable=true]').type('my message 3');
|
||||
cy.get('div[contenteditable=true]').trigger('input', { inputType: "insertParagraph" });
|
||||
// It has not been sent yet
|
||||
cy.contains('.mx_EventTile_body', 'my message 3').should('not.exist');
|
||||
|
||||
// Press Ctrl+Enter
|
||||
cy.get('div[contenteditable=true]').type('{ctrl+enter}');
|
||||
// It was sent
|
||||
cy.contains('.mx_EventTile_body', 'my message 3');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -60,7 +60,7 @@ describe("Create Room", () => {
|
|||
|
||||
cy.url().should("contain", "/#/room/#test-room-1:localhost");
|
||||
cy.stopMeasuring("from-submit-to-room");
|
||||
cy.get(".mx_RoomHeader_nametext").contains(name);
|
||||
cy.get(".mx_RoomHeader_topic").contains(topic);
|
||||
cy.contains(".mx_RoomHeader_nametext", name);
|
||||
cy.contains(".mx_RoomHeader_topic", topic);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -62,7 +62,7 @@ describe("Editing", () => {
|
|||
cy.get(".mx_BasicMessageComposer_input").type("Foo{backspace}{backspace}{backspace}{enter}");
|
||||
cy.checkA11y();
|
||||
});
|
||||
cy.get(".mx_RoomView_body .mx_EventTile").contains(".mx_EventTile[data-scroll-tokens]", "Message");
|
||||
cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", "Message");
|
||||
|
||||
// Assert that the edit composer has gone away
|
||||
cy.get(".mx_EditMessageComposer").should("not.exist");
|
||||
|
|
|
@ -46,7 +46,7 @@ describe("Consent", () => {
|
|||
|
||||
// Accept terms & conditions
|
||||
cy.get(".mx_QuestionDialog").within(() => {
|
||||
cy.get("#mx_BaseDialog_title").contains("Terms and Conditions");
|
||||
cy.contains("#mx_BaseDialog_title", "Terms and Conditions");
|
||||
cy.get(".mx_Dialog_primary").click();
|
||||
});
|
||||
|
||||
|
@ -58,7 +58,7 @@ describe("Consent", () => {
|
|||
cy.visit(url);
|
||||
|
||||
cy.get('[type="submit"]').click();
|
||||
cy.get("p").contains("Danke schon");
|
||||
cy.contains("p", "Danke schon");
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -21,13 +21,6 @@ import { SynapseInstance } from "../../plugins/synapsedocker";
|
|||
describe("Login", () => {
|
||||
let synapse: SynapseInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
cy.visit("/#/login");
|
||||
cy.startSynapse("consent").then(data => {
|
||||
synapse = data;
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cy.stopSynapse(synapse);
|
||||
});
|
||||
|
@ -37,7 +30,11 @@ describe("Login", () => {
|
|||
const password = "p4s5W0rD";
|
||||
|
||||
beforeEach(() => {
|
||||
cy.registerUser(synapse, username, password);
|
||||
cy.startSynapse("consent").then(data => {
|
||||
synapse = data;
|
||||
cy.registerUser(synapse, username, password);
|
||||
cy.visit("/#/login");
|
||||
});
|
||||
});
|
||||
|
||||
it("logs in with an existing account and lands on the home screen", () => {
|
||||
|
@ -65,14 +62,17 @@ describe("Login", () => {
|
|||
|
||||
describe("logout", () => {
|
||||
beforeEach(() => {
|
||||
cy.initTestUser(synapse, "Erin");
|
||||
cy.startSynapse("consent").then(data => {
|
||||
synapse = data;
|
||||
cy.initTestUser(synapse, "Erin");
|
||||
});
|
||||
});
|
||||
|
||||
it("should go to login page on logout", () => {
|
||||
cy.get('[aria-label="User menu"]').click();
|
||||
|
||||
// give a change for the outstanding requests queue to settle before logging out
|
||||
cy.wait(500);
|
||||
cy.wait(2000);
|
||||
|
||||
cy.get(".mx_UserMenu_contextMenu").within(() => {
|
||||
cy.get(".mx_UserMenu_iconSignOut").click();
|
||||
|
@ -94,7 +94,7 @@ describe("Login", () => {
|
|||
cy.get('[aria-label="User menu"]').click();
|
||||
|
||||
// give a change for the outstanding requests queue to settle before logging out
|
||||
cy.wait(500);
|
||||
cy.wait(2000);
|
||||
|
||||
cy.get(".mx_UserMenu_contextMenu").within(() => {
|
||||
cy.get(".mx_UserMenu_iconSignOut").click();
|
||||
|
|
|
@ -94,7 +94,7 @@ describe("Polls", () => {
|
|||
cy.stopSynapse(synapse);
|
||||
});
|
||||
|
||||
it("Open polls can be created and voted in", () => {
|
||||
it("should be creatable and votable", () => {
|
||||
let bot: MatrixClient;
|
||||
cy.getBot(synapse, { displayName: "BotBob" }).then(_bot => {
|
||||
bot = _bot;
|
||||
|
@ -122,7 +122,7 @@ describe("Polls", () => {
|
|||
createPoll(pollParams);
|
||||
|
||||
// Wait for message to send, get its ID and save as @pollId
|
||||
cy.get(".mx_RoomView_body .mx_EventTile").contains(".mx_EventTile[data-scroll-tokens]", pollParams.title)
|
||||
cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", pollParams.title)
|
||||
.invoke("attr", "data-scroll-tokens").as("pollId");
|
||||
|
||||
cy.get<string>("@pollId").then(pollId => {
|
||||
|
@ -159,7 +159,92 @@ describe("Polls", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("displays polls correctly in thread panel", () => {
|
||||
it("should be editable from context menu if no votes have been cast", () => {
|
||||
let bot: MatrixClient;
|
||||
cy.getBot(synapse, { displayName: "BotBob" }).then(_bot => {
|
||||
bot = _bot;
|
||||
});
|
||||
|
||||
let roomId: string;
|
||||
cy.createRoom({}).then(_roomId => {
|
||||
roomId = _roomId;
|
||||
cy.inviteUser(roomId, bot.getUserId());
|
||||
cy.visit('/#/room/' + roomId);
|
||||
});
|
||||
|
||||
cy.openMessageComposerOptions().within(() => {
|
||||
cy.get('[aria-label="Poll"]').click();
|
||||
});
|
||||
|
||||
const pollParams = {
|
||||
title: 'Does the polls feature work?',
|
||||
options: ['Yes', 'No', 'Maybe'],
|
||||
};
|
||||
createPoll(pollParams);
|
||||
|
||||
// Wait for message to send, get its ID and save as @pollId
|
||||
cy.get(".mx_RoomView_body .mx_EventTile").contains(".mx_EventTile[data-scroll-tokens]", pollParams.title)
|
||||
.invoke("attr", "data-scroll-tokens").as("pollId");
|
||||
|
||||
cy.get<string>("@pollId").then(pollId => {
|
||||
// Open context menu
|
||||
getPollTile(pollId).rightclick();
|
||||
|
||||
// Select edit item
|
||||
cy.get('.mx_ContextualMenu').within(() => {
|
||||
cy.get('[aria-label="Edit"]').click();
|
||||
});
|
||||
|
||||
// Expect poll editing dialog
|
||||
cy.get('.mx_PollCreateDialog');
|
||||
});
|
||||
});
|
||||
|
||||
it("should not be editable from context menu if votes have been cast", () => {
|
||||
let bot: MatrixClient;
|
||||
cy.getBot(synapse, { displayName: "BotBob" }).then(_bot => {
|
||||
bot = _bot;
|
||||
});
|
||||
|
||||
let roomId: string;
|
||||
cy.createRoom({}).then(_roomId => {
|
||||
roomId = _roomId;
|
||||
cy.inviteUser(roomId, bot.getUserId());
|
||||
cy.visit('/#/room/' + roomId);
|
||||
});
|
||||
|
||||
cy.openMessageComposerOptions().within(() => {
|
||||
cy.get('[aria-label="Poll"]').click();
|
||||
});
|
||||
|
||||
const pollParams = {
|
||||
title: 'Does the polls feature work?',
|
||||
options: ['Yes', 'No', 'Maybe'],
|
||||
};
|
||||
createPoll(pollParams);
|
||||
|
||||
// Wait for message to send, get its ID and save as @pollId
|
||||
cy.get(".mx_RoomView_body .mx_EventTile").contains(".mx_EventTile[data-scroll-tokens]", pollParams.title)
|
||||
.invoke("attr", "data-scroll-tokens").as("pollId");
|
||||
|
||||
cy.get<string>("@pollId").then(pollId => {
|
||||
// Bot votes 'Maybe' in the poll
|
||||
botVoteForOption(bot, roomId, pollId, pollParams.options[2]);
|
||||
|
||||
// Open context menu
|
||||
getPollTile(pollId).rightclick();
|
||||
|
||||
// Select edit item
|
||||
cy.get('.mx_ContextualMenu').within(() => {
|
||||
cy.get('[aria-label="Edit"]').click();
|
||||
});
|
||||
|
||||
// Expect error dialog
|
||||
cy.get('.mx_ErrorDialog');
|
||||
});
|
||||
});
|
||||
|
||||
it("should be displayed correctly in thread panel", () => {
|
||||
let botBob: MatrixClient;
|
||||
let botCharlie: MatrixClient;
|
||||
cy.getBot(synapse, { displayName: "BotBob" }).then(_bot => {
|
||||
|
@ -190,7 +275,7 @@ describe("Polls", () => {
|
|||
createPoll(pollParams);
|
||||
|
||||
// Wait for message to send, get its ID and save as @pollId
|
||||
cy.get(".mx_RoomView_body .mx_EventTile").contains(".mx_EventTile[data-scroll-tokens]", pollParams.title)
|
||||
cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", pollParams.title)
|
||||
.invoke("attr", "data-scroll-tokens").as("pollId");
|
||||
|
||||
cy.get<string>("@pollId").then(pollId => {
|
||||
|
|
|
@ -93,7 +93,7 @@ describe("Room Directory", () => {
|
|||
cy.get(".mx_RoomDirectory_dialogWrapper").percySnapshotElement("Room Directory - filtered no results");
|
||||
|
||||
cy.get('.mx_RoomDirectory_dialogWrapper [name="dirsearch"]').type("{selectAll}{backspace}test1234");
|
||||
cy.get(".mx_RoomDirectory_dialogWrapper").contains(".mx_RoomDirectory_listItem", name)
|
||||
cy.contains(".mx_RoomDirectory_dialogWrapper .mx_RoomDirectory_listItem", name)
|
||||
.should("exist").as("resultRow");
|
||||
cy.get(".mx_RoomDirectory_dialogWrapper").percySnapshotElement("Room Directory - filtered one result");
|
||||
cy.get("@resultRow").find(".mx_AccessibleButton").contains("Join").click();
|
||||
|
|
|
@ -293,7 +293,7 @@ describe("Sliding Sync", () => {
|
|||
]);
|
||||
|
||||
cy.contains(".mx_RoomTile", "Reject").click();
|
||||
cy.get(".mx_RoomView").contains(".mx_AccessibleButton", "Reject").click();
|
||||
cy.contains(".mx_RoomView .mx_AccessibleButton", "Reject").click();
|
||||
|
||||
// wait for the rejected room to disappear
|
||||
cy.get(".mx_RoomTile").should('have.length', 3);
|
||||
|
@ -328,8 +328,8 @@ describe("Sliding Sync", () => {
|
|||
cy.getClient().then(cli => cli.setRoomTag(roomId, "m.favourite", { order: 0.5 }));
|
||||
});
|
||||
|
||||
cy.get('.mx_RoomSublist[aria-label="Favourites"]').contains(".mx_RoomTile", "Favourite DM").should("exist");
|
||||
cy.get('.mx_RoomSublist[aria-label="People"]').contains(".mx_RoomTile", "Favourite DM").should("not.exist");
|
||||
cy.contains('.mx_RoomSublist[aria-label="Favourites"] .mx_RoomTile', "Favourite DM").should("exist");
|
||||
cy.contains('.mx_RoomSublist[aria-label="People"] .mx_RoomTile', "Favourite DM").should("not.exist");
|
||||
});
|
||||
|
||||
// Regression test for a bug in SS mode, but would be useful to have in non-SS mode too.
|
||||
|
|
|
@ -83,26 +83,26 @@ describe("Spaces", () => {
|
|||
cy.get('input[label="Name"]').type("Let's have a Riot");
|
||||
cy.get('input[label="Address"]').should("have.value", "lets-have-a-riot");
|
||||
cy.get('textarea[label="Description"]').type("This is a space to reminisce Riot.im!");
|
||||
cy.get(".mx_AccessibleButton").contains("Create").click();
|
||||
cy.contains(".mx_AccessibleButton", "Create").click();
|
||||
});
|
||||
|
||||
// Create the default General & Random rooms, as well as a custom "Jokes" room
|
||||
cy.get('input[label="Room name"][value="General"]').should("exist");
|
||||
cy.get('input[label="Room name"][value="Random"]').should("exist");
|
||||
cy.get('input[placeholder="Support"]').type("Jokes");
|
||||
cy.get(".mx_AccessibleButton").contains("Continue").click();
|
||||
cy.contains(".mx_AccessibleButton", "Continue").click();
|
||||
|
||||
// Copy matrix.to link
|
||||
cy.get(".mx_SpacePublicShare_shareButton").focus().realClick();
|
||||
cy.getClipboardText().should("eq", "https://matrix.to/#/#lets-have-a-riot:localhost");
|
||||
|
||||
// Go to space home
|
||||
cy.get(".mx_AccessibleButton").contains("Go to my first room").click();
|
||||
cy.contains(".mx_AccessibleButton", "Go to my first room").click();
|
||||
|
||||
// Assert rooms exist in the room list
|
||||
cy.get(".mx_RoomList").contains(".mx_RoomTile", "General").should("exist");
|
||||
cy.get(".mx_RoomList").contains(".mx_RoomTile", "Random").should("exist");
|
||||
cy.get(".mx_RoomList").contains(".mx_RoomTile", "Jokes").should("exist");
|
||||
cy.contains(".mx_RoomList .mx_RoomTile", "General").should("exist");
|
||||
cy.contains(".mx_RoomList .mx_RoomTile", "Random").should("exist");
|
||||
cy.contains(".mx_RoomList .mx_RoomTile", "Jokes").should("exist");
|
||||
});
|
||||
|
||||
it("should allow user to create private space", () => {
|
||||
|
@ -113,7 +113,7 @@ describe("Spaces", () => {
|
|||
cy.get('input[label="Name"]').type("This is not a Riot");
|
||||
cy.get('input[label="Address"]').should("not.exist");
|
||||
cy.get('textarea[label="Description"]').type("This is a private space of mourning Riot.im...");
|
||||
cy.get(".mx_AccessibleButton").contains("Create").click();
|
||||
cy.contains(".mx_AccessibleButton", "Create").click();
|
||||
});
|
||||
|
||||
cy.get(".mx_SpaceRoomView_privateScope_meAndMyTeammatesButton").click();
|
||||
|
@ -122,20 +122,20 @@ describe("Spaces", () => {
|
|||
cy.get('input[label="Room name"][value="General"]').should("exist");
|
||||
cy.get('input[label="Room name"][value="Random"]').should("exist");
|
||||
cy.get('input[placeholder="Support"]').type("Projects");
|
||||
cy.get(".mx_AccessibleButton").contains("Continue").click();
|
||||
cy.contains(".mx_AccessibleButton", "Continue").click();
|
||||
|
||||
cy.get(".mx_SpaceRoomView").should("contain", "Invite your teammates");
|
||||
cy.get(".mx_AccessibleButton").contains("Skip for now").click();
|
||||
cy.contains(".mx_AccessibleButton", "Skip for now").click();
|
||||
|
||||
// Assert rooms exist in the room list
|
||||
cy.get(".mx_RoomList").contains(".mx_RoomTile", "General").should("exist");
|
||||
cy.get(".mx_RoomList").contains(".mx_RoomTile", "Random").should("exist");
|
||||
cy.get(".mx_RoomList").contains(".mx_RoomTile", "Projects").should("exist");
|
||||
cy.contains(".mx_RoomList .mx_RoomTile", "General").should("exist");
|
||||
cy.contains(".mx_RoomList .mx_RoomTile", "Random").should("exist");
|
||||
cy.contains(".mx_RoomList .mx_RoomTile", "Projects").should("exist");
|
||||
|
||||
// Assert rooms exist in the space explorer
|
||||
cy.get(".mx_SpaceHierarchy_list").contains(".mx_SpaceHierarchy_roomTile", "General").should("exist");
|
||||
cy.get(".mx_SpaceHierarchy_list").contains(".mx_SpaceHierarchy_roomTile", "Random").should("exist");
|
||||
cy.get(".mx_SpaceHierarchy_list").contains(".mx_SpaceHierarchy_roomTile", "Projects").should("exist");
|
||||
cy.contains(".mx_SpaceHierarchy_list .mx_SpaceHierarchy_roomTile", "General").should("exist");
|
||||
cy.contains(".mx_SpaceHierarchy_list .mx_SpaceHierarchy_roomTile", "Random").should("exist");
|
||||
cy.contains(".mx_SpaceHierarchy_list .mx_SpaceHierarchy_roomTile", "Projects").should("exist");
|
||||
});
|
||||
|
||||
it("should allow user to create just-me space", () => {
|
||||
|
@ -155,10 +155,10 @@ describe("Spaces", () => {
|
|||
cy.get(".mx_SpaceRoomView_privateScope_justMeButton").click();
|
||||
|
||||
cy.get(".mx_AddExistingToSpace_entry").click();
|
||||
cy.get(".mx_AccessibleButton").contains("Add").click();
|
||||
cy.contains(".mx_AccessibleButton", "Add").click();
|
||||
|
||||
cy.get(".mx_RoomList").contains(".mx_RoomTile", "Sample Room").should("exist");
|
||||
cy.get(".mx_SpaceHierarchy_list").contains(".mx_SpaceHierarchy_roomTile", "Sample Room").should("exist");
|
||||
cy.contains(".mx_RoomList .mx_RoomTile", "Sample Room").should("exist");
|
||||
cy.contains(".mx_SpaceHierarchy_list .mx_SpaceHierarchy_roomTile", "Sample Room").should("exist");
|
||||
});
|
||||
|
||||
it("should allow user to invite another to a space", () => {
|
||||
|
@ -186,7 +186,7 @@ describe("Spaces", () => {
|
|||
|
||||
cy.get(".mx_InviteDialog_other").within(() => {
|
||||
cy.get('input[type="text"]').type(bot.getUserId());
|
||||
cy.get(".mx_AccessibleButton").contains("Invite").click();
|
||||
cy.contains(".mx_AccessibleButton", "Invite").click();
|
||||
});
|
||||
|
||||
cy.get(".mx_InviteDialog_other").should("not.exist");
|
||||
|
|
|
@ -53,6 +53,7 @@ describe("Threads", () => {
|
|||
cy.window().should("have.prop", "beforeReload", true);
|
||||
|
||||
cy.leaveBeta("Threads");
|
||||
cy.wait(1000);
|
||||
// after reload the property should be gone
|
||||
cy.window().should("not.have.prop", "beforeReload");
|
||||
});
|
||||
|
@ -66,6 +67,7 @@ describe("Threads", () => {
|
|||
cy.window().should("have.prop", "beforeReload", true);
|
||||
|
||||
cy.joinBeta("Threads");
|
||||
cy.wait(1000);
|
||||
// after reload the property should be gone
|
||||
cy.window().should("not.have.prop", "beforeReload");
|
||||
});
|
||||
|
@ -92,7 +94,7 @@ describe("Threads", () => {
|
|||
cy.get(".mx_RoomView_body .mx_BasicMessageComposer_input").type("Hello Mr. Bot{enter}");
|
||||
|
||||
// Wait for message to send, get its ID and save as @threadId
|
||||
cy.get(".mx_RoomView_body .mx_EventTile").contains(".mx_EventTile[data-scroll-tokens]", "Hello Mr. Bot")
|
||||
cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", "Hello Mr. Bot")
|
||||
.invoke("attr", "data-scroll-tokens").as("threadId");
|
||||
|
||||
// Bot starts thread
|
||||
|
@ -116,21 +118,21 @@ describe("Threads", () => {
|
|||
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_content").should("contain", "Test");
|
||||
|
||||
// User reacts to message instead
|
||||
cy.get(".mx_ThreadView .mx_EventTile").contains(".mx_EventTile_line", "Hello there")
|
||||
cy.contains(".mx_ThreadView .mx_EventTile .mx_EventTile_line", "Hello there")
|
||||
.find('[aria-label="React"]').click({ force: true }); // Cypress has no ability to hover
|
||||
cy.get(".mx_EmojiPicker").within(() => {
|
||||
cy.get('input[type="text"]').type("wave");
|
||||
cy.get('[role="menuitem"]').contains("👋").click();
|
||||
cy.contains('[role="menuitem"]', "👋").click();
|
||||
});
|
||||
|
||||
// User redacts their prior response
|
||||
cy.get(".mx_ThreadView .mx_EventTile").contains(".mx_EventTile_line", "Test")
|
||||
cy.contains(".mx_ThreadView .mx_EventTile .mx_EventTile_line", "Test")
|
||||
.find('[aria-label="Options"]').click({ force: true }); // Cypress has no ability to hover
|
||||
cy.get(".mx_IconizedContextMenu").within(() => {
|
||||
cy.get('[role="menuitem"]').contains("Remove").click();
|
||||
cy.contains('[role="menuitem"]', "Remove").click();
|
||||
});
|
||||
cy.get(".mx_TextInputDialog").within(() => {
|
||||
cy.get(".mx_Dialog_primary").contains("Remove").click();
|
||||
cy.contains(".mx_Dialog_primary", "Remove").click();
|
||||
});
|
||||
|
||||
// User asserts summary was updated correctly
|
||||
|
@ -171,7 +173,7 @@ describe("Threads", () => {
|
|||
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_content").should("contain", "Great!");
|
||||
|
||||
// User edits & asserts
|
||||
cy.get(".mx_ThreadView .mx_EventTile_last").contains(".mx_EventTile_line", "Great!").within(() => {
|
||||
cy.contains(".mx_ThreadView .mx_EventTile_last .mx_EventTile_line", "Great!").within(() => {
|
||||
cy.get('[aria-label="Edit"]').click({ force: true }); // Cypress has no ability to hover
|
||||
cy.get(".mx_BasicMessageComposer_input").type(" How about yourself?{enter}");
|
||||
});
|
||||
|
@ -234,7 +236,7 @@ describe("Threads", () => {
|
|||
cy.get(".mx_RoomView_body .mx_BasicMessageComposer_input").type("Hello Mr. Bot{enter}");
|
||||
|
||||
// Create thread
|
||||
cy.get(".mx_RoomView_body .mx_EventTile").contains(".mx_EventTile[data-scroll-tokens]", "Hello Mr. Bot")
|
||||
cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", "Hello Mr. Bot")
|
||||
.realHover().find(".mx_MessageActionBar_threadButton").click();
|
||||
cy.get(".mx_ThreadView_timelinePanelWrapper").should("have.length", 1);
|
||||
|
||||
|
@ -256,7 +258,7 @@ describe("Threads", () => {
|
|||
cy.get(".mx_RoomView_body .mx_BasicMessageComposer_input").type("Hello Mr. Bot{enter}");
|
||||
|
||||
// Create thread
|
||||
cy.get(".mx_RoomView_body .mx_EventTile").contains(".mx_EventTile[data-scroll-tokens]", "Hello Mr. Bot")
|
||||
cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", "Hello Mr. Bot")
|
||||
.realHover().find(".mx_MessageActionBar_threadButton").click();
|
||||
cy.get(".mx_ThreadView_timelinePanelWrapper").should("have.length", 1);
|
||||
|
||||
|
@ -268,7 +270,7 @@ describe("Threads", () => {
|
|||
cy.get(".mx_BaseCard_close").click();
|
||||
|
||||
// Open existing thread
|
||||
cy.get(".mx_RoomView_body .mx_EventTile").contains(".mx_EventTile[data-scroll-tokens]", "Hello Mr. Bot")
|
||||
cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", "Hello Mr. Bot")
|
||||
.realHover().find(".mx_MessageActionBar_threadButton").click();
|
||||
cy.get(".mx_ThreadView_timelinePanelWrapper").should("have.length", 1);
|
||||
cy.get(".mx_BaseCard .mx_EventTile").should("contain", "Hello Mr. Bot");
|
||||
|
|
|
@ -329,7 +329,7 @@ describe("Timeline", () => {
|
|||
cy.getComposer().type(`${MESSAGE}{enter}`);
|
||||
|
||||
// Reply to the message
|
||||
cy.get(".mx_RoomView_body").contains(".mx_EventTile_line", "Hello world").within(() => {
|
||||
cy.contains(".mx_RoomView_body .mx_EventTile_line", "Hello world").within(() => {
|
||||
cy.get('[aria-label="Reply"]').click({ force: true }); // Cypress has no ability to hover
|
||||
});
|
||||
};
|
||||
|
|
|
@ -24,7 +24,7 @@ function assertNoToasts(): void {
|
|||
}
|
||||
|
||||
function getToast(expectedTitle: string): Chainable<JQuery> {
|
||||
return cy.get(".mx_Toast_toast").contains("h2", expectedTitle).should("exist").closest(".mx_Toast_toast");
|
||||
return cy.contains(".mx_Toast_toast h2", expectedTitle).should("exist").closest(".mx_Toast_toast");
|
||||
}
|
||||
|
||||
function acceptToast(expectedTitle: string): void {
|
||||
|
|
|
@ -91,7 +91,7 @@ Cypress.Commands.add("loginUser", (synapse: SynapseInstance, username: string, p
|
|||
Cypress.Commands.add("initTestUser", (synapse: SynapseInstance, displayName: string, prelaunchFn?: () => void): Chainable<UserCredentials> => {
|
||||
// XXX: work around Cypress not clearing IDB between tests
|
||||
cy.window({ log: false }).then(win => {
|
||||
win.indexedDB.databases().then(databases => {
|
||||
win.indexedDB.databases()?.then(databases => {
|
||||
databases.forEach(database => {
|
||||
win.indexedDB.deleteDatabase(database.name);
|
||||
});
|
||||
|
|
|
@ -153,7 +153,7 @@ Cypress.Commands.add("openRoomSettings", (tab?: string): Chainable<JQuery<HTMLEl
|
|||
|
||||
Cypress.Commands.add("switchTab", (tab: string): Chainable<JQuery<HTMLElement>> => {
|
||||
return cy.get(".mx_TabbedView_tabLabels").within(() => {
|
||||
cy.get(".mx_TabbedView_tabLabel").contains(tab).click();
|
||||
cy.contains(".mx_TabbedView_tabLabel", tab).click();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -162,13 +162,13 @@ Cypress.Commands.add("closeDialog", (): Chainable<JQuery<HTMLElement>> => {
|
|||
});
|
||||
|
||||
Cypress.Commands.add("joinBeta", (name: string): Chainable<JQuery<HTMLElement>> => {
|
||||
return cy.get(".mx_BetaCard_title").contains(name).closest(".mx_BetaCard").within(() => {
|
||||
return cy.contains(".mx_BetaCard_title", name).closest(".mx_BetaCard").within(() => {
|
||||
return cy.get(".mx_BetaCard_buttons").contains("Join the beta").click();
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add("leaveBeta", (name: string): Chainable<JQuery<HTMLElement>> => {
|
||||
return cy.get(".mx_BetaCard_title").contains(name).closest(".mx_BetaCard").within(() => {
|
||||
return cy.contains(".mx_BetaCard_title", name).closest(".mx_BetaCard").within(() => {
|
||||
return cy.get(".mx_BetaCard_buttons").contains("Leave the beta").click();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@matrix-org/analytics-events": "^0.2.0",
|
||||
"@matrix-org/matrix-wysiwyg": "^0.2.0",
|
||||
"@matrix-org/matrix-wysiwyg": "^0.3.0",
|
||||
"@matrix-org/react-sdk-module-api": "^0.0.3",
|
||||
"@sentry/browser": "^6.11.0",
|
||||
"@sentry/tracing": "^6.11.0",
|
||||
|
|
|
@ -96,6 +96,7 @@
|
|||
@import "./views/auth/_CountryDropdown.pcss";
|
||||
@import "./views/auth/_InteractiveAuthEntryComponents.pcss";
|
||||
@import "./views/auth/_LanguageSelector.pcss";
|
||||
@import "./views/auth/_LoginWithQR.pcss";
|
||||
@import "./views/auth/_PassphraseField.pcss";
|
||||
@import "./views/auth/_Welcome.pcss";
|
||||
@import "./views/avatars/_BaseAvatar.pcss";
|
||||
|
|
171
res/css/views/auth/_LoginWithQR.pcss
Normal file
171
res/css/views/auth/_LoginWithQR.pcss
Normal file
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
.mx_LoginWithQRSection .mx_AccessibleButton {
|
||||
margin-right: $spacing-12;
|
||||
}
|
||||
|
||||
.mx_AuthPage .mx_LoginWithQR {
|
||||
.mx_AccessibleButton {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.mx_AccessibleButton + .mx_AccessibleButton {
|
||||
margin-top: $spacing-8;
|
||||
}
|
||||
|
||||
.mx_LoginWithQR_separator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
|
||||
&::before, &::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
border-bottom: 1px solid $quinary-content;
|
||||
}
|
||||
|
||||
&:not(:empty) {
|
||||
&::before {
|
||||
margin-right: 1em;
|
||||
}
|
||||
&::after {
|
||||
margin-left: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
font-size: $font-15px;
|
||||
}
|
||||
|
||||
.mx_UserSettingsDialog .mx_LoginWithQR {
|
||||
.mx_AccessibleButton + .mx_AccessibleButton {
|
||||
margin-left: $spacing-12;
|
||||
}
|
||||
|
||||
font-size: $font-14px;
|
||||
|
||||
h1 {
|
||||
font-size: $font-24px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.mx_QRCode {
|
||||
padding: $spacing-12 $spacing-40;
|
||||
margin: $spacing-28 0;
|
||||
}
|
||||
|
||||
.mx_LoginWithQR_buttons {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mx_LoginWithQR_qrWrapper {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_LoginWithQR {
|
||||
min-height: 350px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.mx_LoginWithQR_centreTitle {
|
||||
h1 {
|
||||
text-align: centre;
|
||||
}
|
||||
}
|
||||
|
||||
h1 > svg {
|
||||
&.normal {
|
||||
color: $secondary-content;
|
||||
}
|
||||
&.error {
|
||||
color: $alert;
|
||||
}
|
||||
&.success {
|
||||
color: $accent;
|
||||
}
|
||||
height: 1.3em;
|
||||
margin-right: $spacing-8;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.mx_LoginWithQR_confirmationDigits {
|
||||
text-align: center;
|
||||
margin: $spacing-48 auto;
|
||||
font-weight: 600;
|
||||
font-size: $font-24px;
|
||||
color: $primary-content;
|
||||
}
|
||||
|
||||
.mx_LoginWithQR_confirmationAlert {
|
||||
border: 1px solid $quaternary-content;
|
||||
border-radius: $spacing-8;
|
||||
padding: $spacing-8;
|
||||
line-height: 1.5em;
|
||||
display: flex;
|
||||
|
||||
svg {
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_LoginWithQR_separator {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style-position: inside;
|
||||
padding-inline-start: 0;
|
||||
|
||||
li::marker {
|
||||
color: $accent;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_LoginWithQR_BackButton {
|
||||
height: $spacing-12;
|
||||
margin-bottom: $spacing-24;
|
||||
svg {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_LoginWithQR_main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.mx_QRCode {
|
||||
border: 1px solid $quinary-content;
|
||||
border-radius: $spacing-8;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.mx_LoginWithQR_spinner {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
3
res/img/element-icons/back.svg
Normal file
3
res/img/element-icons/back.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="18" height="14" viewBox="0 0 18 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17 7H1M1 7L7 13M1 7L7 1" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 227 B |
11
res/img/element-icons/devices.svg
Normal file
11
res/img/element-icons/devices.svg
Normal file
|
@ -0,0 +1,11 @@
|
|||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_721_35674)">
|
||||
<path d="M5.33333 9.33313C5.33333 8.5998 5.93333 7.9998 6.66667 7.9998H28C28.7333 7.9998 29.3333 7.3998 29.3333 6.66646C29.3333 5.93313 28.7333 5.33313 28 5.33313H5.33333C3.86667 5.33313 2.66667 6.53313 2.66667 7.9998V22.6665H2C0.893333 22.6665 0 23.5598 0 24.6665C0 25.7731 0.893333 26.6665 2 26.6665H18.6667V22.6665H5.33333V9.33313ZM30.6667 10.6665H22.6667C21.9333 10.6665 21.3333 11.2665 21.3333 11.9998V25.3331C21.3333 26.0665 21.9333 26.6665 22.6667 26.6665H30.6667C31.4 26.6665 32 26.0665 32 25.3331V11.9998C32 11.2665 31.4 10.6665 30.6667 10.6665ZM29.3333 22.6665H24V13.3331H29.3333V22.6665Z" fill="currentColor"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_721_35674">
|
||||
<rect width="32" height="32" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 891 B |
4
res/img/element-icons/qrcode.svg
Normal file
4
res/img/element-icons/qrcode.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="13" height="12" viewBox="0 0 13 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.16667 12V10.6667H8.5V12H7.16667ZM5.83333 10.6667V7.33333H7.16667V10.6667H5.83333ZM11.1667 8.66667V6H12.5V8.66667H11.1667ZM9.83333 6V4.66667H11.1667V6H9.83333ZM1.83333 7.33333V6H3.16667V7.33333H1.83333ZM0.5 6V4.66667H1.83333V6H0.5ZM6.5 1.33333V0H7.83333V1.33333H6.5ZM1.33333 3.16667H3.66667V0.833333H1.33333V3.16667ZM1 4C0.855556 4 0.736111 3.95278 0.641667 3.85833C0.547222 3.76389 0.5 3.64444 0.5 3.5V0.5C0.5 0.355556 0.547222 0.236111 0.641667 0.141667C0.736111 0.0472223 0.855556 0 1 0H4C4.14444 0 4.26389 0.0472223 4.35833 0.141667C4.45278 0.236111 4.5 0.355556 4.5 0.5V3.5C4.5 3.64444 4.45278 3.76389 4.35833 3.85833C4.26389 3.95278 4.14444 4 4 4H1ZM1.33333 11.1667H3.66667V8.83333H1.33333V11.1667ZM1 12C0.855556 12 0.736111 11.9528 0.641667 11.8583C0.547222 11.7639 0.5 11.6444 0.5 11.5V8.5C0.5 8.35556 0.547222 8.23611 0.641667 8.14167C0.736111 8.04722 0.855556 8 1 8H4C4.14444 8 4.26389 8.04722 4.35833 8.14167C4.45278 8.23611 4.5 8.35556 4.5 8.5V11.5C4.5 11.6444 4.45278 11.7639 4.35833 11.8583C4.26389 11.9528 4.14444 12 4 12H1ZM9.33333 3.16667H11.6667V0.833333H9.33333V3.16667ZM9 4C8.85556 4 8.73611 3.95278 8.64167 3.85833C8.54722 3.76389 8.5 3.64444 8.5 3.5V0.5C8.5 0.355556 8.54722 0.236111 8.64167 0.141667C8.73611 0.0472223 8.85556 0 9 0H12C12.1444 0 12.2639 0.0472223 12.3583 0.141667C12.4528 0.236111 12.5 0.355556 12.5 0.5V3.5C12.5 3.64444 12.4528 3.76389 12.3583 3.85833C12.2639 3.95278 12.1444 4 12 4H9ZM9.83333 12V10H8.5V8.66667H11.1667V10.6667H12.5V12H9.83333ZM7.16667 7.33333V6H9.83333V7.33333H7.16667ZM4.5 7.33333V6H3.16667V4.66667H7.16667V6H5.83333V7.33333H4.5ZM5.16667 4V1.33333H6.5V2.66667H7.83333V4H5.16667ZM2 2.5V1.5H3V2.5H2ZM2 10.5V9.5H3V10.5H2ZM10 2.5V1.5H11V2.5H10Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
|
@ -43,7 +43,6 @@ import { RoomUpload } from "./models/RoomUpload";
|
|||
import SettingsStore from "./settings/SettingsStore";
|
||||
import { decorateStartSendingTime, sendRoundTripMetric } from "./sendTimePerformanceMetrics";
|
||||
import { TimelineRenderingType } from "./contexts/RoomContext";
|
||||
import { RoomViewStore } from "./stores/RoomViewStore";
|
||||
import { addReplyToMessageContent } from "./utils/Reply";
|
||||
import ErrorDialog from "./components/views/dialogs/ErrorDialog";
|
||||
import UploadFailureDialog from "./components/views/dialogs/UploadFailureDialog";
|
||||
|
@ -51,6 +50,7 @@ import UploadConfirmDialog from "./components/views/dialogs/UploadConfirmDialog"
|
|||
import { createThumbnail } from "./utils/image-media";
|
||||
import { attachRelation } from "./components/views/rooms/SendMessageComposer";
|
||||
import { doMaybeLocalRoomAction } from "./utils/local-room";
|
||||
import { SdkContextClass } from "./contexts/SDKContext";
|
||||
|
||||
// scraped out of a macOS hidpi (5660ppm) screenshot png
|
||||
// 5669 px (x-axis) , 5669 px (y-axis) , per metre
|
||||
|
@ -361,7 +361,7 @@ export default class ContentMessages {
|
|||
return;
|
||||
}
|
||||
|
||||
const replyToEvent = RoomViewStore.instance.getQuotingEvent();
|
||||
const replyToEvent = SdkContextClass.instance.roomViewStore.getQuotingEvent();
|
||||
if (!this.mediaConfig) { // hot-path optimization to not flash a spinner if we don't need to
|
||||
const modal = Modal.createDialog(Spinner, null, 'mx_Dialog_spinner');
|
||||
await this.ensureMediaConfigFetched(matrixClient);
|
||||
|
|
|
@ -426,7 +426,7 @@ export async function restoreFromLocalStorage(opts?: { ignoreGuest?: boolean }):
|
|||
const { hsUrl, isUrl, hasAccessToken, accessToken, userId, deviceId, isGuest } = await getStoredSessionVars();
|
||||
|
||||
if (hasAccessToken && !accessToken) {
|
||||
abortLogin();
|
||||
await abortLogin();
|
||||
}
|
||||
|
||||
if (accessToken && userId && hsUrl) {
|
||||
|
|
|
@ -41,12 +41,12 @@ import SettingsStore from "./settings/SettingsStore";
|
|||
import { hideToast as hideNotificationsToast } from "./toasts/DesktopNotificationsToast";
|
||||
import { SettingLevel } from "./settings/SettingLevel";
|
||||
import { isPushNotifyDisabled } from "./settings/controllers/NotificationControllers";
|
||||
import { RoomViewStore } from "./stores/RoomViewStore";
|
||||
import UserActivity from "./UserActivity";
|
||||
import { mediaFromMxc } from "./customisations/Media";
|
||||
import ErrorDialog from "./components/views/dialogs/ErrorDialog";
|
||||
import LegacyCallHandler from "./LegacyCallHandler";
|
||||
import VoipUserMapper from "./VoipUserMapper";
|
||||
import { SdkContextClass } from "./contexts/SDKContext";
|
||||
import { localNotificationsAreSilenced } from "./utils/notifications";
|
||||
import { getIncomingCallToastKey, IncomingCallToast } from "./toasts/IncomingCallToast";
|
||||
import ToastStore from "./stores/ToastStore";
|
||||
|
@ -435,7 +435,7 @@ export const Notifier = {
|
|||
if (actions?.notify) {
|
||||
this._performCustomEventHandling(ev);
|
||||
|
||||
if (RoomViewStore.instance.getRoomId() === room.roomId &&
|
||||
if (SdkContextClass.instance.roomViewStore.getRoomId() === room.roomId &&
|
||||
UserActivity.sharedInstance().userActiveRecently() &&
|
||||
!Modal.hasDialogs()
|
||||
) {
|
||||
|
|
|
@ -272,12 +272,12 @@ import { logger } from "matrix-js-sdk/src/logger";
|
|||
import { MatrixClientPeg } from './MatrixClientPeg';
|
||||
import dis from './dispatcher/dispatcher';
|
||||
import WidgetUtils from './utils/WidgetUtils';
|
||||
import { RoomViewStore } from './stores/RoomViewStore';
|
||||
import { _t } from './languageHandler';
|
||||
import { IntegrationManagers } from "./integrations/IntegrationManagers";
|
||||
import { WidgetType } from "./widgets/WidgetType";
|
||||
import { objectClone } from "./utils/objects";
|
||||
import { EffectiveMembership, getEffectiveMembership } from './utils/membership';
|
||||
import { SdkContextClass } from './contexts/SDKContext';
|
||||
|
||||
enum Action {
|
||||
CloseScalar = "close_scalar",
|
||||
|
@ -721,7 +721,7 @@ const onMessage = function(event: MessageEvent<any>): void {
|
|||
}
|
||||
}
|
||||
|
||||
if (roomId !== RoomViewStore.instance.getRoomId()) {
|
||||
if (roomId !== SdkContextClass.instance.roomViewStore.getRoomId()) {
|
||||
sendError(event, _t('Room %(roomId)s not visible', { roomId: roomId }));
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -62,7 +62,6 @@ import InfoDialog from "./components/views/dialogs/InfoDialog";
|
|||
import SlashCommandHelpDialog from "./components/views/dialogs/SlashCommandHelpDialog";
|
||||
import { shouldShowComponent } from "./customisations/helpers/UIComponents";
|
||||
import { TimelineRenderingType } from './contexts/RoomContext';
|
||||
import { RoomViewStore } from "./stores/RoomViewStore";
|
||||
import { XOR } from "./@types/common";
|
||||
import { PosthogAnalytics } from "./PosthogAnalytics";
|
||||
import { ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload";
|
||||
|
@ -70,6 +69,7 @@ import VoipUserMapper from './VoipUserMapper';
|
|||
import { htmlSerializeFromMdIfNeeded } from './editor/serialize';
|
||||
import { leaveRoomBehaviour } from "./utils/leave-behaviour";
|
||||
import { isLocalRoom } from './utils/localRoom/isLocalRoom';
|
||||
import { SdkContextClass } from './contexts/SDKContext';
|
||||
|
||||
// XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816
|
||||
interface HTMLInputEvent extends Event {
|
||||
|
@ -209,7 +209,7 @@ function successSync(value: any) {
|
|||
|
||||
const isCurrentLocalRoom = (): boolean => {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const room = cli.getRoom(RoomViewStore.instance.getRoomId());
|
||||
const room = cli.getRoom(SdkContextClass.instance.roomViewStore.getRoomId());
|
||||
return isLocalRoom(room);
|
||||
};
|
||||
|
||||
|
@ -868,7 +868,7 @@ export const Commands = [
|
|||
description: _td('Define the power level of a user'),
|
||||
isEnabled(): boolean {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const room = cli.getRoom(RoomViewStore.instance.getRoomId());
|
||||
const room = cli.getRoom(SdkContextClass.instance.roomViewStore.getRoomId());
|
||||
return room?.currentState.maySendStateEvent(EventType.RoomPowerLevels, cli.getUserId())
|
||||
&& !isLocalRoom(room);
|
||||
},
|
||||
|
@ -909,7 +909,7 @@ export const Commands = [
|
|||
description: _td('Deops user with given id'),
|
||||
isEnabled(): boolean {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const room = cli.getRoom(RoomViewStore.instance.getRoomId());
|
||||
const room = cli.getRoom(SdkContextClass.instance.roomViewStore.getRoomId());
|
||||
return room?.currentState.maySendStateEvent(EventType.RoomPowerLevels, cli.getUserId())
|
||||
&& !isLocalRoom(room);
|
||||
},
|
||||
|
|
|
@ -25,7 +25,7 @@ import { MatrixClientPeg } from "../MatrixClientPeg";
|
|||
import { arrayFastClone } from "../utils/arrays";
|
||||
import { PlaybackManager } from "./PlaybackManager";
|
||||
import { isVoiceMessage } from "../utils/EventUtils";
|
||||
import { RoomViewStore } from "../stores/RoomViewStore";
|
||||
import { SdkContextClass } from "../contexts/SDKContext";
|
||||
|
||||
/**
|
||||
* Audio playback queue management for a given room. This keeps track of where the user
|
||||
|
@ -51,7 +51,7 @@ export class PlaybackQueue {
|
|||
constructor(private room: Room) {
|
||||
this.loadClocks();
|
||||
|
||||
RoomViewStore.instance.addRoomListener(this.room.roomId, (isActive) => {
|
||||
SdkContextClass.instance.roomViewStore.addRoomListener(this.room.roomId, (isActive) => {
|
||||
if (!isActive) return;
|
||||
|
||||
// Reset the state of the playbacks before they start mounting and enqueuing updates.
|
||||
|
|
|
@ -137,6 +137,7 @@ import { TimelineRenderingType } from "../../contexts/RoomContext";
|
|||
import { UseCaseSelection } from '../views/elements/UseCaseSelection';
|
||||
import { ValidatedServerConfig } from '../../utils/ValidatedServerConfig';
|
||||
import { isLocalRoom } from '../../utils/localRoom/isLocalRoom';
|
||||
import { SdkContextClass, SDKContext } from '../../contexts/SDKContext';
|
||||
import { viewUserDeviceSettings } from '../../actions/handlers/viewUserDeviceSettings';
|
||||
|
||||
// legacy export
|
||||
|
@ -238,9 +239,12 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
private readonly dispatcherRef: string;
|
||||
private readonly themeWatcher: ThemeWatcher;
|
||||
private readonly fontWatcher: FontWatcher;
|
||||
private readonly stores: SdkContextClass;
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
this.stores = SdkContextClass.instance;
|
||||
this.stores.constructEagerStores();
|
||||
|
||||
this.state = {
|
||||
view: Views.LOADING,
|
||||
|
@ -762,6 +766,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
Modal.createDialog(DialPadModal, {}, "mx_Dialog_dialPadWrapper");
|
||||
break;
|
||||
case Action.OnLoggedIn:
|
||||
this.stores.client = MatrixClientPeg.get();
|
||||
if (
|
||||
// Skip this handling for token login as that always calls onLoggedIn itself
|
||||
!this.tokenLogin &&
|
||||
|
@ -2087,7 +2092,9 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
}
|
||||
|
||||
return <ErrorBoundary>
|
||||
{ view }
|
||||
<SDKContext.Provider value={this.stores}>
|
||||
{ view }
|
||||
</SDKContext.Provider>
|
||||
</ErrorBoundary>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,21 +44,18 @@ import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks';
|
|||
import ResizeNotifier from '../../utils/ResizeNotifier';
|
||||
import ContentMessages from '../../ContentMessages';
|
||||
import Modal from '../../Modal';
|
||||
import LegacyCallHandler, { LegacyCallHandlerEvent } from '../../LegacyCallHandler';
|
||||
import { LegacyCallHandlerEvent } from '../../LegacyCallHandler';
|
||||
import dis, { defaultDispatcher } from '../../dispatcher/dispatcher';
|
||||
import * as Rooms from '../../Rooms';
|
||||
import eventSearch, { searchPagination } from '../../Searching';
|
||||
import MainSplit from './MainSplit';
|
||||
import RightPanel from './RightPanel';
|
||||
import { RoomViewStore } from '../../stores/RoomViewStore';
|
||||
import RoomScrollStateStore, { ScrollState } from '../../stores/RoomScrollStateStore';
|
||||
import WidgetEchoStore from '../../stores/WidgetEchoStore';
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
import { Layout } from "../../settings/enums/Layout";
|
||||
import AccessibleButton from "../views/elements/AccessibleButton";
|
||||
import RightPanelStore from "../../stores/right-panel/RightPanelStore";
|
||||
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
|
||||
import MatrixClientContext, { MatrixClientProps, withMatrixClientHOC } from "../../contexts/MatrixClientContext";
|
||||
import { E2EStatus, shieldStatusForRoom } from '../../utils/ShieldUtils';
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
import { IMatrixClientCreds } from "../../MatrixClientPeg";
|
||||
|
@ -76,12 +73,10 @@ import { IOOBData, IThreepidInvite } from "../../stores/ThreepidInviteStore";
|
|||
import EffectsOverlay from "../views/elements/EffectsOverlay";
|
||||
import { containsEmoji } from '../../effects/utils';
|
||||
import { CHAT_EFFECTS } from '../../effects';
|
||||
import WidgetStore from "../../stores/WidgetStore";
|
||||
import { CallView } from "../views/voip/CallView";
|
||||
import { UPDATE_EVENT } from "../../stores/AsyncStore";
|
||||
import Notifier from "../../Notifier";
|
||||
import { showToast as showNotificationsToast } from "../../toasts/DesktopNotificationsToast";
|
||||
import { RoomNotificationStateStore } from "../../stores/notifications/RoomNotificationStateStore";
|
||||
import { Container, WidgetLayoutStore } from "../../stores/widgets/WidgetLayoutStore";
|
||||
import { getKeyBindingsManager } from '../../KeyBindingsManager';
|
||||
import { objectHasDiff } from "../../utils/objects";
|
||||
|
@ -118,8 +113,8 @@ import { isLocalRoom } from '../../utils/localRoom/isLocalRoom';
|
|||
import { ShowThreadPayload } from "../../dispatcher/payloads/ShowThreadPayload";
|
||||
import { RoomStatusBarUnsentMessages } from './RoomStatusBarUnsentMessages';
|
||||
import { LargeLoader } from './LargeLoader';
|
||||
import { VoiceBroadcastInfoEventType } from '../../voice-broadcast';
|
||||
import { isVideoRoom } from '../../utils/video-rooms';
|
||||
import { SDKContext } from '../../contexts/SDKContext';
|
||||
import { CallStore, CallStoreEvent } from "../../stores/CallStore";
|
||||
import { Call } from "../../models/Call";
|
||||
|
||||
|
@ -133,7 +128,7 @@ if (DEBUG) {
|
|||
debuglog = logger.log.bind(console);
|
||||
}
|
||||
|
||||
interface IRoomProps extends MatrixClientProps {
|
||||
interface IRoomProps {
|
||||
threepidInvite: IThreepidInvite;
|
||||
oobData?: IOOBData;
|
||||
|
||||
|
@ -203,7 +198,6 @@ export interface IRoomState {
|
|||
upgradeRecommendation?: IRecommendedVersion;
|
||||
canReact: boolean;
|
||||
canSendMessages: boolean;
|
||||
canSendVoiceBroadcasts: boolean;
|
||||
tombstone?: MatrixEvent;
|
||||
resizing: boolean;
|
||||
layout: Layout;
|
||||
|
@ -381,13 +375,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
private messagePanel: TimelinePanel;
|
||||
private roomViewBody = createRef<HTMLDivElement>();
|
||||
|
||||
static contextType = MatrixClientContext;
|
||||
public context!: React.ContextType<typeof MatrixClientContext>;
|
||||
static contextType = SDKContext;
|
||||
public context!: React.ContextType<typeof SDKContext>;
|
||||
|
||||
constructor(props: IRoomProps, context: React.ContextType<typeof MatrixClientContext>) {
|
||||
constructor(props: IRoomProps, context: React.ContextType<typeof SDKContext>) {
|
||||
super(props, context);
|
||||
|
||||
const llMembers = context.hasLazyLoadMembersEnabled();
|
||||
const llMembers = context.client.hasLazyLoadMembersEnabled();
|
||||
this.state = {
|
||||
roomId: null,
|
||||
roomLoading: true,
|
||||
|
@ -408,7 +402,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
statusBarVisible: false,
|
||||
canReact: false,
|
||||
canSendMessages: false,
|
||||
canSendVoiceBroadcasts: false,
|
||||
resizing: false,
|
||||
layout: SettingsStore.getValue("layout"),
|
||||
lowBandwidth: SettingsStore.getValue("lowBandwidth"),
|
||||
|
@ -422,7 +415,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
showJoinLeaves: true,
|
||||
showAvatarChanges: true,
|
||||
showDisplaynameChanges: true,
|
||||
matrixClientIsReady: context?.isInitialSyncComplete(),
|
||||
matrixClientIsReady: context.client?.isInitialSyncComplete(),
|
||||
mainSplitContentType: MainSplitContentType.Timeline,
|
||||
timelineRenderingType: TimelineRenderingType.Room,
|
||||
liveTimeline: undefined,
|
||||
|
@ -430,25 +423,25 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
};
|
||||
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
context.on(ClientEvent.Room, this.onRoom);
|
||||
context.on(RoomEvent.Timeline, this.onRoomTimeline);
|
||||
context.on(RoomEvent.TimelineReset, this.onRoomTimelineReset);
|
||||
context.on(RoomEvent.Name, this.onRoomName);
|
||||
context.on(RoomStateEvent.Events, this.onRoomStateEvents);
|
||||
context.on(RoomStateEvent.Update, this.onRoomStateUpdate);
|
||||
context.on(RoomEvent.MyMembership, this.onMyMembership);
|
||||
context.on(CryptoEvent.KeyBackupStatus, this.onKeyBackupStatus);
|
||||
context.on(CryptoEvent.DeviceVerificationChanged, this.onDeviceVerificationChanged);
|
||||
context.on(CryptoEvent.UserTrustStatusChanged, this.onUserVerificationChanged);
|
||||
context.on(CryptoEvent.KeysChanged, this.onCrossSigningKeysChanged);
|
||||
context.on(MatrixEventEvent.Decrypted, this.onEventDecrypted);
|
||||
context.client.on(ClientEvent.Room, this.onRoom);
|
||||
context.client.on(RoomEvent.Timeline, this.onRoomTimeline);
|
||||
context.client.on(RoomEvent.TimelineReset, this.onRoomTimelineReset);
|
||||
context.client.on(RoomEvent.Name, this.onRoomName);
|
||||
context.client.on(RoomStateEvent.Events, this.onRoomStateEvents);
|
||||
context.client.on(RoomStateEvent.Update, this.onRoomStateUpdate);
|
||||
context.client.on(RoomEvent.MyMembership, this.onMyMembership);
|
||||
context.client.on(CryptoEvent.KeyBackupStatus, this.onKeyBackupStatus);
|
||||
context.client.on(CryptoEvent.DeviceVerificationChanged, this.onDeviceVerificationChanged);
|
||||
context.client.on(CryptoEvent.UserTrustStatusChanged, this.onUserVerificationChanged);
|
||||
context.client.on(CryptoEvent.KeysChanged, this.onCrossSigningKeysChanged);
|
||||
context.client.on(MatrixEventEvent.Decrypted, this.onEventDecrypted);
|
||||
// Start listening for RoomViewStore updates
|
||||
RoomViewStore.instance.on(UPDATE_EVENT, this.onRoomViewStoreUpdate);
|
||||
context.roomViewStore.on(UPDATE_EVENT, this.onRoomViewStoreUpdate);
|
||||
|
||||
RightPanelStore.instance.on(UPDATE_EVENT, this.onRightPanelStoreUpdate);
|
||||
context.rightPanelStore.on(UPDATE_EVENT, this.onRightPanelStoreUpdate);
|
||||
|
||||
WidgetEchoStore.on(UPDATE_EVENT, this.onWidgetEchoStoreUpdate);
|
||||
WidgetStore.instance.on(UPDATE_EVENT, this.onWidgetStoreUpdate);
|
||||
context.widgetStore.on(UPDATE_EVENT, this.onWidgetStoreUpdate);
|
||||
|
||||
CallStore.instance.on(CallStoreEvent.ActiveCalls, this.onActiveCalls);
|
||||
|
||||
|
@ -501,16 +494,16 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
action: "appsDrawer",
|
||||
show: true,
|
||||
});
|
||||
if (WidgetLayoutStore.instance.hasMaximisedWidget(this.state.room)) {
|
||||
if (this.context.widgetLayoutStore.hasMaximisedWidget(this.state.room)) {
|
||||
// Show chat in right panel when a widget is maximised
|
||||
RightPanelStore.instance.setCard({ phase: RightPanelPhases.Timeline });
|
||||
this.context.rightPanelStore.setCard({ phase: RightPanelPhases.Timeline });
|
||||
}
|
||||
this.checkWidgets(this.state.room);
|
||||
};
|
||||
|
||||
private checkWidgets = (room: Room): void => {
|
||||
this.setState({
|
||||
hasPinnedWidgets: WidgetLayoutStore.instance.hasPinnedWidgets(room),
|
||||
hasPinnedWidgets: this.context.widgetLayoutStore.hasPinnedWidgets(room),
|
||||
mainSplitContentType: this.getMainSplitContentType(room),
|
||||
showApps: this.shouldShowApps(room),
|
||||
});
|
||||
|
@ -518,12 +511,12 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
|
||||
private getMainSplitContentType = (room: Room) => {
|
||||
if (
|
||||
(SettingsStore.getValue("feature_group_calls") && RoomViewStore.instance.isViewingCall())
|
||||
(SettingsStore.getValue("feature_group_calls") && this.context.roomViewStore.isViewingCall())
|
||||
|| isVideoRoom(room)
|
||||
) {
|
||||
return MainSplitContentType.Call;
|
||||
}
|
||||
if (WidgetLayoutStore.instance.hasMaximisedWidget(room)) {
|
||||
if (this.context.widgetLayoutStore.hasMaximisedWidget(room)) {
|
||||
return MainSplitContentType.MaximisedWidget;
|
||||
}
|
||||
return MainSplitContentType.Timeline;
|
||||
|
@ -534,7 +527,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!initial && this.state.roomId !== RoomViewStore.instance.getRoomId()) {
|
||||
if (!initial && this.state.roomId !== this.context.roomViewStore.getRoomId()) {
|
||||
// RoomView explicitly does not support changing what room
|
||||
// is being viewed: instead it should just be re-mounted when
|
||||
// switching rooms. Therefore, if the room ID changes, we
|
||||
|
@ -549,45 +542,45 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
return;
|
||||
}
|
||||
|
||||
const roomId = RoomViewStore.instance.getRoomId();
|
||||
const room = this.context.getRoom(roomId);
|
||||
const roomId = this.context.roomViewStore.getRoomId();
|
||||
const room = this.context.client.getRoom(roomId);
|
||||
|
||||
// This convoluted type signature ensures we get IntelliSense *and* correct typing
|
||||
const newState: Partial<IRoomState> & Pick<IRoomState, any> = {
|
||||
roomId,
|
||||
roomAlias: RoomViewStore.instance.getRoomAlias(),
|
||||
roomLoading: RoomViewStore.instance.isRoomLoading(),
|
||||
roomLoadError: RoomViewStore.instance.getRoomLoadError(),
|
||||
joining: RoomViewStore.instance.isJoining(),
|
||||
replyToEvent: RoomViewStore.instance.getQuotingEvent(),
|
||||
roomAlias: this.context.roomViewStore.getRoomAlias(),
|
||||
roomLoading: this.context.roomViewStore.isRoomLoading(),
|
||||
roomLoadError: this.context.roomViewStore.getRoomLoadError(),
|
||||
joining: this.context.roomViewStore.isJoining(),
|
||||
replyToEvent: this.context.roomViewStore.getQuotingEvent(),
|
||||
// we should only peek once we have a ready client
|
||||
shouldPeek: this.state.matrixClientIsReady && RoomViewStore.instance.shouldPeek(),
|
||||
shouldPeek: this.state.matrixClientIsReady && this.context.roomViewStore.shouldPeek(),
|
||||
showReadReceipts: SettingsStore.getValue("showReadReceipts", roomId),
|
||||
showRedactions: SettingsStore.getValue("showRedactions", roomId),
|
||||
showJoinLeaves: SettingsStore.getValue("showJoinLeaves", roomId),
|
||||
showAvatarChanges: SettingsStore.getValue("showAvatarChanges", roomId),
|
||||
showDisplaynameChanges: SettingsStore.getValue("showDisplaynameChanges", roomId),
|
||||
wasContextSwitch: RoomViewStore.instance.getWasContextSwitch(),
|
||||
wasContextSwitch: this.context.roomViewStore.getWasContextSwitch(),
|
||||
mainSplitContentType: room === null ? undefined : this.getMainSplitContentType(room),
|
||||
initialEventId: null, // default to clearing this, will get set later in the method if needed
|
||||
showRightPanel: RightPanelStore.instance.isOpenForRoom(roomId),
|
||||
showRightPanel: this.context.rightPanelStore.isOpenForRoom(roomId),
|
||||
activeCall: CallStore.instance.getActiveCall(roomId),
|
||||
};
|
||||
|
||||
if (
|
||||
this.state.mainSplitContentType !== MainSplitContentType.Timeline
|
||||
&& newState.mainSplitContentType === MainSplitContentType.Timeline
|
||||
&& RightPanelStore.instance.isOpen
|
||||
&& RightPanelStore.instance.currentCard.phase === RightPanelPhases.Timeline
|
||||
&& RightPanelStore.instance.roomPhaseHistory.some(card => (card.phase === RightPanelPhases.Timeline))
|
||||
&& this.context.rightPanelStore.isOpen
|
||||
&& this.context.rightPanelStore.currentCard.phase === RightPanelPhases.Timeline
|
||||
&& this.context.rightPanelStore.roomPhaseHistory.some(card => (card.phase === RightPanelPhases.Timeline))
|
||||
) {
|
||||
// We're returning to the main timeline, so hide the right panel timeline
|
||||
RightPanelStore.instance.setCard({ phase: RightPanelPhases.RoomSummary });
|
||||
RightPanelStore.instance.togglePanel(this.state.roomId ?? null);
|
||||
this.context.rightPanelStore.setCard({ phase: RightPanelPhases.RoomSummary });
|
||||
this.context.rightPanelStore.togglePanel(this.state.roomId ?? null);
|
||||
newState.showRightPanel = false;
|
||||
}
|
||||
|
||||
const initialEventId = RoomViewStore.instance.getInitialEventId();
|
||||
const initialEventId = this.context.roomViewStore.getInitialEventId();
|
||||
if (initialEventId) {
|
||||
let initialEvent = room?.findEventById(initialEventId);
|
||||
// The event does not exist in the current sync data
|
||||
|
@ -600,7 +593,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
// becomes available to fetch a whole thread
|
||||
if (!initialEvent) {
|
||||
initialEvent = await fetchInitialEvent(
|
||||
this.context,
|
||||
this.context.client,
|
||||
roomId,
|
||||
initialEventId,
|
||||
);
|
||||
|
@ -616,21 +609,21 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
action: Action.ShowThread,
|
||||
rootEvent: thread.rootEvent,
|
||||
initialEvent,
|
||||
highlighted: RoomViewStore.instance.isInitialEventHighlighted(),
|
||||
scroll_into_view: RoomViewStore.instance.initialEventScrollIntoView(),
|
||||
highlighted: this.context.roomViewStore.isInitialEventHighlighted(),
|
||||
scroll_into_view: this.context.roomViewStore.initialEventScrollIntoView(),
|
||||
});
|
||||
} else {
|
||||
newState.initialEventId = initialEventId;
|
||||
newState.isInitialEventHighlighted = RoomViewStore.instance.isInitialEventHighlighted();
|
||||
newState.initialEventScrollIntoView = RoomViewStore.instance.initialEventScrollIntoView();
|
||||
newState.isInitialEventHighlighted = this.context.roomViewStore.isInitialEventHighlighted();
|
||||
newState.initialEventScrollIntoView = this.context.roomViewStore.initialEventScrollIntoView();
|
||||
|
||||
if (thread && initialEvent?.isThreadRoot) {
|
||||
dis.dispatch<ShowThreadPayload>({
|
||||
action: Action.ShowThread,
|
||||
rootEvent: thread.rootEvent,
|
||||
initialEvent,
|
||||
highlighted: RoomViewStore.instance.isInitialEventHighlighted(),
|
||||
scroll_into_view: RoomViewStore.instance.initialEventScrollIntoView(),
|
||||
highlighted: this.context.roomViewStore.isInitialEventHighlighted(),
|
||||
scroll_into_view: this.context.roomViewStore.initialEventScrollIntoView(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -657,7 +650,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
|
||||
if (!initial && this.state.shouldPeek && !newState.shouldPeek) {
|
||||
// Stop peeking because we have joined this room now
|
||||
this.context.stopPeeking();
|
||||
this.context.client.stopPeeking();
|
||||
}
|
||||
|
||||
// Temporary logging to diagnose https://github.com/vector-im/element-web/issues/4307
|
||||
|
@ -674,7 +667,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
// NB: This does assume that the roomID will not change for the lifetime of
|
||||
// the RoomView instance
|
||||
if (initial) {
|
||||
newState.room = this.context.getRoom(newState.roomId);
|
||||
newState.room = this.context.client.getRoom(newState.roomId);
|
||||
if (newState.room) {
|
||||
newState.showApps = this.shouldShowApps(newState.room);
|
||||
this.onRoomLoaded(newState.room);
|
||||
|
@ -784,7 +777,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
peekLoading: true,
|
||||
isPeeking: true, // this will change to false if peeking fails
|
||||
});
|
||||
this.context.peekInRoom(roomId).then((room) => {
|
||||
this.context.client.peekInRoom(roomId).then((room) => {
|
||||
if (this.unmounted) {
|
||||
return;
|
||||
}
|
||||
|
@ -817,7 +810,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
});
|
||||
} else if (room) {
|
||||
// Stop peeking because we have joined this room previously
|
||||
this.context.stopPeeking();
|
||||
this.context.client.stopPeeking();
|
||||
this.setState({ isPeeking: false });
|
||||
}
|
||||
}
|
||||
|
@ -835,7 +828,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
// Otherwise (in case the user set hideWidgetDrawer by clicking the button) follow the parameter.
|
||||
const isManuallyShown = hideWidgetDrawer ? hideWidgetDrawer === "false": true;
|
||||
|
||||
const widgets = WidgetLayoutStore.instance.getContainerWidgets(room, Container.Top);
|
||||
const widgets = this.context.widgetLayoutStore.getContainerWidgets(room, Container.Top);
|
||||
return isManuallyShown && widgets.length > 0;
|
||||
}
|
||||
|
||||
|
@ -848,7 +841,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
callState: callState,
|
||||
});
|
||||
|
||||
LegacyCallHandler.instance.on(LegacyCallHandlerEvent.CallState, this.onCallState);
|
||||
this.context.legacyCallHandler.on(LegacyCallHandlerEvent.CallState, this.onCallState);
|
||||
window.addEventListener('beforeunload', this.onPageUnload);
|
||||
}
|
||||
|
||||
|
@ -885,7 +878,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
// (We could use isMounted, but facebook have deprecated that.)
|
||||
this.unmounted = true;
|
||||
|
||||
LegacyCallHandler.instance.removeListener(LegacyCallHandlerEvent.CallState, this.onCallState);
|
||||
this.context.legacyCallHandler.removeListener(LegacyCallHandlerEvent.CallState, this.onCallState);
|
||||
|
||||
// update the scroll map before we get unmounted
|
||||
if (this.state.roomId) {
|
||||
|
@ -893,47 +886,47 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
}
|
||||
|
||||
if (this.state.shouldPeek) {
|
||||
this.context.stopPeeking();
|
||||
this.context.client.stopPeeking();
|
||||
}
|
||||
|
||||
// stop tracking room changes to format permalinks
|
||||
this.stopAllPermalinkCreators();
|
||||
|
||||
dis.unregister(this.dispatcherRef);
|
||||
if (this.context) {
|
||||
this.context.removeListener(ClientEvent.Room, this.onRoom);
|
||||
this.context.removeListener(RoomEvent.Timeline, this.onRoomTimeline);
|
||||
this.context.removeListener(RoomEvent.TimelineReset, this.onRoomTimelineReset);
|
||||
this.context.removeListener(RoomEvent.Name, this.onRoomName);
|
||||
this.context.removeListener(RoomStateEvent.Events, this.onRoomStateEvents);
|
||||
this.context.removeListener(RoomEvent.MyMembership, this.onMyMembership);
|
||||
this.context.removeListener(RoomStateEvent.Update, this.onRoomStateUpdate);
|
||||
this.context.removeListener(CryptoEvent.KeyBackupStatus, this.onKeyBackupStatus);
|
||||
this.context.removeListener(CryptoEvent.DeviceVerificationChanged, this.onDeviceVerificationChanged);
|
||||
this.context.removeListener(CryptoEvent.UserTrustStatusChanged, this.onUserVerificationChanged);
|
||||
this.context.removeListener(CryptoEvent.KeysChanged, this.onCrossSigningKeysChanged);
|
||||
this.context.removeListener(MatrixEventEvent.Decrypted, this.onEventDecrypted);
|
||||
if (this.context.client) {
|
||||
this.context.client.removeListener(ClientEvent.Room, this.onRoom);
|
||||
this.context.client.removeListener(RoomEvent.Timeline, this.onRoomTimeline);
|
||||
this.context.client.removeListener(RoomEvent.TimelineReset, this.onRoomTimelineReset);
|
||||
this.context.client.removeListener(RoomEvent.Name, this.onRoomName);
|
||||
this.context.client.removeListener(RoomStateEvent.Events, this.onRoomStateEvents);
|
||||
this.context.client.removeListener(RoomEvent.MyMembership, this.onMyMembership);
|
||||
this.context.client.removeListener(RoomStateEvent.Update, this.onRoomStateUpdate);
|
||||
this.context.client.removeListener(CryptoEvent.KeyBackupStatus, this.onKeyBackupStatus);
|
||||
this.context.client.removeListener(CryptoEvent.DeviceVerificationChanged, this.onDeviceVerificationChanged);
|
||||
this.context.client.removeListener(CryptoEvent.UserTrustStatusChanged, this.onUserVerificationChanged);
|
||||
this.context.client.removeListener(CryptoEvent.KeysChanged, this.onCrossSigningKeysChanged);
|
||||
this.context.client.removeListener(MatrixEventEvent.Decrypted, this.onEventDecrypted);
|
||||
}
|
||||
|
||||
window.removeEventListener('beforeunload', this.onPageUnload);
|
||||
|
||||
RoomViewStore.instance.off(UPDATE_EVENT, this.onRoomViewStoreUpdate);
|
||||
this.context.roomViewStore.off(UPDATE_EVENT, this.onRoomViewStoreUpdate);
|
||||
|
||||
RightPanelStore.instance.off(UPDATE_EVENT, this.onRightPanelStoreUpdate);
|
||||
this.context.rightPanelStore.off(UPDATE_EVENT, this.onRightPanelStoreUpdate);
|
||||
WidgetEchoStore.removeListener(UPDATE_EVENT, this.onWidgetEchoStoreUpdate);
|
||||
WidgetStore.instance.removeListener(UPDATE_EVENT, this.onWidgetStoreUpdate);
|
||||
this.context.widgetStore.removeListener(UPDATE_EVENT, this.onWidgetStoreUpdate);
|
||||
|
||||
this.props.resizeNotifier.off("isResizing", this.onIsResizing);
|
||||
|
||||
if (this.state.room) {
|
||||
WidgetLayoutStore.instance.off(
|
||||
this.context.widgetLayoutStore.off(
|
||||
WidgetLayoutStore.emissionForRoom(this.state.room),
|
||||
this.onWidgetLayoutChange,
|
||||
);
|
||||
}
|
||||
|
||||
CallStore.instance.off(CallStoreEvent.ActiveCalls, this.onActiveCalls);
|
||||
LegacyCallHandler.instance.off(LegacyCallHandlerEvent.CallState, this.onCallState);
|
||||
this.context.legacyCallHandler.off(LegacyCallHandlerEvent.CallState, this.onCallState);
|
||||
|
||||
// cancel any pending calls to the throttled updated
|
||||
this.updateRoomMembers.cancel();
|
||||
|
@ -944,13 +937,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
|
||||
if (this.viewsLocalRoom) {
|
||||
// clean up if this was a local room
|
||||
this.props.mxClient.store.removeRoom(this.state.room.roomId);
|
||||
this.context.client.store.removeRoom(this.state.room.roomId);
|
||||
}
|
||||
}
|
||||
|
||||
private onRightPanelStoreUpdate = () => {
|
||||
this.setState({
|
||||
showRightPanel: RightPanelStore.instance.isOpenForRoom(this.state.roomId),
|
||||
showRightPanel: this.context.rightPanelStore.isOpenForRoom(this.state.roomId),
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -1017,7 +1010,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
break;
|
||||
case 'picture_snapshot':
|
||||
ContentMessages.sharedInstance().sendContentListToRoom(
|
||||
[payload.file], this.state.room.roomId, null, this.context);
|
||||
[payload.file], this.state.room.roomId, null, this.context.client);
|
||||
break;
|
||||
case 'notifier_enabled':
|
||||
case Action.UploadStarted:
|
||||
|
@ -1043,7 +1036,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
case 'MatrixActions.sync':
|
||||
if (!this.state.matrixClientIsReady) {
|
||||
this.setState({
|
||||
matrixClientIsReady: this.context?.isInitialSyncComplete(),
|
||||
matrixClientIsReady: this.context.client?.isInitialSyncComplete(),
|
||||
}, () => {
|
||||
// send another "initial" RVS update to trigger peeking if needed
|
||||
this.onRoomViewStoreUpdate(true);
|
||||
|
@ -1112,7 +1105,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
|
||||
private onLocalRoomEvent(roomId: string) {
|
||||
if (roomId !== this.state.room.roomId) return;
|
||||
createRoomFromLocalRoom(this.props.mxClient, this.state.room as LocalRoom);
|
||||
createRoomFromLocalRoom(this.context.client, this.state.room as LocalRoom);
|
||||
}
|
||||
|
||||
private onRoomTimeline = (ev: MatrixEvent, room: Room | null, toStartOfTimeline: boolean, removed, data) => {
|
||||
|
@ -1145,7 +1138,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
this.handleEffects(ev);
|
||||
}
|
||||
|
||||
if (ev.getSender() !== this.context.credentials.userId) {
|
||||
if (ev.getSender() !== this.context.client.credentials.userId) {
|
||||
// update unread count when scrolled up
|
||||
if (!this.state.searchResults && this.state.atEndOfLiveTimeline) {
|
||||
// no change
|
||||
|
@ -1165,7 +1158,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
};
|
||||
|
||||
private handleEffects = (ev: MatrixEvent) => {
|
||||
const notifState = RoomNotificationStateStore.instance.getRoomState(this.state.room);
|
||||
const notifState = this.context.roomNotificationStateStore.getRoomState(this.state.room);
|
||||
if (!notifState.isUnread) return;
|
||||
|
||||
CHAT_EFFECTS.forEach(effect => {
|
||||
|
@ -1202,7 +1195,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
private onRoomLoaded = (room: Room) => {
|
||||
if (this.unmounted) return;
|
||||
// Attach a widget store listener only when we get a room
|
||||
WidgetLayoutStore.instance.on(WidgetLayoutStore.emissionForRoom(room), this.onWidgetLayoutChange);
|
||||
this.context.widgetLayoutStore.on(WidgetLayoutStore.emissionForRoom(room), this.onWidgetLayoutChange);
|
||||
|
||||
this.calculatePeekRules(room);
|
||||
this.updatePreviewUrlVisibility(room);
|
||||
|
@ -1214,10 +1207,10 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
|
||||
if (
|
||||
this.getMainSplitContentType(room) !== MainSplitContentType.Timeline
|
||||
&& RoomNotificationStateStore.instance.getRoomState(room).isUnread
|
||||
&& this.context.roomNotificationStateStore.getRoomState(room).isUnread
|
||||
) {
|
||||
// Automatically open the chat panel to make unread messages easier to discover
|
||||
RightPanelStore.instance.setCard({ phase: RightPanelPhases.Timeline }, true, room.roomId);
|
||||
this.context.rightPanelStore.setCard({ phase: RightPanelPhases.Timeline }, true, room.roomId);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
|
@ -1244,7 +1237,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
|
||||
private async loadMembersIfJoined(room: Room) {
|
||||
// lazy load members if enabled
|
||||
if (this.context.hasLazyLoadMembersEnabled()) {
|
||||
if (this.context.client.hasLazyLoadMembersEnabled()) {
|
||||
if (room && room.getMyMembership() === 'join') {
|
||||
try {
|
||||
await room.loadMembersIfNeeded();
|
||||
|
@ -1270,7 +1263,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
|
||||
private updatePreviewUrlVisibility({ roomId }: Room) {
|
||||
// URL Previews in E2EE rooms can be a privacy leak so use a different setting which is per-room explicit
|
||||
const key = this.context.isRoomEncrypted(roomId) ? 'urlPreviewsEnabled_e2ee' : 'urlPreviewsEnabled';
|
||||
const key = this.context.client.isRoomEncrypted(roomId) ? 'urlPreviewsEnabled_e2ee' : 'urlPreviewsEnabled';
|
||||
this.setState({
|
||||
showUrlPreview: SettingsStore.getValue(key, roomId),
|
||||
});
|
||||
|
@ -1283,7 +1276,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
|
||||
// Detach the listener if the room is changing for some reason
|
||||
if (this.state.room) {
|
||||
WidgetLayoutStore.instance.off(
|
||||
this.context.widgetLayoutStore.off(
|
||||
WidgetLayoutStore.emissionForRoom(this.state.room),
|
||||
this.onWidgetLayoutChange,
|
||||
);
|
||||
|
@ -1320,15 +1313,15 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
};
|
||||
|
||||
private async updateE2EStatus(room: Room) {
|
||||
if (!this.context.isRoomEncrypted(room.roomId)) return;
|
||||
if (!this.context.client.isRoomEncrypted(room.roomId)) return;
|
||||
|
||||
// If crypto is not currently enabled, we aren't tracking devices at all,
|
||||
// so we don't know what the answer is. Let's error on the safe side and show
|
||||
// a warning for this case.
|
||||
let e2eStatus = E2EStatus.Warning;
|
||||
if (this.context.isCryptoEnabled()) {
|
||||
if (this.context.client.isCryptoEnabled()) {
|
||||
/* At this point, the user has encryption on and cross-signing on */
|
||||
e2eStatus = await shieldStatusForRoom(this.context, room);
|
||||
e2eStatus = await shieldStatusForRoom(this.context.client, room);
|
||||
}
|
||||
|
||||
if (this.unmounted) return;
|
||||
|
@ -1374,19 +1367,17 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
|
||||
private updatePermissions(room: Room) {
|
||||
if (room) {
|
||||
const me = this.context.getUserId();
|
||||
const me = this.context.client.getUserId();
|
||||
const canReact = (
|
||||
room.getMyMembership() === "join" &&
|
||||
room.currentState.maySendEvent(EventType.Reaction, me)
|
||||
);
|
||||
const canSendMessages = room.maySendMessage();
|
||||
const canSelfRedact = room.currentState.maySendEvent(EventType.RoomRedaction, me);
|
||||
const canSendVoiceBroadcasts = room.currentState.maySendEvent(VoiceBroadcastInfoEventType, me);
|
||||
|
||||
this.setState({
|
||||
canReact,
|
||||
canSendMessages,
|
||||
canSendVoiceBroadcasts,
|
||||
canSelfRedact,
|
||||
});
|
||||
}
|
||||
|
@ -1442,7 +1433,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
|
||||
private onJoinButtonClicked = () => {
|
||||
// If the user is a ROU, allow them to transition to a PWLU
|
||||
if (this.context?.isGuest()) {
|
||||
if (this.context.client?.isGuest()) {
|
||||
// Join this room once the user has registered and logged in
|
||||
// (If we failed to peek, we may not have a valid room object.)
|
||||
dis.dispatch<DoAfterSyncPreparedPayload<ViewRoomPayload>>({
|
||||
|
@ -1499,13 +1490,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
};
|
||||
|
||||
private injectSticker(url: string, info: object, text: string, threadId: string | null) {
|
||||
if (this.context.isGuest()) {
|
||||
if (this.context.client.isGuest()) {
|
||||
dis.dispatch({ action: 'require_registration' });
|
||||
return;
|
||||
}
|
||||
|
||||
ContentMessages.sharedInstance()
|
||||
.sendStickerContentToRoom(url, this.state.room.roomId, threadId, info, text, this.context)
|
||||
.sendStickerContentToRoom(url, this.state.room.roomId, threadId, info, text, this.context.client)
|
||||
.then(undefined, (error) => {
|
||||
if (error.name === "UnknownDeviceError") {
|
||||
// Let the staus bar handle this
|
||||
|
@ -1578,7 +1569,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
return b.length - a.length;
|
||||
});
|
||||
|
||||
if (this.context.supportsExperimentalThreads()) {
|
||||
if (this.context.client.supportsExperimentalThreads()) {
|
||||
// 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) {
|
||||
|
@ -1586,7 +1577,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
const bundledRelationship = event
|
||||
.getServerAggregatedRelation<IThreadBundledRelationship>(THREAD_RELATION_TYPE.name);
|
||||
if (!bundledRelationship || event.getThread()) continue;
|
||||
const room = this.context.getRoom(event.getRoomId());
|
||||
const room = this.context.client.getRoom(event.getRoomId());
|
||||
const thread = room.findThreadForEvent(event);
|
||||
if (thread) {
|
||||
event.setThread(thread);
|
||||
|
@ -1658,7 +1649,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
|
||||
const mxEv = result.context.getEvent();
|
||||
const roomId = mxEv.getRoomId();
|
||||
const room = this.context.getRoom(roomId);
|
||||
const room = this.context.client.getRoom(roomId);
|
||||
if (!room) {
|
||||
// if we do not have the room in js-sdk stores then hide it as we cannot easily show it
|
||||
// As per the spec, an all rooms search can create this condition,
|
||||
|
@ -1715,7 +1706,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
this.setState({
|
||||
rejecting: true,
|
||||
});
|
||||
this.context.leave(this.state.roomId).then(() => {
|
||||
this.context.client.leave(this.state.roomId).then(() => {
|
||||
dis.dispatch({ action: Action.ViewHomePage });
|
||||
this.setState({
|
||||
rejecting: false,
|
||||
|
@ -1742,13 +1733,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
});
|
||||
|
||||
try {
|
||||
const myMember = this.state.room.getMember(this.context.getUserId());
|
||||
const myMember = this.state.room.getMember(this.context.client.getUserId());
|
||||
const inviteEvent = myMember.events.member;
|
||||
const ignoredUsers = this.context.getIgnoredUsers();
|
||||
const ignoredUsers = this.context.client.getIgnoredUsers();
|
||||
ignoredUsers.push(inviteEvent.getSender()); // de-duped internally in the js-sdk
|
||||
await this.context.setIgnoredUsers(ignoredUsers);
|
||||
await this.context.client.setIgnoredUsers(ignoredUsers);
|
||||
|
||||
await this.context.leave(this.state.roomId);
|
||||
await this.context.client.leave(this.state.roomId);
|
||||
dis.dispatch({ action: Action.ViewHomePage });
|
||||
this.setState({
|
||||
rejecting: false,
|
||||
|
@ -1911,7 +1902,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
if (!this.state.room) {
|
||||
return null;
|
||||
}
|
||||
return LegacyCallHandler.instance.getCallForRoom(this.state.room.roomId);
|
||||
return this.context.legacyCallHandler.getCallForRoom(this.state.room.roomId);
|
||||
}
|
||||
|
||||
// this has to be a proper method rather than an unnamed function,
|
||||
|
@ -1924,7 +1915,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
const createEvent = this.state.room.currentState.getStateEvents(EventType.RoomCreate, "");
|
||||
if (!createEvent || !createEvent.getContent()['predecessor']) return null;
|
||||
|
||||
return this.context.getRoom(createEvent.getContent()['predecessor']['room_id']);
|
||||
return this.context.client.getRoom(createEvent.getContent()['predecessor']['room_id']);
|
||||
}
|
||||
|
||||
getHiddenHighlightCount() {
|
||||
|
@ -1953,7 +1944,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
Array.from(dataTransfer.files),
|
||||
this.state.room?.roomId ?? this.state.roomId,
|
||||
null,
|
||||
this.context,
|
||||
this.context.client,
|
||||
TimelineRenderingType.Room,
|
||||
);
|
||||
|
||||
|
@ -1970,7 +1961,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
}
|
||||
|
||||
private renderLocalRoomCreateLoader(): ReactElement {
|
||||
const names = this.state.room.getDefaultRoomName(this.props.mxClient.getUserId());
|
||||
const names = this.state.room.getDefaultRoomName(this.context.client.getUserId());
|
||||
return <RoomContext.Provider value={this.state}>
|
||||
<LocalRoomCreateLoader
|
||||
names={names}
|
||||
|
@ -2081,7 +2072,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
</ErrorBoundary>
|
||||
);
|
||||
} else {
|
||||
const myUserId = this.context.credentials.userId;
|
||||
const myUserId = this.context.client.credentials.userId;
|
||||
const myMember = this.state.room.getMember(myUserId);
|
||||
const inviteEvent = myMember ? myMember.events.member : null;
|
||||
let inviterName = _t("Unknown");
|
||||
|
@ -2162,7 +2153,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
const showRoomUpgradeBar = (
|
||||
roomVersionRecommendation &&
|
||||
roomVersionRecommendation.needsUpgrade &&
|
||||
this.state.room.userMayUpgradeRoom(this.context.credentials.userId)
|
||||
this.state.room.userMayUpgradeRoom(this.context.client.credentials.userId)
|
||||
);
|
||||
|
||||
const hiddenHighlightCount = this.getHiddenHighlightCount();
|
||||
|
@ -2174,7 +2165,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
searchInProgress={this.state.searchInProgress}
|
||||
onCancelClick={this.onCancelSearchClick}
|
||||
onSearch={this.onSearch}
|
||||
isRoomEncrypted={this.context.isRoomEncrypted(this.state.room.roomId)}
|
||||
isRoomEncrypted={this.context.client.isRoomEncrypted(this.state.room.roomId)}
|
||||
/>;
|
||||
} else if (showRoomUpgradeBar) {
|
||||
aux = <RoomUpgradeWarningBar room={this.state.room} />;
|
||||
|
@ -2236,7 +2227,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
const auxPanel = (
|
||||
<AuxPanel
|
||||
room={this.state.room}
|
||||
userId={this.context.credentials.userId}
|
||||
userId={this.context.client.credentials.userId}
|
||||
showApps={this.state.showApps}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
>
|
||||
|
@ -2257,7 +2248,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
resizeNotifier={this.props.resizeNotifier}
|
||||
replyToEvent={this.state.replyToEvent}
|
||||
permalinkCreator={this.permalinkCreator}
|
||||
showVoiceBroadcastButton={this.state.canSendVoiceBroadcasts}
|
||||
/>;
|
||||
}
|
||||
|
||||
|
@ -2397,7 +2387,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
mainSplitBody = <>
|
||||
<AppsDrawer
|
||||
room={this.state.room}
|
||||
userId={this.context.credentials.userId}
|
||||
userId={this.context.client.credentials.userId}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
showApps={true}
|
||||
/>
|
||||
|
@ -2451,7 +2441,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
onAppsClick = null;
|
||||
onForgetClick = null;
|
||||
onSearchClick = null;
|
||||
if (this.state.room.canInvite(this.context.credentials.userId)) {
|
||||
if (this.state.room.canInvite(this.context.client.credentials.userId)) {
|
||||
onInviteClick = this.onInviteClick;
|
||||
}
|
||||
viewingCall = true;
|
||||
|
@ -2493,5 +2483,4 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
}
|
||||
}
|
||||
|
||||
const RoomViewWithMatrixClient = withMatrixClientHOC(RoomView);
|
||||
export default RoomViewWithMatrixClient;
|
||||
export default RoomView;
|
||||
|
|
|
@ -60,13 +60,13 @@ import MatrixClientContext from "../../contexts/MatrixClientContext";
|
|||
import { useTypedEventEmitterState } from "../../hooks/useEventEmitter";
|
||||
import { IOOBData } from "../../stores/ThreepidInviteStore";
|
||||
import { awaitRoomDownSync } from "../../utils/RoomUpgrade";
|
||||
import { RoomViewStore } from "../../stores/RoomViewStore";
|
||||
import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { JoinRoomReadyPayload } from "../../dispatcher/payloads/JoinRoomReadyPayload";
|
||||
import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
|
||||
import { getKeyBindingsManager } from "../../KeyBindingsManager";
|
||||
import { Alignment } from "../views/elements/Tooltip";
|
||||
import { getTopic } from "../../hooks/room/useTopic";
|
||||
import { SdkContextClass } from "../../contexts/SDKContext";
|
||||
|
||||
interface IProps {
|
||||
space: Room;
|
||||
|
@ -378,7 +378,7 @@ export const joinRoom = (cli: MatrixClient, hierarchy: RoomHierarchy, roomId: st
|
|||
metricsTrigger: "SpaceHierarchy",
|
||||
});
|
||||
}, err => {
|
||||
RoomViewStore.instance.showJoinRoomError(err, roomId);
|
||||
SdkContextClass.instance.roomViewStore.showJoinRoomError(err, roomId);
|
||||
});
|
||||
|
||||
return prom;
|
||||
|
|
|
@ -51,10 +51,10 @@ import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
|
|||
import Measured from '../views/elements/Measured';
|
||||
import PosthogTrackers from "../../PosthogTrackers";
|
||||
import { ButtonEvent } from "../views/elements/AccessibleButton";
|
||||
import { RoomViewStore } from '../../stores/RoomViewStore';
|
||||
import Spinner from "../views/elements/Spinner";
|
||||
import { ComposerInsertPayload, ComposerType } from "../../dispatcher/payloads/ComposerInsertPayload";
|
||||
import Heading from '../views/typography/Heading';
|
||||
import { SdkContextClass } from '../../contexts/SDKContext';
|
||||
|
||||
interface IProps {
|
||||
room: Room;
|
||||
|
@ -113,7 +113,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
|
|||
room.removeListener(ThreadEvent.New, this.onNewThread);
|
||||
SettingsStore.unwatchSetting(this.layoutWatcherRef);
|
||||
|
||||
const hasRoomChanged = RoomViewStore.instance.getRoomId() !== roomId;
|
||||
const hasRoomChanged = SdkContextClass.instance.roomViewStore.getRoomId() !== roomId;
|
||||
if (this.props.isInitialEventHighlighted && !hasRoomChanged) {
|
||||
dis.dispatch<ViewRoomPayload>({
|
||||
action: Action.ViewRoom,
|
||||
|
|
396
src/components/views/auth/LoginWithQR.tsx
Normal file
396
src/components/views/auth/LoginWithQR.tsx
Normal file
|
@ -0,0 +1,396 @@
|
|||
/*
|
||||
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 { MSC3906Rendezvous, MSC3906RendezvousPayload, RendezvousFailureReason } from 'matrix-js-sdk/src/rendezvous';
|
||||
import { MSC3886SimpleHttpRendezvousTransport } from 'matrix-js-sdk/src/rendezvous/transports';
|
||||
import { MSC3903ECDHPayload, MSC3903ECDHv1RendezvousChannel } from 'matrix-js-sdk/src/rendezvous/channels';
|
||||
import { logger } from 'matrix-js-sdk/src/logger';
|
||||
import { MatrixClient } from 'matrix-js-sdk/src/client';
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import QRCode from '../elements/QRCode';
|
||||
import Spinner from '../elements/Spinner';
|
||||
import { Icon as BackButtonIcon } from "../../../../res/img/element-icons/back.svg";
|
||||
import { Icon as DevicesIcon } from "../../../../res/img/element-icons/devices.svg";
|
||||
import { Icon as WarningBadge } from "../../../../res/img/element-icons/warning-badge.svg";
|
||||
import { Icon as InfoIcon } from "../../../../res/img/element-icons/i.svg";
|
||||
import { wrapRequestWithDialog } from '../../../utils/UserInteractiveAuth';
|
||||
|
||||
/**
|
||||
* The intention of this enum is to have a mode that scans a QR code instead of generating one.
|
||||
*/
|
||||
export enum Mode {
|
||||
/**
|
||||
* A QR code with be generated and shown
|
||||
*/
|
||||
Show = "show",
|
||||
}
|
||||
|
||||
enum Phase {
|
||||
Loading,
|
||||
ShowingQR,
|
||||
Connecting,
|
||||
Connected,
|
||||
WaitingForDevice,
|
||||
Verifying,
|
||||
Error,
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
client: MatrixClient;
|
||||
mode: Mode;
|
||||
onFinished(...args: any): void;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
phase: Phase;
|
||||
rendezvous?: MSC3906Rendezvous;
|
||||
confirmationDigits?: string;
|
||||
failureReason?: RendezvousFailureReason;
|
||||
mediaPermissionError?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A component that allows sign in and E2EE set up with a QR code.
|
||||
*
|
||||
* It implements both `login.start` and `login-reciprocate` capabilities as well as both scanning and showing QR codes.
|
||||
*
|
||||
* This uses the unstable feature of MSC3906: https://github.com/matrix-org/matrix-spec-proposals/pull/3906
|
||||
*/
|
||||
export default class LoginWithQR extends React.Component<IProps, IState> {
|
||||
public constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
phase: Phase.Loading,
|
||||
};
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
this.updateMode(this.props.mode).then(() => {});
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps: Readonly<IProps>): void {
|
||||
if (prevProps.mode !== this.props.mode) {
|
||||
this.updateMode(this.props.mode).then(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
private async updateMode(mode: Mode) {
|
||||
this.setState({ phase: Phase.Loading });
|
||||
if (this.state.rendezvous) {
|
||||
this.state.rendezvous.onFailure = undefined;
|
||||
await this.state.rendezvous.cancel(RendezvousFailureReason.UserCancelled);
|
||||
this.setState({ rendezvous: undefined });
|
||||
}
|
||||
if (mode === Mode.Show) {
|
||||
await this.generateCode();
|
||||
}
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
if (this.state.rendezvous) {
|
||||
// eslint-disable-next-line react/no-direct-mutation-state
|
||||
this.state.rendezvous.onFailure = undefined;
|
||||
// calling cancel will call close() as well to clean up the resources
|
||||
this.state.rendezvous.cancel(RendezvousFailureReason.UserCancelled).then(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
private approveLogin = async (): Promise<void> => {
|
||||
if (!this.state.rendezvous) {
|
||||
throw new Error('Rendezvous not found');
|
||||
}
|
||||
this.setState({ phase: Phase.Loading });
|
||||
|
||||
try {
|
||||
logger.info("Requesting login token");
|
||||
|
||||
const { login_token: loginToken } = await wrapRequestWithDialog(this.props.client.requestLoginToken, {
|
||||
matrixClient: this.props.client,
|
||||
title: _t("Sign in new device"),
|
||||
})();
|
||||
|
||||
this.setState({ phase: Phase.WaitingForDevice });
|
||||
|
||||
const newDeviceId = await this.state.rendezvous.approveLoginOnExistingDevice(loginToken);
|
||||
if (!newDeviceId) {
|
||||
// user denied
|
||||
return;
|
||||
}
|
||||
if (!this.props.client.crypto) {
|
||||
// no E2EE to set up
|
||||
this.props.onFinished(true);
|
||||
return;
|
||||
}
|
||||
await this.state.rendezvous.verifyNewDeviceOnExistingDevice();
|
||||
this.props.onFinished(true);
|
||||
} catch (e) {
|
||||
logger.error('Error whilst approving sign in', e);
|
||||
this.setState({ phase: Phase.Error, failureReason: RendezvousFailureReason.Unknown });
|
||||
}
|
||||
};
|
||||
|
||||
private generateCode = async () => {
|
||||
let rendezvous: MSC3906Rendezvous;
|
||||
try {
|
||||
const transport = new MSC3886SimpleHttpRendezvousTransport<MSC3903ECDHPayload>({
|
||||
onFailure: this.onFailure,
|
||||
client: this.props.client,
|
||||
});
|
||||
|
||||
const channel = new MSC3903ECDHv1RendezvousChannel<MSC3906RendezvousPayload>(
|
||||
transport, undefined, this.onFailure,
|
||||
);
|
||||
|
||||
rendezvous = new MSC3906Rendezvous(channel, this.props.client, this.onFailure);
|
||||
|
||||
await rendezvous.generateCode();
|
||||
this.setState({
|
||||
phase: Phase.ShowingQR,
|
||||
rendezvous,
|
||||
failureReason: undefined,
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error('Error whilst generating QR code', e);
|
||||
this.setState({ phase: Phase.Error, failureReason: RendezvousFailureReason.HomeserverLacksSupport });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const confirmationDigits = await rendezvous.startAfterShowingCode();
|
||||
this.setState({ phase: Phase.Connected, confirmationDigits });
|
||||
} catch (e) {
|
||||
logger.error('Error whilst doing QR login', e);
|
||||
// only set to error phase if it hasn't already been set by onFailure or similar
|
||||
if (this.state.phase !== Phase.Error) {
|
||||
this.setState({ phase: Phase.Error, failureReason: RendezvousFailureReason.Unknown });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private onFailure = (reason: RendezvousFailureReason) => {
|
||||
logger.info(`Rendezvous failed: ${reason}`);
|
||||
this.setState({ phase: Phase.Error, failureReason: reason });
|
||||
};
|
||||
|
||||
public reset() {
|
||||
this.setState({
|
||||
rendezvous: undefined,
|
||||
confirmationDigits: undefined,
|
||||
failureReason: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
private cancelClicked = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
await this.state.rendezvous?.cancel(RendezvousFailureReason.UserCancelled);
|
||||
this.reset();
|
||||
this.props.onFinished(false);
|
||||
};
|
||||
|
||||
private declineClicked = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
await this.state.rendezvous?.declineLoginOnExistingDevice();
|
||||
this.reset();
|
||||
this.props.onFinished(false);
|
||||
};
|
||||
|
||||
private tryAgainClicked = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
this.reset();
|
||||
await this.updateMode(this.props.mode);
|
||||
};
|
||||
|
||||
private onBackClick = async () => {
|
||||
await this.state.rendezvous?.cancel(RendezvousFailureReason.UserCancelled);
|
||||
|
||||
this.props.onFinished(false);
|
||||
};
|
||||
|
||||
private cancelButton = () => <AccessibleButton
|
||||
kind="primary_outline"
|
||||
onClick={this.cancelClicked}
|
||||
>
|
||||
{ _t("Cancel") }
|
||||
</AccessibleButton>;
|
||||
|
||||
private simpleSpinner = (description?: string): JSX.Element => {
|
||||
return <div className="mx_LoginWithQR_spinner">
|
||||
<div>
|
||||
<Spinner />
|
||||
{ description && <p>{ description }</p> }
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
|
||||
public render() {
|
||||
let title: string;
|
||||
let titleIcon: JSX.Element | undefined;
|
||||
let main: JSX.Element | undefined;
|
||||
let buttons: JSX.Element | undefined;
|
||||
let backButton = true;
|
||||
let cancellationMessage: string | undefined;
|
||||
let centreTitle = false;
|
||||
|
||||
switch (this.state.phase) {
|
||||
case Phase.Error:
|
||||
switch (this.state.failureReason) {
|
||||
case RendezvousFailureReason.Expired:
|
||||
cancellationMessage = _t("The linking wasn't completed in the required time.");
|
||||
break;
|
||||
case RendezvousFailureReason.InvalidCode:
|
||||
cancellationMessage = _t("The scanned code is invalid.");
|
||||
break;
|
||||
case RendezvousFailureReason.UnsupportedAlgorithm:
|
||||
cancellationMessage = _t("Linking with this device is not supported.");
|
||||
break;
|
||||
case RendezvousFailureReason.UserDeclined:
|
||||
cancellationMessage = _t("The request was declined on the other device.");
|
||||
break;
|
||||
case RendezvousFailureReason.OtherDeviceAlreadySignedIn:
|
||||
cancellationMessage = _t("The other device is already signed in.");
|
||||
break;
|
||||
case RendezvousFailureReason.OtherDeviceNotSignedIn:
|
||||
cancellationMessage = _t("The other device isn't signed in.");
|
||||
break;
|
||||
case RendezvousFailureReason.UserCancelled:
|
||||
cancellationMessage = _t("The request was cancelled.");
|
||||
break;
|
||||
case RendezvousFailureReason.Unknown:
|
||||
cancellationMessage = _t("An unexpected error occurred.");
|
||||
break;
|
||||
case RendezvousFailureReason.HomeserverLacksSupport:
|
||||
cancellationMessage = _t("The homeserver doesn't support signing in another device.");
|
||||
break;
|
||||
default:
|
||||
cancellationMessage = _t("The request was cancelled.");
|
||||
break;
|
||||
}
|
||||
title = _t("Connection failed");
|
||||
centreTitle = true;
|
||||
titleIcon = <WarningBadge className="error" />;
|
||||
backButton = false;
|
||||
main = <p data-testid="cancellation-message">{ cancellationMessage }</p>;
|
||||
buttons = <>
|
||||
<AccessibleButton
|
||||
kind="primary"
|
||||
onClick={this.tryAgainClicked}
|
||||
>
|
||||
{ _t("Try again") }
|
||||
</AccessibleButton>
|
||||
{ this.cancelButton() }
|
||||
</>;
|
||||
break;
|
||||
case Phase.Connected:
|
||||
title = _t("Devices connected");
|
||||
titleIcon = <DevicesIcon className="normal" />;
|
||||
backButton = false;
|
||||
main = <>
|
||||
<p>{ _t("Check that the code below matches with your other device:") }</p>
|
||||
<div className="mx_LoginWithQR_confirmationDigits">
|
||||
{ this.state.confirmationDigits }
|
||||
</div>
|
||||
<div className="mx_LoginWithQR_confirmationAlert">
|
||||
<div>
|
||||
<InfoIcon />
|
||||
</div>
|
||||
<div>{ _t("By approving access for this device, it will have full access to your account.") }</div>
|
||||
</div>
|
||||
</>;
|
||||
|
||||
buttons = <>
|
||||
<AccessibleButton
|
||||
data-testid="decline-login-button"
|
||||
kind="primary_outline"
|
||||
onClick={this.declineClicked}
|
||||
>
|
||||
{ _t("Cancel") }
|
||||
</AccessibleButton>
|
||||
<AccessibleButton
|
||||
data-testid="approve-login-button"
|
||||
kind="primary"
|
||||
onClick={this.approveLogin}
|
||||
>
|
||||
{ _t("Approve") }
|
||||
</AccessibleButton>
|
||||
</>;
|
||||
break;
|
||||
case Phase.ShowingQR:
|
||||
title =_t("Sign in with QR code");
|
||||
if (this.state.rendezvous) {
|
||||
const code = <div className="mx_LoginWithQR_qrWrapper">
|
||||
<QRCode data={[{ data: Buffer.from(this.state.rendezvous.code), mode: 'byte' }]} className="mx_QRCode" />
|
||||
</div>;
|
||||
main = <>
|
||||
<p>{ _t("Scan the QR code below with your device that's signed out.") }</p>
|
||||
<ol>
|
||||
<li>{ _t("Start at the sign in screen") }</li>
|
||||
<li>{ _t("Select 'Scan QR code'") }</li>
|
||||
<li>{ _t("Review and approve the sign in") }</li>
|
||||
</ol>
|
||||
{ code }
|
||||
</>;
|
||||
} else {
|
||||
main = this.simpleSpinner();
|
||||
buttons = this.cancelButton();
|
||||
}
|
||||
break;
|
||||
case Phase.Loading:
|
||||
main = this.simpleSpinner();
|
||||
break;
|
||||
case Phase.Connecting:
|
||||
main = this.simpleSpinner(_t("Connecting..."));
|
||||
buttons = this.cancelButton();
|
||||
break;
|
||||
case Phase.WaitingForDevice:
|
||||
main = this.simpleSpinner(_t("Waiting for device to sign in"));
|
||||
buttons = this.cancelButton();
|
||||
break;
|
||||
case Phase.Verifying:
|
||||
title = _t("Success");
|
||||
centreTitle = true;
|
||||
main = this.simpleSpinner(_t("Completing set up of your new device"));
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_LoginWithQR">
|
||||
<div className={centreTitle ? "mx_LoginWithQR_centreTitle" : ""}>
|
||||
{ backButton ?
|
||||
<AccessibleButton
|
||||
data-testid="back-button"
|
||||
className="mx_LoginWithQR_BackButton"
|
||||
onClick={this.onBackClick}
|
||||
title="Back"
|
||||
>
|
||||
<BackButtonIcon />
|
||||
</AccessibleButton>
|
||||
: null }
|
||||
<h1>{ titleIcon }{ title }</h1>
|
||||
</div>
|
||||
<div className="mx_LoginWithQR_main">
|
||||
{ main }
|
||||
</div>
|
||||
<div className="mx_LoginWithQR_buttons">
|
||||
{ buttons }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -24,13 +24,13 @@ import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
|||
import { Action } from "../../../dispatcher/actions";
|
||||
import { Call, ConnectionState, ElementCall } from "../../../models/Call";
|
||||
import { useCall } from "../../../hooks/useCall";
|
||||
import { RoomViewStore } from "../../../stores/RoomViewStore";
|
||||
import { useEventEmitterState } from "../../../hooks/useEventEmitter";
|
||||
import {
|
||||
OwnBeaconStore,
|
||||
OwnBeaconStoreEvent,
|
||||
} from "../../../stores/OwnBeaconStore";
|
||||
import { CallDurationFromEvent } from "../voip/CallDuration";
|
||||
import { SdkContextClass } from "../../../contexts/SDKContext";
|
||||
|
||||
interface RoomCallBannerProps {
|
||||
roomId: Room["roomId"];
|
||||
|
@ -114,7 +114,7 @@ const RoomCallBanner: React.FC<Props> = ({ roomId }) => {
|
|||
}
|
||||
|
||||
// Check if the call is already showing. No banner is needed in this case.
|
||||
if (RoomViewStore.instance.isViewingCall()) {
|
||||
if (SdkContextClass.instance.roomViewStore.isViewingCall()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -37,7 +37,6 @@ import Modal from "../../../Modal";
|
|||
import ExportDialog from "../dialogs/ExportDialog";
|
||||
import { useFeatureEnabled } from "../../../hooks/useSettings";
|
||||
import { usePinnedEvents } from "../right_panel/PinnedMessagesCard";
|
||||
import { RoomViewStore } from "../../../stores/RoomViewStore";
|
||||
import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases';
|
||||
import { ROOM_NOTIFICATIONS_TAB } from "../dialogs/RoomSettingsDialog";
|
||||
import { useEventEmitterState } from "../../../hooks/useEventEmitter";
|
||||
|
@ -50,6 +49,7 @@ import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
|||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import DevtoolsDialog from "../dialogs/DevtoolsDialog";
|
||||
import { SdkContextClass } from "../../../contexts/SDKContext";
|
||||
|
||||
interface IProps extends IContextMenuProps {
|
||||
room: Room;
|
||||
|
@ -332,7 +332,7 @@ const RoomContextMenu = ({ room, onFinished, ...props }: IProps) => {
|
|||
};
|
||||
|
||||
const ensureViewingRoom = (ev: ButtonEvent) => {
|
||||
if (RoomViewStore.instance.getRoomId() === room.roomId) return;
|
||||
if (SdkContextClass.instance.roomViewStore.getRoomId() === room.roomId) return;
|
||||
dis.dispatch<ViewRoomPayload>({
|
||||
action: Action.ViewRoom,
|
||||
room_id: room.roomId,
|
||||
|
@ -377,7 +377,7 @@ const RoomContextMenu = ({ room, onFinished, ...props }: IProps) => {
|
|||
ev.stopPropagation();
|
||||
|
||||
Modal.createDialog(DevtoolsDialog, {
|
||||
roomId: RoomViewStore.instance.getRoomId(),
|
||||
roomId: SdkContextClass.instance.roomViewStore.getRoomId(),
|
||||
}, "mx_DevtoolsDialog_wrapper");
|
||||
onFinished();
|
||||
}}
|
||||
|
|
|
@ -38,7 +38,7 @@ interface IDialogAesthetics {
|
|||
};
|
||||
}
|
||||
|
||||
interface IProps extends IDialogProps {
|
||||
export interface InteractiveAuthDialogProps extends IDialogProps {
|
||||
// matrix client to use for UI auth requests
|
||||
matrixClient: MatrixClient;
|
||||
|
||||
|
@ -82,8 +82,8 @@ interface IState {
|
|||
uiaStagePhase: number | string;
|
||||
}
|
||||
|
||||
export default class InteractiveAuthDialog extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
export default class InteractiveAuthDialog extends React.Component<InteractiveAuthDialogProps, IState> {
|
||||
constructor(props: InteractiveAuthDialogProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
|
|
|
@ -66,7 +66,7 @@ import { BreadcrumbsStore } from "../../../../stores/BreadcrumbsStore";
|
|||
import { RoomNotificationState } from "../../../../stores/notifications/RoomNotificationState";
|
||||
import { RoomNotificationStateStore } from "../../../../stores/notifications/RoomNotificationStateStore";
|
||||
import { RecentAlgorithm } from "../../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm";
|
||||
import { RoomViewStore } from "../../../../stores/RoomViewStore";
|
||||
import { SdkContextClass } from "../../../../contexts/SDKContext";
|
||||
import { getMetaSpaceName } from "../../../../stores/spaces";
|
||||
import SpaceStore from "../../../../stores/spaces/SpaceStore";
|
||||
import { DirectoryMember, Member, startDmOnFirstMessage } from "../../../../utils/direct-messages";
|
||||
|
@ -1060,7 +1060,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
|||
</h4>
|
||||
<div>
|
||||
{ BreadcrumbsStore.instance.rooms
|
||||
.filter(r => r.roomId !== RoomViewStore.instance.getRoomId())
|
||||
.filter(r => r.roomId !== SdkContextClass.instance.roomViewStore.getRoomId())
|
||||
.map(room => (
|
||||
<TooltipOption
|
||||
id={`mx_SpotlightDialog_button_recentlyViewed_${room.roomId}`}
|
||||
|
|
|
@ -43,13 +43,13 @@ import { IApp } from "../../../stores/WidgetStore";
|
|||
import { Container, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
|
||||
import { OwnProfileStore } from '../../../stores/OwnProfileStore';
|
||||
import { UPDATE_EVENT } from '../../../stores/AsyncStore';
|
||||
import { RoomViewStore } from '../../../stores/RoomViewStore';
|
||||
import WidgetUtils from '../../../utils/WidgetUtils';
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import { ActionPayload } from "../../../dispatcher/payloads";
|
||||
import { Action } from '../../../dispatcher/actions';
|
||||
import { ElementWidgetCapabilities } from '../../../stores/widgets/ElementWidgetCapabilities';
|
||||
import { WidgetMessagingStore } from '../../../stores/widgets/WidgetMessagingStore';
|
||||
import { SdkContextClass } from '../../../contexts/SDKContext';
|
||||
|
||||
interface IProps {
|
||||
app: IApp;
|
||||
|
@ -175,7 +175,7 @@ export default class AppTile extends React.Component<IProps, IState> {
|
|||
);
|
||||
if (isActiveWidget) {
|
||||
// We just left the room that the active widget was from.
|
||||
if (this.props.room && RoomViewStore.instance.getRoomId() !== this.props.room.roomId) {
|
||||
if (this.props.room && SdkContextClass.instance.roomViewStore.getRoomId() !== this.props.room.roomId) {
|
||||
// If we are not actively looking at the room then destroy this widget entirely.
|
||||
this.endWidgetActions();
|
||||
} else if (WidgetType.JITSI.matches(this.props.app.type)) {
|
||||
|
|
|
@ -83,7 +83,7 @@ const OptionsButton: React.FC<IOptionsButtonProps> = ({
|
|||
getRelationsForEvent,
|
||||
}) => {
|
||||
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
|
||||
const [onFocus, isActive, ref] = useRovingTabIndex(button);
|
||||
const [onFocus, isActive] = useRovingTabIndex(button);
|
||||
useEffect(() => {
|
||||
onFocusChange(menuDisplayed);
|
||||
}, [onFocusChange, menuDisplayed]);
|
||||
|
@ -123,7 +123,7 @@ const OptionsButton: React.FC<IOptionsButtonProps> = ({
|
|||
onClick={onOptionsClick}
|
||||
onContextMenu={onOptionsClick}
|
||||
isExpanded={menuDisplayed}
|
||||
inputRef={ref}
|
||||
inputRef={button}
|
||||
onFocus={onFocus}
|
||||
tabIndex={isActive ? 0 : -1}
|
||||
>
|
||||
|
@ -141,7 +141,7 @@ interface IReactButtonProps {
|
|||
|
||||
const ReactButton: React.FC<IReactButtonProps> = ({ mxEvent, reactions, onFocusChange }) => {
|
||||
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
|
||||
const [onFocus, isActive, ref] = useRovingTabIndex(button);
|
||||
const [onFocus, isActive] = useRovingTabIndex(button);
|
||||
useEffect(() => {
|
||||
onFocusChange(menuDisplayed);
|
||||
}, [onFocusChange, menuDisplayed]);
|
||||
|
@ -173,7 +173,7 @@ const ReactButton: React.FC<IReactButtonProps> = ({ mxEvent, reactions, onFocusC
|
|||
onClick={onClick}
|
||||
onContextMenu={onClick}
|
||||
isExpanded={menuDisplayed}
|
||||
inputRef={ref}
|
||||
inputRef={button}
|
||||
onFocus={onFocus}
|
||||
tabIndex={isActive ? 0 : -1}
|
||||
>
|
||||
|
|
|
@ -43,8 +43,6 @@ import MjolnirBody from "./MjolnirBody";
|
|||
import MBeaconBody from "./MBeaconBody";
|
||||
import { IEventTileOps } from "../rooms/EventTile";
|
||||
import { VoiceBroadcastBody, VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from '../../../voice-broadcast';
|
||||
import { Features } from '../../../settings/Settings';
|
||||
import { SettingLevel } from '../../../settings/SettingLevel';
|
||||
|
||||
// onMessageAllowed is handled internally
|
||||
interface IProps extends Omit<IBodyProps, "onMessageAllowed" | "mediaEventHelper"> {
|
||||
|
@ -58,18 +56,10 @@ interface IProps extends Omit<IBodyProps, "onMessageAllowed" | "mediaEventHelper
|
|||
isSeeingThroughMessageHiddenForModeration?: boolean;
|
||||
}
|
||||
|
||||
interface State {
|
||||
voiceBroadcastEnabled: boolean;
|
||||
}
|
||||
|
||||
export interface IOperableEventTile {
|
||||
getEventTileOps(): IEventTileOps;
|
||||
}
|
||||
|
||||
interface State {
|
||||
voiceBroadcastEnabled: boolean;
|
||||
}
|
||||
|
||||
const baseBodyTypes = new Map<string, typeof React.Component>([
|
||||
[MsgType.Text, TextualBody],
|
||||
[MsgType.Notice, TextualBody],
|
||||
|
@ -87,7 +77,7 @@ const baseEvTypes = new Map<string, React.ComponentType<Partial<IBodyProps>>>([
|
|||
[M_BEACON_INFO.altName, MBeaconBody],
|
||||
]);
|
||||
|
||||
export default class MessageEvent extends React.Component<IProps, State> implements IMediaBody, IOperableEventTile {
|
||||
export default class MessageEvent extends React.Component<IProps> implements IMediaBody, IOperableEventTile {
|
||||
private body: React.RefObject<React.Component | IOperableEventTile> = createRef();
|
||||
private mediaHelper: MediaEventHelper;
|
||||
private bodyTypes = new Map<string, typeof React.Component>(baseBodyTypes.entries());
|
||||
|
@ -95,7 +85,6 @@ export default class MessageEvent extends React.Component<IProps, State> impleme
|
|||
|
||||
public static contextType = MatrixClientContext;
|
||||
public context!: React.ContextType<typeof MatrixClientContext>;
|
||||
private voiceBroadcastSettingWatcherRef: string;
|
||||
|
||||
public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
|
||||
super(props, context);
|
||||
|
@ -105,29 +94,15 @@ export default class MessageEvent extends React.Component<IProps, State> impleme
|
|||
}
|
||||
|
||||
this.updateComponentMaps();
|
||||
|
||||
this.state = {
|
||||
// only check voice broadcast settings for a voice broadcast event
|
||||
voiceBroadcastEnabled: this.props.mxEvent.getType() === VoiceBroadcastInfoEventType
|
||||
&& SettingsStore.getValue(Features.VoiceBroadcast),
|
||||
};
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
this.props.mxEvent.addListener(MatrixEventEvent.Decrypted, this.onDecrypted);
|
||||
|
||||
if (this.props.mxEvent.getType() === VoiceBroadcastInfoEventType) {
|
||||
this.watchVoiceBroadcastFeatureSetting();
|
||||
}
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
this.props.mxEvent.removeListener(MatrixEventEvent.Decrypted, this.onDecrypted);
|
||||
this.mediaHelper?.destroy();
|
||||
|
||||
if (this.voiceBroadcastSettingWatcherRef) {
|
||||
SettingsStore.unwatchSetting(this.voiceBroadcastSettingWatcherRef);
|
||||
}
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps: Readonly<IProps>) {
|
||||
|
@ -171,16 +146,6 @@ export default class MessageEvent extends React.Component<IProps, State> impleme
|
|||
this.forceUpdate();
|
||||
};
|
||||
|
||||
private watchVoiceBroadcastFeatureSetting(): void {
|
||||
this.voiceBroadcastSettingWatcherRef = SettingsStore.watchSetting(
|
||||
Features.VoiceBroadcast,
|
||||
null,
|
||||
(settingName: string, roomId: string, atLevel: SettingLevel, newValAtLevel, newValue: boolean) => {
|
||||
this.setState({ voiceBroadcastEnabled: newValue });
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const content = this.props.mxEvent.getContent();
|
||||
const type = this.props.mxEvent.getType();
|
||||
|
@ -209,8 +174,7 @@ export default class MessageEvent extends React.Component<IProps, State> impleme
|
|||
}
|
||||
|
||||
if (
|
||||
this.state.voiceBroadcastEnabled
|
||||
&& type === VoiceBroadcastInfoEventType
|
||||
type === VoiceBroadcastInfoEventType
|
||||
&& content?.state === VoiceBroadcastInfoState.Started
|
||||
) {
|
||||
BodyType = VoiceBroadcastBody;
|
||||
|
|
|
@ -33,7 +33,6 @@ import dis from '../../../dispatcher/dispatcher';
|
|||
import { _t } from '../../../languageHandler';
|
||||
import { ActionPayload } from '../../../dispatcher/payloads';
|
||||
import { Action } from '../../../dispatcher/actions';
|
||||
import { RoomViewStore } from '../../../stores/RoomViewStore';
|
||||
import ContentMessages from '../../../ContentMessages';
|
||||
import UploadBar from '../../structures/UploadBar';
|
||||
import SettingsStore from '../../../settings/SettingsStore';
|
||||
|
@ -42,6 +41,7 @@ import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
|||
import Measured from '../elements/Measured';
|
||||
import Heading from '../typography/Heading';
|
||||
import { UPDATE_EVENT } from '../../../stores/AsyncStore';
|
||||
import { SdkContextClass } from '../../../contexts/SDKContext';
|
||||
|
||||
interface IProps {
|
||||
room: Room;
|
||||
|
@ -91,7 +91,7 @@ export default class TimelineCard extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
RoomViewStore.instance.addListener(UPDATE_EVENT, this.onRoomViewStoreUpdate);
|
||||
SdkContextClass.instance.roomViewStore.addListener(UPDATE_EVENT, this.onRoomViewStoreUpdate);
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
this.readReceiptsSettingWatcher = SettingsStore.watchSetting("showReadReceipts", null, (...[,,, value]) =>
|
||||
this.setState({ showReadReceipts: value as boolean }),
|
||||
|
@ -102,7 +102,7 @@ export default class TimelineCard extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
RoomViewStore.instance.removeListener(UPDATE_EVENT, this.onRoomViewStoreUpdate);
|
||||
SdkContextClass.instance.roomViewStore.removeListener(UPDATE_EVENT, this.onRoomViewStoreUpdate);
|
||||
|
||||
if (this.readReceiptsSettingWatcher) {
|
||||
SettingsStore.unwatchSetting(this.readReceiptsSettingWatcher);
|
||||
|
@ -116,12 +116,9 @@ export default class TimelineCard extends React.Component<IProps, IState> {
|
|||
|
||||
private onRoomViewStoreUpdate = async (initial?: boolean): Promise<void> => {
|
||||
const newState: Pick<IState, any> = {
|
||||
// roomLoading: RoomViewStore.instance.isRoomLoading(),
|
||||
// roomLoadError: RoomViewStore.instance.getRoomLoadError(),
|
||||
|
||||
initialEventId: RoomViewStore.instance.getInitialEventId(),
|
||||
isInitialEventHighlighted: RoomViewStore.instance.isInitialEventHighlighted(),
|
||||
replyToEvent: RoomViewStore.instance.getQuotingEvent(),
|
||||
initialEventId: SdkContextClass.instance.roomViewStore.getInitialEventId(),
|
||||
isInitialEventHighlighted: SdkContextClass.instance.roomViewStore.isInitialEventHighlighted(),
|
||||
replyToEvent: SdkContextClass.instance.roomViewStore.getQuotingEvent(),
|
||||
};
|
||||
|
||||
this.setState(newState);
|
||||
|
|
|
@ -36,7 +36,6 @@ import { _t } from '../../../languageHandler';
|
|||
import DMRoomMap from '../../../utils/DMRoomMap';
|
||||
import AccessibleButton, { ButtonEvent } from '../elements/AccessibleButton';
|
||||
import SdkConfig from '../../../SdkConfig';
|
||||
import { RoomViewStore } from "../../../stores/RoomViewStore";
|
||||
import MultiInviter from "../../../utils/MultiInviter";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import E2EIcon from "../rooms/E2EIcon";
|
||||
|
@ -77,6 +76,7 @@ import UserIdentifierCustomisations from '../../../customisations/UserIdentifier
|
|||
import PosthogTrackers from "../../../PosthogTrackers";
|
||||
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { DirectoryMember, startDmOnFirstMessage } from '../../../utils/direct-messages';
|
||||
import { SdkContextClass } from '../../../contexts/SDKContext';
|
||||
|
||||
export interface IDevice {
|
||||
deviceId: string;
|
||||
|
@ -412,7 +412,7 @@ const UserOptionsSection: React.FC<{
|
|||
}
|
||||
|
||||
if (canInvite && (member?.membership ?? 'leave') === 'leave' && shouldShowComponent(UIComponent.InviteUsers)) {
|
||||
const roomId = member && member.roomId ? member.roomId : RoomViewStore.instance.getRoomId();
|
||||
const roomId = member && member.roomId ? member.roomId : SdkContextClass.instance.roomViewStore.getRoomId();
|
||||
const onInviteUserButton = async (ev: ButtonEvent) => {
|
||||
try {
|
||||
// We use a MultiInviter to re-use the invite logic, even though we're only inviting one user.
|
||||
|
|
|
@ -932,6 +932,7 @@ export class UnwrappedEventTile extends React.Component<IProps, IState> {
|
|||
rightClick={true}
|
||||
reactions={this.state.reactions}
|
||||
link={this.state.contextMenu.link}
|
||||
getRelationsForEvent={this.props.getRelationsForEvent}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -86,7 +86,6 @@ interface IProps extends MatrixClientProps {
|
|||
relation?: IEventRelation;
|
||||
e2eStatus?: E2EStatus;
|
||||
compact?: boolean;
|
||||
showVoiceBroadcastButton?: boolean;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
|
@ -395,10 +394,6 @@ class MessageComposer extends React.Component<IProps, IState> {
|
|||
return this.state.showStickersButton && !isLocalRoom(this.props.room);
|
||||
}
|
||||
|
||||
private get showVoiceBroadcastButton(): boolean {
|
||||
return this.props.showVoiceBroadcastButton && this.state.showVoiceBroadcastButton;
|
||||
}
|
||||
|
||||
public render() {
|
||||
const isWysiwygComposerEnabled = SettingsStore.getValue("feature_wysiwyg_composer");
|
||||
const controls = [
|
||||
|
@ -420,6 +415,7 @@ class MessageComposer extends React.Component<IProps, IState> {
|
|||
<SendWysiwygComposer key="controls_input"
|
||||
disabled={this.state.haveRecording}
|
||||
onChange={this.onWysiwygChange}
|
||||
onSend={() => this.sendMessage}
|
||||
/>,
|
||||
);
|
||||
} else {
|
||||
|
@ -537,10 +533,10 @@ class MessageComposer extends React.Component<IProps, IState> {
|
|||
showPollsButton={this.state.showPollsButton}
|
||||
showStickersButton={this.showStickersButton}
|
||||
toggleButtonMenu={this.toggleButtonMenu}
|
||||
showVoiceBroadcastButton={this.showVoiceBroadcastButton}
|
||||
showVoiceBroadcastButton={this.state.showVoiceBroadcastButton}
|
||||
onStartVoiceBroadcastClick={() => {
|
||||
startNewVoiceBroadcastRecording(
|
||||
this.props.room.roomId,
|
||||
this.props.room,
|
||||
MatrixClientPeg.get(),
|
||||
VoiceBroadcastRecordingsStore.instance(),
|
||||
);
|
||||
|
|
|
@ -179,6 +179,7 @@ const EmojiButton: React.FC<IEmojiButtonProps> = ({ addEmoji, menuPosition }) =>
|
|||
iconClassName="mx_MessageComposer_emoji"
|
||||
onClick={openMenu}
|
||||
title={_t("Emoji")}
|
||||
inputRef={button}
|
||||
/>
|
||||
|
||||
{ contextMenu }
|
||||
|
|
|
@ -38,7 +38,6 @@ import { ITagMap } from "../../../stores/room-list/algorithms/models";
|
|||
import { DefaultTagID, TagID } from "../../../stores/room-list/models";
|
||||
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
|
||||
import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore";
|
||||
import { RoomViewStore } from "../../../stores/RoomViewStore";
|
||||
import {
|
||||
isMetaSpace,
|
||||
ISuggestedRoom,
|
||||
|
@ -62,6 +61,7 @@ import IconizedContextMenu, {
|
|||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||
import ExtraTile from "./ExtraTile";
|
||||
import RoomSublist, { IAuxButtonProps } from "./RoomSublist";
|
||||
import { SdkContextClass } from "../../../contexts/SDKContext";
|
||||
|
||||
interface IProps {
|
||||
onKeyDown: (ev: React.KeyboardEvent, state: IRovingTabIndexState) => void;
|
||||
|
@ -421,7 +421,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
|||
|
||||
public componentDidMount(): void {
|
||||
this.dispatcherRef = defaultDispatcher.register(this.onAction);
|
||||
RoomViewStore.instance.on(UPDATE_EVENT, this.onRoomViewStoreUpdate);
|
||||
SdkContextClass.instance.roomViewStore.on(UPDATE_EVENT, this.onRoomViewStoreUpdate);
|
||||
SpaceStore.instance.on(UPDATE_SUGGESTED_ROOMS, this.updateSuggestedRooms);
|
||||
RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.updateLists);
|
||||
this.favouriteMessageWatcher =
|
||||
|
@ -436,19 +436,19 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
|||
RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.updateLists);
|
||||
SettingsStore.unwatchSetting(this.favouriteMessageWatcher);
|
||||
defaultDispatcher.unregister(this.dispatcherRef);
|
||||
RoomViewStore.instance.off(UPDATE_EVENT, this.onRoomViewStoreUpdate);
|
||||
SdkContextClass.instance.roomViewStore.off(UPDATE_EVENT, this.onRoomViewStoreUpdate);
|
||||
}
|
||||
|
||||
private onRoomViewStoreUpdate = () => {
|
||||
this.setState({
|
||||
currentRoomId: RoomViewStore.instance.getRoomId(),
|
||||
currentRoomId: SdkContextClass.instance.roomViewStore.getRoomId(),
|
||||
});
|
||||
};
|
||||
|
||||
private onAction = (payload: ActionPayload) => {
|
||||
if (payload.action === Action.ViewRoomDelta) {
|
||||
const viewRoomDeltaPayload = payload as ViewRoomDeltaPayload;
|
||||
const currentRoomId = RoomViewStore.instance.getRoomId();
|
||||
const currentRoomId = SdkContextClass.instance.roomViewStore.getRoomId();
|
||||
const room = this.getRoomDelta(currentRoomId, viewRoomDeltaPayload.delta, viewRoomDeltaPayload.unread);
|
||||
if (room) {
|
||||
defaultDispatcher.dispatch<ViewRoomPayload>({
|
||||
|
|
|
@ -44,10 +44,10 @@ import PosthogTrackers from "../../../PosthogTrackers";
|
|||
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||
import { RoomViewStore } from "../../../stores/RoomViewStore";
|
||||
import { RoomTileCallSummary } from "./RoomTileCallSummary";
|
||||
import { RoomGeneralContextMenu } from "../context_menus/RoomGeneralContextMenu";
|
||||
import { CallStore, CallStoreEvent } from "../../../stores/CallStore";
|
||||
import { SdkContextClass } from "../../../contexts/SDKContext";
|
||||
|
||||
interface IProps {
|
||||
room: Room;
|
||||
|
@ -86,7 +86,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
super(props);
|
||||
|
||||
this.state = {
|
||||
selected: RoomViewStore.instance.getRoomId() === this.props.room.roomId,
|
||||
selected: SdkContextClass.instance.roomViewStore.getRoomId() === this.props.room.roomId,
|
||||
notificationsMenuPosition: null,
|
||||
generalMenuPosition: null,
|
||||
call: CallStore.instance.getCall(this.props.room.roomId),
|
||||
|
@ -146,7 +146,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
this.scrollIntoView();
|
||||
}
|
||||
|
||||
RoomViewStore.instance.addRoomListener(this.props.room.roomId, this.onActiveRoomUpdate);
|
||||
SdkContextClass.instance.roomViewStore.addRoomListener(this.props.room.roomId, this.onActiveRoomUpdate);
|
||||
this.dispatcherRef = defaultDispatcher.register(this.onAction);
|
||||
MessagePreviewStore.instance.on(
|
||||
MessagePreviewStore.getPreviewChangedEventName(this.props.room),
|
||||
|
@ -163,7 +163,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
RoomViewStore.instance.removeRoomListener(this.props.room.roomId, this.onActiveRoomUpdate);
|
||||
SdkContextClass.instance.roomViewStore.removeRoomListener(this.props.room.roomId, this.onActiveRoomUpdate);
|
||||
MessagePreviewStore.instance.off(
|
||||
MessagePreviewStore.getPreviewChangedEventName(this.props.room),
|
||||
this.onRoomPreviewChanged,
|
||||
|
|
|
@ -14,58 +14,14 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { forwardRef, RefObject, useMemo } from 'react';
|
||||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import React, { forwardRef, RefObject } from 'react';
|
||||
|
||||
import { useRoomContext } from '../../../../contexts/RoomContext';
|
||||
import { useMatrixClientContext } from '../../../../contexts/MatrixClientContext';
|
||||
import EditorStateTransfer from '../../../../utils/EditorStateTransfer';
|
||||
import { CommandPartCreator, Part } from '../../../../editor/parts';
|
||||
import { IRoomState } from '../../../structures/RoomView';
|
||||
import SettingsStore from '../../../../settings/SettingsStore';
|
||||
import { parseEvent } from '../../../../editor/deserialize';
|
||||
import { WysiwygComposer } from './components/WysiwygComposer';
|
||||
import { EditionButtons } from './components/EditionButtons';
|
||||
import { useWysiwygEditActionHandler } from './hooks/useWysiwygEditActionHandler';
|
||||
import { endEditing } from './utils/editing';
|
||||
import { editMessage } from './utils/message';
|
||||
|
||||
function parseEditorStateTransfer(
|
||||
editorStateTransfer: EditorStateTransfer,
|
||||
roomContext: IRoomState,
|
||||
mxClient: MatrixClient,
|
||||
) {
|
||||
if (!roomContext.room) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { room } = roomContext;
|
||||
|
||||
const partCreator = new CommandPartCreator(room, mxClient);
|
||||
|
||||
let parts: Part[];
|
||||
if (editorStateTransfer.hasEditorState()) {
|
||||
// if restoring state from a previous editor,
|
||||
// restore serialized parts from the state
|
||||
parts = editorStateTransfer.getSerializedParts().map(p => partCreator.deserializePart(p));
|
||||
} else {
|
||||
// otherwise, either restore serialized parts from localStorage or parse the body of the event
|
||||
// TODO local storage
|
||||
// const restoredParts = this.restoreStoredEditorState(partCreator);
|
||||
|
||||
if (editorStateTransfer.getEvent().getContent().format === 'org.matrix.custom.html') {
|
||||
return editorStateTransfer.getEvent().getContent().formatted_body || "";
|
||||
}
|
||||
|
||||
parts = parseEvent(editorStateTransfer.getEvent(), partCreator, {
|
||||
shouldEscape: SettingsStore.getValue("MessageComposerInput.useMarkdown"),
|
||||
});
|
||||
}
|
||||
|
||||
return parts.reduce((content, part) => content + part.text, '');
|
||||
// Todo local storage
|
||||
// this.saveStoredEditorState();
|
||||
}
|
||||
import { useEditing } from './hooks/useEditing';
|
||||
import { useInitialContent } from './hooks/useInitialContent';
|
||||
|
||||
interface ContentProps {
|
||||
disabled: boolean;
|
||||
|
@ -81,25 +37,26 @@ const Content = forwardRef<HTMLElement, ContentProps>(
|
|||
interface EditWysiwygComposerProps {
|
||||
disabled?: boolean;
|
||||
onChange?: (content: string) => void;
|
||||
editorStateTransfer?: EditorStateTransfer;
|
||||
editorStateTransfer: EditorStateTransfer;
|
||||
}
|
||||
|
||||
export function EditWysiwygComposer({ editorStateTransfer, ...props }: EditWysiwygComposerProps) {
|
||||
const roomContext = useRoomContext();
|
||||
const mxClient = useMatrixClientContext();
|
||||
|
||||
const initialContent = useMemo(() => {
|
||||
if (editorStateTransfer) {
|
||||
return parseEditorStateTransfer(editorStateTransfer, roomContext, mxClient);
|
||||
}
|
||||
}, [editorStateTransfer, roomContext, mxClient]);
|
||||
const initialContent = useInitialContent(editorStateTransfer);
|
||||
const isReady = !editorStateTransfer || Boolean(initialContent);
|
||||
|
||||
return isReady && <WysiwygComposer initialContent={initialContent} {...props}>{ (ref, wysiwyg, content) => (
|
||||
<>
|
||||
<Content disabled={props.disabled} ref={ref} />
|
||||
<EditionButtons onCancelClick={() => endEditing(roomContext)} onSaveClick={() => editMessage(content, { roomContext, mxClient, editorStateTransfer })} />
|
||||
</>)
|
||||
}
|
||||
const { editMessage, endEditing, setContent } = useEditing(initialContent, editorStateTransfer);
|
||||
|
||||
return isReady && <WysiwygComposer
|
||||
initialContent={initialContent}
|
||||
onChange={setContent}
|
||||
onSend={editMessage}
|
||||
{...props}>
|
||||
{ (ref, wysiwyg,
|
||||
content) => (
|
||||
<>
|
||||
<Content disabled={props.disabled} ref={ref} />
|
||||
<EditionButtons onCancelClick={endEditing} onSaveClick={editMessage} />
|
||||
</>)
|
||||
}
|
||||
</WysiwygComposer>;
|
||||
}
|
||||
|
|
|
@ -15,32 +15,33 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React, { forwardRef, RefObject } from 'react';
|
||||
import { FormattingFunctions } from '@matrix-org/matrix-wysiwyg';
|
||||
|
||||
import { useWysiwygSendActionHandler } from './hooks/useWysiwygSendActionHandler';
|
||||
import { WysiwygComposer } from './components/WysiwygComposer';
|
||||
import { Wysiwyg } from './types';
|
||||
|
||||
interface SendWysiwygComposerProps {
|
||||
disabled?: boolean;
|
||||
onChange?: (content: string) => void;
|
||||
}
|
||||
|
||||
export function SendWysiwygComposer(props: SendWysiwygComposerProps) {
|
||||
return (
|
||||
<WysiwygComposer {...props}>{ (ref, wysiwyg) => (
|
||||
<Content disabled={props.disabled} ref={ref} wysiwyg={wysiwyg} />
|
||||
) }
|
||||
</WysiwygComposer>);
|
||||
onSend(): () => void;
|
||||
}
|
||||
|
||||
interface ContentProps {
|
||||
disabled: boolean;
|
||||
wysiwyg: Wysiwyg;
|
||||
formattingFunctions: FormattingFunctions;
|
||||
}
|
||||
|
||||
const Content = forwardRef<HTMLElement, ContentProps>(
|
||||
function Content({ disabled, wysiwyg }: ContentProps, forwardRef: RefObject<HTMLElement>) {
|
||||
function Content({ disabled, formattingFunctions: wysiwyg }: ContentProps, forwardRef: RefObject<HTMLElement>) {
|
||||
useWysiwygSendActionHandler(disabled, forwardRef, wysiwyg);
|
||||
return null;
|
||||
},
|
||||
);
|
||||
|
||||
export function SendWysiwygComposer(props: SendWysiwygComposerProps) {
|
||||
return (
|
||||
<WysiwygComposer {...props}>{ (ref, wysiwyg) => (
|
||||
<Content disabled={props.disabled} ref={ref} formattingFunctions={wysiwyg} />
|
||||
) }
|
||||
</WysiwygComposer>);
|
||||
}
|
||||
|
|
|
@ -14,24 +14,31 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { MutableRefObject, ReactNode, useEffect } from 'react';
|
||||
import { useWysiwyg } from "@matrix-org/matrix-wysiwyg";
|
||||
import React, { memo, MutableRefObject, ReactNode, useEffect } from 'react';
|
||||
import { useWysiwyg, FormattingFunctions } from "@matrix-org/matrix-wysiwyg";
|
||||
|
||||
import { FormattingButtons } from './FormattingButtons';
|
||||
import { Editor } from './Editor';
|
||||
import { Wysiwyg } from '../types';
|
||||
import { useInputEventProcessor } from '../hooks/useInputEventProcessor';
|
||||
|
||||
interface WysiwygComposerProps {
|
||||
disabled?: boolean;
|
||||
onChange?: (content: string) => void;
|
||||
onSend: () => void;
|
||||
initialContent?: string;
|
||||
children?: (ref: MutableRefObject<HTMLDivElement | null>, wysiwyg: Wysiwyg, content: string) => ReactNode;
|
||||
children?: (
|
||||
ref: MutableRefObject<HTMLDivElement | null>,
|
||||
wysiwyg: FormattingFunctions,
|
||||
content: string) => ReactNode;
|
||||
}
|
||||
|
||||
export function WysiwygComposer(
|
||||
{ disabled = false, onChange, initialContent, children }: WysiwygComposerProps,
|
||||
export const WysiwygComposer = memo(function WysiwygComposer(
|
||||
{ disabled = false, onChange, onSend, initialContent, children }: WysiwygComposerProps,
|
||||
) {
|
||||
const { ref, isWysiwygReady, content, formattingStates, wysiwyg } = useWysiwyg({ initialContent });
|
||||
const inputEventProcessor = useInputEventProcessor(onSend);
|
||||
|
||||
const { ref, isWysiwygReady, content, formattingStates, wysiwyg } =
|
||||
useWysiwyg({ initialContent, inputEventProcessor });
|
||||
|
||||
useEffect(() => {
|
||||
if (!disabled && content !== null) {
|
||||
|
@ -46,4 +53,4 @@ export function WysiwygComposer(
|
|||
{ children?.(ref, wysiwyg, content) }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
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 { useCallback, useState } from "react";
|
||||
|
||||
import { useMatrixClientContext } from "../../../../../contexts/MatrixClientContext";
|
||||
import { useRoomContext } from "../../../../../contexts/RoomContext";
|
||||
import EditorStateTransfer from "../../../../../utils/EditorStateTransfer";
|
||||
import { endEditing } from "../utils/editing";
|
||||
import { editMessage } from "../utils/message";
|
||||
|
||||
export function useEditing(initialContent: string, editorStateTransfer: EditorStateTransfer) {
|
||||
const roomContext = useRoomContext();
|
||||
const mxClient = useMatrixClientContext();
|
||||
|
||||
const [content, setContent] = useState(initialContent);
|
||||
const editMessageMemoized = useCallback(() =>
|
||||
editMessage(content, { roomContext, mxClient, editorStateTransfer }),
|
||||
[content, roomContext, mxClient, editorStateTransfer],
|
||||
);
|
||||
|
||||
const endEditingMemoized = useCallback(() => endEditing(roomContext), [roomContext]);
|
||||
|
||||
return { setContent, editMessage: editMessageMemoized, endEditing: endEditingMemoized };
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
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 { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { useMatrixClientContext } from "../../../../../contexts/MatrixClientContext";
|
||||
import { useRoomContext } from "../../../../../contexts/RoomContext";
|
||||
import { parseEvent } from "../../../../../editor/deserialize";
|
||||
import { CommandPartCreator, Part } from "../../../../../editor/parts";
|
||||
import SettingsStore from "../../../../../settings/SettingsStore";
|
||||
import EditorStateTransfer from "../../../../../utils/EditorStateTransfer";
|
||||
|
||||
function parseEditorStateTransfer(
|
||||
editorStateTransfer: EditorStateTransfer,
|
||||
room: Room,
|
||||
mxClient: MatrixClient,
|
||||
): string {
|
||||
const partCreator = new CommandPartCreator(room, mxClient);
|
||||
|
||||
let parts: Part[];
|
||||
if (editorStateTransfer.hasEditorState()) {
|
||||
// if restoring state from a previous editor,
|
||||
// restore serialized parts from the state
|
||||
parts = editorStateTransfer.getSerializedParts().map(p => partCreator.deserializePart(p));
|
||||
} else {
|
||||
// otherwise, either restore serialized parts from localStorage or parse the body of the event
|
||||
// TODO local storage
|
||||
// const restoredParts = this.restoreStoredEditorState(partCreator);
|
||||
|
||||
if (editorStateTransfer.getEvent().getContent().format === 'org.matrix.custom.html') {
|
||||
return editorStateTransfer.getEvent().getContent().formatted_body || "";
|
||||
}
|
||||
|
||||
parts = parseEvent(editorStateTransfer.getEvent(), partCreator, {
|
||||
shouldEscape: SettingsStore.getValue("MessageComposerInput.useMarkdown"),
|
||||
});
|
||||
}
|
||||
|
||||
return parts.reduce((content, part) => content + part.text, '');
|
||||
// Todo local storage
|
||||
// this.saveStoredEditorState();
|
||||
}
|
||||
|
||||
export function useInitialContent(editorStateTransfer: EditorStateTransfer) {
|
||||
const roomContext = useRoomContext();
|
||||
const mxClient = useMatrixClientContext();
|
||||
|
||||
return useMemo<string>(() => {
|
||||
if (editorStateTransfer && roomContext.room) {
|
||||
return parseEditorStateTransfer(editorStateTransfer, roomContext.room, mxClient);
|
||||
}
|
||||
}, [editorStateTransfer, roomContext, mxClient]);
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
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 { WysiwygInputEvent } from "@matrix-org/matrix-wysiwyg";
|
||||
import { useCallback } from "react";
|
||||
|
||||
import { useSettingValue } from "../../../../../hooks/useSettings";
|
||||
|
||||
export function useInputEventProcessor(onSend: () => void) {
|
||||
const isCtrlEnter = useSettingValue("MessageComposerInput.ctrlEnterToSend") as boolean;
|
||||
return useCallback((event: WysiwygInputEvent) => {
|
||||
if (event instanceof ClipboardEvent) {
|
||||
return event;
|
||||
}
|
||||
|
||||
if (
|
||||
(event.inputType === 'insertParagraph' && !isCtrlEnter) ||
|
||||
event.inputType === 'sendMessage'
|
||||
) {
|
||||
onSend();
|
||||
return null;
|
||||
}
|
||||
|
||||
return event;
|
||||
}
|
||||
, [isCtrlEnter, onSend]);
|
||||
}
|
|
@ -15,19 +15,19 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import { RefObject, useCallback, useRef } from "react";
|
||||
import { FormattingFunctions } from "@matrix-org/matrix-wysiwyg";
|
||||
|
||||
import defaultDispatcher from "../../../../../dispatcher/dispatcher";
|
||||
import { Action } from "../../../../../dispatcher/actions";
|
||||
import { ActionPayload } from "../../../../../dispatcher/payloads";
|
||||
import { TimelineRenderingType, useRoomContext } from "../../../../../contexts/RoomContext";
|
||||
import { useDispatcher } from "../../../../../hooks/useDispatcher";
|
||||
import { Wysiwyg } from "../types";
|
||||
import { focusComposer } from "./utils";
|
||||
|
||||
export function useWysiwygSendActionHandler(
|
||||
disabled: boolean,
|
||||
composerElement: RefObject<HTMLElement>,
|
||||
wysiwyg: Wysiwyg,
|
||||
wysiwyg: FormattingFunctions,
|
||||
) {
|
||||
const roomContext = useRoomContext();
|
||||
const timeoutId = useRef<number>();
|
||||
|
|
|
@ -19,13 +19,14 @@ import classNames from 'classnames';
|
|||
import { IMyDevice } from "matrix-js-sdk/src/client";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { CrossSigningInfo } from "matrix-js-sdk/src/crypto/CrossSigning";
|
||||
import { CryptoEvent } from 'matrix-js-sdk/src/crypto';
|
||||
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import DevicesPanelEntry from "./DevicesPanelEntry";
|
||||
import Spinner from "../elements/Spinner";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import { deleteDevicesWithInteractiveAuth } from './devices/deleteDevices';
|
||||
import MatrixClientContext from '../../../contexts/MatrixClientContext';
|
||||
|
||||
interface IProps {
|
||||
className?: string;
|
||||
|
@ -40,6 +41,8 @@ interface IState {
|
|||
}
|
||||
|
||||
export default class DevicesPanel extends React.Component<IProps, IState> {
|
||||
public static contextType = MatrixClientContext;
|
||||
public context!: React.ContextType<typeof MatrixClientContext>;
|
||||
private unmounted = false;
|
||||
|
||||
constructor(props: IProps) {
|
||||
|
@ -52,15 +55,22 @@ export default class DevicesPanel extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
this.context.on(CryptoEvent.DevicesUpdated, this.onDevicesUpdated);
|
||||
this.loadDevices();
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
this.context.off(CryptoEvent.DevicesUpdated, this.onDevicesUpdated);
|
||||
this.unmounted = true;
|
||||
}
|
||||
|
||||
private onDevicesUpdated = (users: string[]) => {
|
||||
if (!users.includes(this.context.getUserId())) return;
|
||||
this.loadDevices();
|
||||
};
|
||||
|
||||
private loadDevices(): void {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const cli = this.context;
|
||||
cli.getDevices().then(
|
||||
(resp) => {
|
||||
if (this.unmounted) { return; }
|
||||
|
@ -111,7 +121,7 @@ export default class DevicesPanel extends React.Component<IProps, IState> {
|
|||
|
||||
private isDeviceVerified(device: IMyDevice): boolean | null {
|
||||
try {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const cli = this.context;
|
||||
const deviceInfo = cli.getStoredDevice(cli.getUserId(), device.device_id);
|
||||
return this.state.crossSigningInfo.checkDeviceTrust(
|
||||
this.state.crossSigningInfo,
|
||||
|
@ -184,7 +194,7 @@ export default class DevicesPanel extends React.Component<IProps, IState> {
|
|||
|
||||
try {
|
||||
await deleteDevicesWithInteractiveAuth(
|
||||
MatrixClientPeg.get(),
|
||||
this.context,
|
||||
this.state.selectedDevices,
|
||||
(success) => {
|
||||
if (success) {
|
||||
|
@ -208,7 +218,7 @@ export default class DevicesPanel extends React.Component<IProps, IState> {
|
|||
};
|
||||
|
||||
private renderDevice = (device: IMyDevice): JSX.Element => {
|
||||
const myDeviceId = MatrixClientPeg.get().getDeviceId();
|
||||
const myDeviceId = this.context.getDeviceId();
|
||||
const myDevice = this.state.devices.find((device) => (device.device_id === myDeviceId));
|
||||
|
||||
const isOwnDevice = device.device_id === myDeviceId;
|
||||
|
@ -246,7 +256,7 @@ export default class DevicesPanel extends React.Component<IProps, IState> {
|
|||
return <Spinner />;
|
||||
}
|
||||
|
||||
const myDeviceId = MatrixClientPeg.get().getDeviceId();
|
||||
const myDeviceId = this.context.getDeviceId();
|
||||
const myDevice = devices.find((device) => (device.device_id === myDeviceId));
|
||||
|
||||
if (!myDevice) {
|
||||
|
|
|
@ -62,7 +62,6 @@ const DeviceDetails: React.FC<Props> = ({
|
|||
id: 'session',
|
||||
values: [
|
||||
{ label: _t('Session ID'), value: device.device_id },
|
||||
{ label: _t('Client'), value: device.client },
|
||||
{
|
||||
label: _t('Last activity'),
|
||||
value: device.last_seen_ts && formatDate(new Date(device.last_seen_ts)),
|
||||
|
@ -84,6 +83,7 @@ const DeviceDetails: React.FC<Props> = ({
|
|||
values: [
|
||||
{ label: _t('Model'), value: device.deviceModel },
|
||||
{ label: _t('Operating system'), value: device.deviceOperatingSystem },
|
||||
{ label: _t('Browser'), value: device.client },
|
||||
{ label: _t('IP address'), value: device.last_seen_ip },
|
||||
],
|
||||
},
|
||||
|
|
63
src/components/views/settings/devices/LoginWithQRSection.tsx
Normal file
63
src/components/views/settings/devices/LoginWithQRSection.tsx
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
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 type { IServerVersions } from 'matrix-js-sdk/src/matrix';
|
||||
import { _t } from '../../../../languageHandler';
|
||||
import AccessibleButton from '../../elements/AccessibleButton';
|
||||
import SettingsSubsection from '../shared/SettingsSubsection';
|
||||
import SettingsStore from '../../../../settings/SettingsStore';
|
||||
|
||||
interface IProps {
|
||||
onShowQr: () => void;
|
||||
versions: IServerVersions;
|
||||
}
|
||||
|
||||
export default class LoginWithQRSection extends React.Component<IProps> {
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
const msc3882Supported = !!this.props.versions?.unstable_features?.['org.matrix.msc3882'];
|
||||
const msc3886Supported = !!this.props.versions?.unstable_features?.['org.matrix.msc3886'];
|
||||
|
||||
// Needs to be enabled as a feature + server support MSC3886 or have a default rendezvous server configured:
|
||||
const offerShowQr = SettingsStore.getValue("feature_qr_signin_reciprocate_show") &&
|
||||
msc3882Supported && msc3886Supported; // We don't support configuration of a fallback at the moment so we just check the MSCs
|
||||
|
||||
// don't show anything if no method is available
|
||||
if (!offerShowQr) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <SettingsSubsection
|
||||
heading={_t('Sign in with QR code')}
|
||||
>
|
||||
<div className="mx_LoginWithQRSection">
|
||||
<p className="mx_SettingsTab_subsectionText">{
|
||||
_t("You can use this device to sign in a new device with a QR code. You will need to " +
|
||||
"scan the QR code shown on this device with your device that's signed out.")
|
||||
}</p>
|
||||
<AccessibleButton
|
||||
onClick={this.props.onShowQr}
|
||||
kind="primary"
|
||||
>{ _t("Show QR code") }</AccessibleButton>
|
||||
</div>
|
||||
</SettingsSubsection>;
|
||||
}
|
||||
}
|
|
@ -31,6 +31,7 @@ import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/reque
|
|||
import { MatrixError } from "matrix-js-sdk/src/http-api";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { LocalNotificationSettings } from "matrix-js-sdk/src/@types/local_notifications";
|
||||
import { CryptoEvent } from "matrix-js-sdk/src/crypto";
|
||||
|
||||
import MatrixClientContext from "../../../../contexts/MatrixClientContext";
|
||||
import { _t } from "../../../../languageHandler";
|
||||
|
@ -179,6 +180,12 @@ export const useOwnDevices = (): DevicesState => {
|
|||
refreshDevices();
|
||||
}, [refreshDevices]);
|
||||
|
||||
useEventEmitter(matrixClient, CryptoEvent.DevicesUpdated, (users: string[]): void => {
|
||||
if (users.includes(userId)) {
|
||||
refreshDevices();
|
||||
}
|
||||
});
|
||||
|
||||
useEventEmitter(matrixClient, ClientEvent.AccountData, (event: MatrixEvent): void => {
|
||||
const type = event.getType();
|
||||
if (type.startsWith(LOCAL_NOTIFICATION_SETTINGS_PREFIX.name)) {
|
||||
|
|
|
@ -38,6 +38,9 @@ import InlineSpinner from "../../../elements/InlineSpinner";
|
|||
import { PosthogAnalytics } from "../../../../../PosthogAnalytics";
|
||||
import { showDialog as showAnalyticsLearnMoreDialog } from "../../../dialogs/AnalyticsLearnMoreDialog";
|
||||
import { privateShouldBeEncrypted } from "../../../../../utils/rooms";
|
||||
import LoginWithQR, { Mode } from '../../../auth/LoginWithQR';
|
||||
import LoginWithQRSection from '../../devices/LoginWithQRSection';
|
||||
import type { IServerVersions } from 'matrix-js-sdk/src/matrix';
|
||||
|
||||
interface IIgnoredUserProps {
|
||||
userId: string;
|
||||
|
@ -72,6 +75,8 @@ interface IState {
|
|||
waitingUnignored: string[];
|
||||
managingInvites: boolean;
|
||||
invitedRoomIds: Set<string>;
|
||||
showLoginWithQR: Mode | null;
|
||||
versions?: IServerVersions;
|
||||
}
|
||||
|
||||
export default class SecurityUserSettingsTab extends React.Component<IProps, IState> {
|
||||
|
@ -88,6 +93,7 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
|
|||
waitingUnignored: [],
|
||||
managingInvites: false,
|
||||
invitedRoomIds,
|
||||
showLoginWithQR: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -102,6 +108,7 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
|
|||
public componentDidMount(): void {
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
MatrixClientPeg.get().on(RoomEvent.MyMembership, this.onMyMembership);
|
||||
MatrixClientPeg.get().getVersions().then(versions => this.setState({ versions }));
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
|
@ -251,6 +258,14 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
|
|||
);
|
||||
}
|
||||
|
||||
private onShowQRClicked = (): void => {
|
||||
this.setState({ showLoginWithQR: Mode.Show });
|
||||
};
|
||||
|
||||
private onLoginWithQRFinished = (): void => {
|
||||
this.setState({ showLoginWithQR: null });
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
const secureBackup = (
|
||||
<div className='mx_SettingsTab_section'>
|
||||
|
@ -347,6 +362,7 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
|
|||
}
|
||||
|
||||
const useNewSessionManager = SettingsStore.getValue("feature_new_device_manager");
|
||||
const showQrCodeEnabled = SettingsStore.getValue("feature_qr_signin_reciprocate_show");
|
||||
const devicesSection = useNewSessionManager
|
||||
? null
|
||||
: <>
|
||||
|
@ -363,8 +379,20 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
|
|||
</span>
|
||||
<DevicesPanel />
|
||||
</div>
|
||||
{ showQrCodeEnabled ?
|
||||
<LoginWithQRSection onShowQr={this.onShowQRClicked} versions={this.state.versions} />
|
||||
: null
|
||||
}
|
||||
</>;
|
||||
|
||||
const client = MatrixClientPeg.get();
|
||||
|
||||
if (showQrCodeEnabled && this.state.showLoginWithQR) {
|
||||
return <div className="mx_SettingsTab mx_SecurityUserSettingsTab">
|
||||
<LoginWithQR onFinished={this.onLoginWithQRFinished} mode={this.state.showLoginWithQR} client={client} />
|
||||
</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_SettingsTab mx_SecurityUserSettingsTab">
|
||||
{ warning }
|
||||
|
|
|
@ -32,6 +32,10 @@ import SecurityRecommendations from '../../devices/SecurityRecommendations';
|
|||
import { DeviceSecurityVariation, ExtendedDevice } from '../../devices/types';
|
||||
import { deleteDevicesWithInteractiveAuth } from '../../devices/deleteDevices';
|
||||
import SettingsTab from '../SettingsTab';
|
||||
import LoginWithQRSection from '../../devices/LoginWithQRSection';
|
||||
import LoginWithQR, { Mode } from '../../../auth/LoginWithQR';
|
||||
import SettingsStore from '../../../../../settings/SettingsStore';
|
||||
import { useAsyncMemo } from '../../../../../hooks/useAsyncMemo';
|
||||
|
||||
const useSignOut = (
|
||||
matrixClient: MatrixClient,
|
||||
|
@ -104,6 +108,7 @@ const SessionManagerTab: React.FC = () => {
|
|||
const matrixClient = useContext(MatrixClientContext);
|
||||
const userId = matrixClient.getUserId();
|
||||
const currentUserMember = userId && matrixClient.getUser(userId) || undefined;
|
||||
const clientVersions = useAsyncMemo(() => matrixClient.getVersions(), [matrixClient]);
|
||||
|
||||
const onDeviceExpandToggle = (deviceId: ExtendedDevice['device_id']): void => {
|
||||
if (expandedDeviceIds.includes(deviceId)) {
|
||||
|
@ -175,6 +180,26 @@ const SessionManagerTab: React.FC = () => {
|
|||
onSignOutOtherDevices(Object.keys(otherDevices));
|
||||
}: undefined;
|
||||
|
||||
const [signInWithQrMode, setSignInWithQrMode] = useState<Mode | null>();
|
||||
|
||||
const showQrCodeEnabled = SettingsStore.getValue("feature_qr_signin_reciprocate_show");
|
||||
|
||||
const onQrFinish = useCallback(() => {
|
||||
setSignInWithQrMode(null);
|
||||
}, [setSignInWithQrMode]);
|
||||
|
||||
const onShowQrClicked = useCallback(() => {
|
||||
setSignInWithQrMode(Mode.Show);
|
||||
}, [setSignInWithQrMode]);
|
||||
|
||||
if (showQrCodeEnabled && signInWithQrMode) {
|
||||
return <LoginWithQR
|
||||
mode={signInWithQrMode}
|
||||
onFinished={onQrFinish}
|
||||
client={matrixClient}
|
||||
/>;
|
||||
}
|
||||
|
||||
return <SettingsTab heading={_t('Sessions')}>
|
||||
<SecurityRecommendations
|
||||
devices={devices}
|
||||
|
@ -222,6 +247,10 @@ const SessionManagerTab: React.FC = () => {
|
|||
/>
|
||||
</SettingsSubsection>
|
||||
}
|
||||
{ showQrCodeEnabled ?
|
||||
<LoginWithQRSection onShowQr={onShowQrClicked} versions={clientVersions} />
|
||||
: null
|
||||
}
|
||||
</SettingsTab>;
|
||||
};
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ import { Icon as FavoriteIcon } from '../../../../res/img/element-icons/roomlist
|
|||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import Modal from "../../../Modal";
|
||||
import DevtoolsDialog from "../dialogs/DevtoolsDialog";
|
||||
import { RoomViewStore } from "../../../stores/RoomViewStore";
|
||||
import { SdkContextClass } from "../../../contexts/SDKContext";
|
||||
|
||||
const QuickSettingsButton = ({ isPanelCollapsed = false }) => {
|
||||
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu<HTMLDivElement>();
|
||||
|
@ -72,7 +72,7 @@ const QuickSettingsButton = ({ isPanelCollapsed = false }) => {
|
|||
onClick={() => {
|
||||
closeMenu();
|
||||
Modal.createDialog(DevtoolsDialog, {
|
||||
roomId: RoomViewStore.instance.getRoomId(),
|
||||
roomId: SdkContextClass.instance.roomViewStore.getRoomId(),
|
||||
}, "mx_DevtoolsDialog_wrapper");
|
||||
}}
|
||||
kind="danger_outline"
|
||||
|
|
|
@ -21,7 +21,6 @@ import classNames from 'classnames';
|
|||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
|
||||
import LegacyCallView from "./LegacyCallView";
|
||||
import { RoomViewStore } from '../../../stores/RoomViewStore';
|
||||
import LegacyCallHandler, { LegacyCallHandlerEvent } from '../../../LegacyCallHandler';
|
||||
import PersistentApp from "../elements/PersistentApp";
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
|
@ -34,6 +33,7 @@ import ActiveWidgetStore, { ActiveWidgetStoreEvent } from '../../../stores/Activ
|
|||
import WidgetStore, { IApp } from "../../../stores/WidgetStore";
|
||||
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { UPDATE_EVENT } from '../../../stores/AsyncStore';
|
||||
import { SdkContextClass } from '../../../contexts/SDKContext';
|
||||
import { CallStore } from "../../../stores/CallStore";
|
||||
import {
|
||||
VoiceBroadcastRecording,
|
||||
|
@ -129,7 +129,7 @@ class PipView extends React.Component<IProps, IState> {
|
|||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
const roomId = RoomViewStore.instance.getRoomId();
|
||||
const roomId = SdkContextClass.instance.roomViewStore.getRoomId();
|
||||
|
||||
const [primaryCall, secondaryCalls] = getPrimarySecondaryCallsForPip(roomId);
|
||||
|
||||
|
@ -147,7 +147,7 @@ class PipView extends React.Component<IProps, IState> {
|
|||
public componentDidMount() {
|
||||
LegacyCallHandler.instance.addListener(LegacyCallHandlerEvent.CallChangeRoom, this.updateCalls);
|
||||
LegacyCallHandler.instance.addListener(LegacyCallHandlerEvent.CallState, this.updateCalls);
|
||||
RoomViewStore.instance.addListener(UPDATE_EVENT, this.onRoomViewStoreUpdate);
|
||||
SdkContextClass.instance.roomViewStore.addListener(UPDATE_EVENT, this.onRoomViewStoreUpdate);
|
||||
MatrixClientPeg.get().on(CallEvent.RemoteHoldUnhold, this.onCallRemoteHold);
|
||||
const room = MatrixClientPeg.get()?.getRoom(this.state.viewedRoomId);
|
||||
if (room) {
|
||||
|
@ -164,7 +164,7 @@ class PipView extends React.Component<IProps, IState> {
|
|||
LegacyCallHandler.instance.removeListener(LegacyCallHandlerEvent.CallState, this.updateCalls);
|
||||
const cli = MatrixClientPeg.get();
|
||||
cli?.removeListener(CallEvent.RemoteHoldUnhold, this.onCallRemoteHold);
|
||||
RoomViewStore.instance.removeListener(UPDATE_EVENT, this.onRoomViewStoreUpdate);
|
||||
SdkContextClass.instance.roomViewStore.removeListener(UPDATE_EVENT, this.onRoomViewStoreUpdate);
|
||||
const room = cli?.getRoom(this.state.viewedRoomId);
|
||||
if (room) {
|
||||
WidgetLayoutStore.instance.off(WidgetLayoutStore.emissionForRoom(room), this.updateCalls);
|
||||
|
@ -186,7 +186,7 @@ class PipView extends React.Component<IProps, IState> {
|
|||
private onMove = () => this.movePersistedElement.current?.();
|
||||
|
||||
private onRoomViewStoreUpdate = () => {
|
||||
const newRoomId = RoomViewStore.instance.getRoomId();
|
||||
const newRoomId = SdkContextClass.instance.roomViewStore.getRoomId();
|
||||
const oldRoomId = this.state.viewedRoomId;
|
||||
if (newRoomId === oldRoomId) return;
|
||||
// The WidgetLayoutStore observer always tracks the currently viewed Room,
|
||||
|
|
|
@ -45,7 +45,6 @@ const RoomContext = createContext<IRoomState>({
|
|||
canReact: false,
|
||||
canSelfRedact: false,
|
||||
canSendMessages: false,
|
||||
canSendVoiceBroadcasts: false,
|
||||
resizing: false,
|
||||
layout: Layout.Group,
|
||||
lowBandwidth: false,
|
||||
|
|
127
src/contexts/SDKContext.ts
Normal file
127
src/contexts/SDKContext.ts
Normal file
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
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 { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { createContext } from "react";
|
||||
|
||||
import defaultDispatcher from "../dispatcher/dispatcher";
|
||||
import LegacyCallHandler from "../LegacyCallHandler";
|
||||
import { PosthogAnalytics } from "../PosthogAnalytics";
|
||||
import { SlidingSyncManager } from "../SlidingSyncManager";
|
||||
import { RoomNotificationStateStore } from "../stores/notifications/RoomNotificationStateStore";
|
||||
import RightPanelStore from "../stores/right-panel/RightPanelStore";
|
||||
import { RoomViewStore } from "../stores/RoomViewStore";
|
||||
import SpaceStore, { SpaceStoreClass } from "../stores/spaces/SpaceStore";
|
||||
import { WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore";
|
||||
import WidgetStore from "../stores/WidgetStore";
|
||||
|
||||
export const SDKContext = createContext<SdkContextClass>(undefined);
|
||||
SDKContext.displayName = "SDKContext";
|
||||
|
||||
/**
|
||||
* A class which lazily initialises stores as and when they are requested, ensuring they remain
|
||||
* as singletons scoped to this object.
|
||||
*/
|
||||
export class SdkContextClass {
|
||||
/**
|
||||
* The global SdkContextClass instance. This is a temporary measure whilst so many stores remain global
|
||||
* as well. Over time, these stores should accept a `SdkContextClass` instance in their constructor.
|
||||
* When all stores do this, this static variable can be deleted.
|
||||
*/
|
||||
public static readonly instance = new SdkContextClass();
|
||||
|
||||
// Optional as we don't have a client on initial load if unregistered. This should be set
|
||||
// when the MatrixClient is first acquired in the dispatcher event Action.OnLoggedIn.
|
||||
// It is only safe to set this once, as updating this value will NOT notify components using
|
||||
// this Context.
|
||||
public client?: MatrixClient;
|
||||
|
||||
// All protected fields to make it easier to derive test stores
|
||||
protected _RightPanelStore?: RightPanelStore;
|
||||
protected _RoomNotificationStateStore?: RoomNotificationStateStore;
|
||||
protected _RoomViewStore?: RoomViewStore;
|
||||
protected _WidgetLayoutStore?: WidgetLayoutStore;
|
||||
protected _WidgetStore?: WidgetStore;
|
||||
protected _PosthogAnalytics?: PosthogAnalytics;
|
||||
protected _SlidingSyncManager?: SlidingSyncManager;
|
||||
protected _SpaceStore?: SpaceStoreClass;
|
||||
protected _LegacyCallHandler?: LegacyCallHandler;
|
||||
|
||||
/**
|
||||
* Automatically construct stores which need to be created eagerly so they can register with
|
||||
* the dispatcher.
|
||||
*/
|
||||
public constructEagerStores() {
|
||||
this._RoomViewStore = this.roomViewStore;
|
||||
}
|
||||
|
||||
public get legacyCallHandler(): LegacyCallHandler {
|
||||
if (!this._LegacyCallHandler) {
|
||||
this._LegacyCallHandler = LegacyCallHandler.instance;
|
||||
}
|
||||
return this._LegacyCallHandler;
|
||||
}
|
||||
public get rightPanelStore(): RightPanelStore {
|
||||
if (!this._RightPanelStore) {
|
||||
this._RightPanelStore = RightPanelStore.instance;
|
||||
}
|
||||
return this._RightPanelStore;
|
||||
}
|
||||
public get roomNotificationStateStore(): RoomNotificationStateStore {
|
||||
if (!this._RoomNotificationStateStore) {
|
||||
this._RoomNotificationStateStore = RoomNotificationStateStore.instance;
|
||||
}
|
||||
return this._RoomNotificationStateStore;
|
||||
}
|
||||
public get roomViewStore(): RoomViewStore {
|
||||
if (!this._RoomViewStore) {
|
||||
this._RoomViewStore = new RoomViewStore(
|
||||
defaultDispatcher, this,
|
||||
);
|
||||
}
|
||||
return this._RoomViewStore;
|
||||
}
|
||||
public get widgetLayoutStore(): WidgetLayoutStore {
|
||||
if (!this._WidgetLayoutStore) {
|
||||
this._WidgetLayoutStore = WidgetLayoutStore.instance;
|
||||
}
|
||||
return this._WidgetLayoutStore;
|
||||
}
|
||||
public get widgetStore(): WidgetStore {
|
||||
if (!this._WidgetStore) {
|
||||
this._WidgetStore = WidgetStore.instance;
|
||||
}
|
||||
return this._WidgetStore;
|
||||
}
|
||||
public get posthogAnalytics(): PosthogAnalytics {
|
||||
if (!this._PosthogAnalytics) {
|
||||
this._PosthogAnalytics = PosthogAnalytics.instance;
|
||||
}
|
||||
return this._PosthogAnalytics;
|
||||
}
|
||||
public get slidingSyncManager(): SlidingSyncManager {
|
||||
if (!this._SlidingSyncManager) {
|
||||
this._SlidingSyncManager = SlidingSyncManager.instance;
|
||||
}
|
||||
return this._SlidingSyncManager;
|
||||
}
|
||||
public get spaceStore(): SpaceStoreClass {
|
||||
if (!this._SpaceStore) {
|
||||
this._SpaceStore = SpaceStore.instance;
|
||||
}
|
||||
return this._SpaceStore;
|
||||
}
|
||||
}
|
|
@ -2163,5 +2163,15 @@
|
|||
"You cannot place calls in this browser.": "Не можете да провеждате обаждания в този браузър.",
|
||||
"Calls are unsupported": "Обажданията не се поддържат",
|
||||
"The user you called is busy.": "Потребителят, когото потърсихте, е зает.",
|
||||
"User Busy": "Потребителят е зает"
|
||||
"User Busy": "Потребителят е зает",
|
||||
"Some invites couldn't be sent": "Някои покани не можаха да бъдат изпратени",
|
||||
"We sent the others, but the below people couldn't be invited to <RoomName/>": "Изпратихме останалите покани, но следните хора не можаха да бъдат поканени в <RoomName/>",
|
||||
"Empty room (was %(oldName)s)": "Празна стая (беше %(oldName)s)",
|
||||
"Inviting %(user)s and %(count)s others|one": "Канене на %(user)s и още 1 друг",
|
||||
"Inviting %(user)s and %(count)s others|other": "Канене на %(user)s и %(count)s други",
|
||||
"Inviting %(user1)s and %(user2)s": "Канене на %(user1)s и %(user2)s",
|
||||
"%(user)s and %(count)s others|one": "%(user)s и още 1",
|
||||
"%(user)s and %(count)s others|other": "%(user)s и %(count)s други",
|
||||
"%(user1)s and %(user2)s": "%(user1)s и %(user2)s",
|
||||
"Empty room": "Празна стая"
|
||||
}
|
||||
|
|
|
@ -538,7 +538,7 @@
|
|||
"Upgrade this room to version %(version)s": "Aktualizace místnosti na verzi %(version)s",
|
||||
"Security & Privacy": "Zabezpečení a soukromí",
|
||||
"Encryption": "Šifrování",
|
||||
"Once enabled, encryption cannot be disabled.": "Jakmile je šifrování povoleno, nelze jej zakázat.",
|
||||
"Once enabled, encryption cannot be disabled.": "Po zapnutí šifrování ho není možné vypnout.",
|
||||
"Encrypted": "Šifrováno",
|
||||
"General": "Obecné",
|
||||
"General failure": "Nějaká chyba",
|
||||
|
@ -1262,7 +1262,7 @@
|
|||
"about a day from now": "asi za den",
|
||||
"%(num)s days from now": "za %(num)s dní",
|
||||
"Show info about bridges in room settings": "Zobrazovat v nastavení místnosti informace o propojeních",
|
||||
"Never send encrypted messages to unverified sessions from this session": "Nikdy neposílat šifrované zprávy neověřených zařízením",
|
||||
"Never send encrypted messages to unverified sessions from this session": "Nikdy neposílat šifrované zprávy do neověřených relací z této relace",
|
||||
"Never send encrypted messages to unverified sessions in this room from this session": "Nikdy v této místnosti neposílat šifrované zprávy neověřeným relacím",
|
||||
"Enable message search in encrypted rooms": "Povolit vyhledávání v šifrovaných místnostech",
|
||||
"How fast should messages be downloaded.": "Jak rychle se mají zprávy stahovat.",
|
||||
|
@ -3582,12 +3582,58 @@
|
|||
"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 notifikace v této relaci.",
|
||||
"Push notifications": "Push notifikace",
|
||||
"Toggle push notifications on this session.": "Přepnout push oznámení v této relaci.",
|
||||
"Push notifications": "Push oznámení",
|
||||
"Enable notifications for this device": "Povolit oznámení pro toto zařízení",
|
||||
"Turn off to disable notifications on all your devices and sessions": "Vypnutím zakážete oznámení na všech zařízeních a relacích",
|
||||
"Enable notifications for this account": "Povolit oznámení pro tento účet",
|
||||
"Video call ended": "Videohovor ukončen",
|
||||
"%(name)s started a video call": "%(name)s zahájil(a) videohovor",
|
||||
"Record the client name, version, and url to recognise sessions more easily in session manager": "Zaznamenat název, verzi a url pro snadnější rozpoznání relací ve správci relací"
|
||||
"Record the client name, version, and url to recognise sessions more easily in session manager": "Zaznamenat název, verzi a url pro snadnější rozpoznání relací ve správci relací",
|
||||
"URL": "URL",
|
||||
"Version": "Verze",
|
||||
"Application": "Aplikace",
|
||||
"Room info": "Informace o místnosti",
|
||||
"View chat timeline": "Zobrazit časovou osu konverzace",
|
||||
"Close call": "Zavřít hovor",
|
||||
"Freedom": "Svoboda",
|
||||
"Layout type": "Typ rozložení",
|
||||
"Spotlight": "Reflektor",
|
||||
"Unknown session type": "Neznámý typ relace",
|
||||
"Web session": "Relace na webu",
|
||||
"Mobile session": "Relace mobilního zařízení",
|
||||
"Desktop session": "Relace stolního počítače",
|
||||
"Fill screen": "Vyplnit obrazovku",
|
||||
"Video call started": "Videohovor byl zahájen",
|
||||
"Unknown room": "Neznámá místnost",
|
||||
"Video call started in %(roomName)s. (not supported by this browser)": "Videohovor byl zahájen v %(roomName)s. (není podporováno tímto prohlížečem)",
|
||||
"Video call started in %(roomName)s.": "Videohovor byl zahájen v %(roomName)s.",
|
||||
"Operating system": "Operační systém",
|
||||
"Model": "Model",
|
||||
"Client": "Klient",
|
||||
"Video call (%(brand)s)": "Videohovor (%(brand)s)",
|
||||
"Call type": "Typ volání",
|
||||
"You do not have sufficient permissions to change this.": "Ke změně nemáte dostatečná oprávnění.",
|
||||
"%(brand)s is end-to-end encrypted, but is currently limited to smaller numbers of users.": "%(brand)s je koncově šifrovaný, ale v současné době je omezen na menší počet uživatelů.",
|
||||
"Enable %(brand)s as an additional calling option in this room": "Povolit %(brand)s jako další možnost volání v této místnosti",
|
||||
"Join %(brand)s calls": "Připojit se k %(brand)s volání",
|
||||
"Start %(brand)s calls": "Zahájit %(brand)s volání",
|
||||
"Sorry — this call is currently full": "Omlouváme se — tento hovor je v současné době plný",
|
||||
"Our new sessions manager provides better visibility of all your sessions, and greater control over them including the ability to remotely toggle push notifications.": "Náš nový správce relací poskytuje lepší přehled o všech relacích a lepší kontrolu nad nimi, včetně možnosti vzdáleně přepínat push oznámení.",
|
||||
"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í",
|
||||
"Wysiwyg composer (plain text mode coming soon) (under active development)": "Wysiwyg editor (textový režim již brzy) (v aktivním vývoji)",
|
||||
"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í",
|
||||
"Italic": "Kurzíva",
|
||||
"Try out the rich text editor (plain text mode coming soon)": "Vyzkoušejte nový editor (textový režim již brzy)",
|
||||
"You have already joined this call from another device": "K tomuto hovoru jste se již připojili z jiného zařízení",
|
||||
"stop voice broadcast": "zastavit hlasové vysílání",
|
||||
"Notifications silenced": "Oznámení ztlumena",
|
||||
"Yes, stop broadcast": "Ano, zastavit vysílání",
|
||||
"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.": "Opravdu chcete ukončit živé vysílání? Tím se vysílání ukončí a v místnosti bude k dispozici celý záznam.",
|
||||
"Stop live broadcasting?": "Ukončit živé vysílání?"
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -637,6 +637,10 @@
|
|||
"Send <b>%(msgtype)s</b> messages as you in your active room": "Send <b>%(msgtype)s</b> messages as you in your active room",
|
||||
"See <b>%(msgtype)s</b> messages posted to this room": "See <b>%(msgtype)s</b> messages posted to this room",
|
||||
"See <b>%(msgtype)s</b> messages posted to your active room": "See <b>%(msgtype)s</b> messages posted to your active room",
|
||||
"Can't start a new voice broadcast": "Can't start a new voice broadcast",
|
||||
"You are already recording a voice broadcast. Please end your current voice broadcast to start a new one.": "You are already recording a voice broadcast. Please end your current voice broadcast to start a new one.",
|
||||
"You don't have the required permissions to start a voice broadcast in this room. Contact a room administrator to upgrade your permissions.": "You don't have the required permissions to start a voice broadcast in this room. Contact a room administrator to upgrade your permissions.",
|
||||
"Someone else is already recording a voice broadcast. Wait for their voice broadcast to end to start a new one.": "Someone else is already recording a voice broadcast. Wait for their voice broadcast to end to start a new one.",
|
||||
"Stop live broadcasting?": "Stop live broadcasting?",
|
||||
"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.": "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.",
|
||||
"Yes, stop broadcast": "Yes, stop broadcast",
|
||||
|
@ -935,6 +939,7 @@
|
|||
"New session manager": "New session manager",
|
||||
"Have greater visibility and control over all your sessions.": "Have greater visibility and control over all your sessions.",
|
||||
"Our new sessions manager provides better visibility of all your sessions, and greater control over them including the ability to remotely toggle push notifications.": "Our new sessions manager provides better visibility of all your sessions, and greater control over them including the ability to remotely toggle push notifications.",
|
||||
"Allow a QR code to be shown in session manager to sign in another device (requires compatible homeserver)": "Allow a QR code to be shown in session manager to sign in another device (requires compatible homeserver)",
|
||||
"Font size": "Font size",
|
||||
"Use custom size": "Use custom size",
|
||||
"Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing",
|
||||
|
@ -1742,7 +1747,6 @@
|
|||
"Rename session": "Rename session",
|
||||
"Please be aware that session names are also visible to people you communicate with": "Please be aware that session names are also visible to people you communicate with",
|
||||
"Session ID": "Session ID",
|
||||
"Client": "Client",
|
||||
"Last activity": "Last activity",
|
||||
"Application": "Application",
|
||||
"Version": "Version",
|
||||
|
@ -1750,6 +1754,7 @@
|
|||
"Device": "Device",
|
||||
"Model": "Model",
|
||||
"Operating system": "Operating system",
|
||||
"Browser": "Browser",
|
||||
"IP address": "IP address",
|
||||
"Session details": "Session details",
|
||||
"Toggle push notifications on this session.": "Toggle push notifications on this session.",
|
||||
|
@ -1788,6 +1793,9 @@
|
|||
"Filter devices": "Filter devices",
|
||||
"Show": "Show",
|
||||
"%(selectedDeviceCount)s sessions selected": "%(selectedDeviceCount)s sessions selected",
|
||||
"Sign in with QR code": "Sign in with QR code",
|
||||
"You can use this device to sign in a new device with a QR code. You will need to scan the QR code shown on this device with your device that's signed out.": "You can use this device to sign in a new device with a QR code. You will need to scan the QR code shown on this device with your device that's signed out.",
|
||||
"Show QR code": "Show QR code",
|
||||
"Security recommendations": "Security recommendations",
|
||||
"Improve your account security by following these recommendations": "Improve your account security by following these recommendations",
|
||||
"View all": "View all",
|
||||
|
@ -3181,6 +3189,26 @@
|
|||
"Submit": "Submit",
|
||||
"Something went wrong in confirming your identity. Cancel and try again.": "Something went wrong in confirming your identity. Cancel and try again.",
|
||||
"Start authentication": "Start authentication",
|
||||
"Sign in new device": "Sign in new device",
|
||||
"The linking wasn't completed in the required time.": "The linking wasn't completed in the required time.",
|
||||
"The scanned code is invalid.": "The scanned code is invalid.",
|
||||
"Linking with this device is not supported.": "Linking with this device is not supported.",
|
||||
"The request was declined on the other device.": "The request was declined on the other device.",
|
||||
"The other device is already signed in.": "The other device is already signed in.",
|
||||
"The other device isn't signed in.": "The other device isn't signed in.",
|
||||
"The request was cancelled.": "The request was cancelled.",
|
||||
"An unexpected error occurred.": "An unexpected error occurred.",
|
||||
"The homeserver doesn't support signing in another device.": "The homeserver doesn't support signing in another device.",
|
||||
"Devices connected": "Devices connected",
|
||||
"Check that the code below matches with your other device:": "Check that the code below matches with your other device:",
|
||||
"By approving access for this device, it will have full access to your account.": "By approving access for this device, it will have full access to your account.",
|
||||
"Scan the QR code below with your device that's signed out.": "Scan the QR code below with your device that's signed out.",
|
||||
"Start at the sign in screen": "Start at the sign in screen",
|
||||
"Select 'Scan QR code'": "Select 'Scan QR code'",
|
||||
"Review and approve the sign in": "Review and approve the sign in",
|
||||
"Connecting...": "Connecting...",
|
||||
"Waiting for device to sign in": "Waiting for device to sign in",
|
||||
"Completing set up of your new device": "Completing set up of your new device",
|
||||
"Enter password": "Enter password",
|
||||
"Nice, strong password!": "Nice, strong password!",
|
||||
"Password is allowed, but unsafe": "Password is allowed, but unsafe",
|
||||
|
|
|
@ -3549,5 +3549,83 @@
|
|||
"Empty room (was %(oldName)s)": "Sala vacía (antes era %(oldName)s)",
|
||||
"%(user)s and %(count)s others|one": "%(user)s y 1 más",
|
||||
"%(user)s and %(count)s others|other": "%(user)s y %(count)s más",
|
||||
"%(user1)s and %(user2)s": "%(user1)s y %(user2)s"
|
||||
"%(user1)s and %(user2)s": "%(user1)s y %(user2)s",
|
||||
"Spotlight": "Spotlight",
|
||||
"Your server lacks native support, you must specify a proxy": "Tu servidor no es compatible, debes configurar un intermediario (proxy)",
|
||||
"View chat timeline": "Ver historial del chat",
|
||||
"You do not have permission to start voice calls": "No tienes permiso para iniciar llamadas de voz",
|
||||
"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.",
|
||||
"Please be aware that session names are also visible to people you communicate with": "Ten en cuenta que cualquiera con quien te comuniques puede ver los nombres de las sesiones",
|
||||
"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",
|
||||
"Enable notifications for this device": "Activar notificaciones en este dispositivo",
|
||||
"Turn off to disable notifications on all your devices and sessions": "Desactiva para no recibir notificaciones en todos tus dispositivos y sesiones",
|
||||
"You need to be able to kick users to do that.": "Debes poder sacar usuarios para hacer eso.",
|
||||
"Video call started in %(roomName)s. (not supported by this browser)": "Videollamada empezada en %(roomName)s. (no compatible con este navegador)",
|
||||
"Video call started in %(roomName)s.": "Videollamada empezada en %(roomName)s.",
|
||||
"Layout type": "Tipo de disposición",
|
||||
"%(downloadButton)s or %(copyButton)s": "%(downloadButton)s o %(copyButton)s",
|
||||
"%(securityKey)s or %(recoveryFile)s": "%(securityKey)s o %(recoveryFile)s",
|
||||
"Proxy URL": "URL de servidor proxy",
|
||||
"Proxy URL (optional)": "URL de servidor proxy (opcional)",
|
||||
"To disable you will need to log out and back in, use with caution!": "Para desactivarlo, tendrás que cerrar sesión y volverla a iniciar. ¡Ten cuidado!",
|
||||
"Sliding Sync configuration": "Configuración de la sincronización progresiva",
|
||||
"Your server lacks native support": "Tu servidor no es compatible",
|
||||
"Your server has native support": "Tu servidor es compatible",
|
||||
"Checking...": "Comprobando…",
|
||||
"%(qrCode)s or %(appLinks)s": "%(qrCode)s o %(appLinks)s",
|
||||
"Video call ended": "Videollamada terminada",
|
||||
"%(name)s started a video call": "%(name)s comenzó una videollamada",
|
||||
"%(qrCode)s or %(emojiCompare)s": "%(qrCode)s o %(emojiCompare)s",
|
||||
"Room info": "Info. de la sala",
|
||||
"Underline": "Subrayado",
|
||||
"Italic": "Cursiva",
|
||||
"Close call": "Terminar llamada",
|
||||
"Freedom": "Libertad",
|
||||
"There's no one here to call": "No hay nadie a quien llamar aquí",
|
||||
"You do not have permission to start video calls": "No tienes permiso para empezar videollamadas",
|
||||
"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",
|
||||
"Desktop session": "Sesión de escritorio",
|
||||
"Toggle push notifications on this session.": "Activar/desactivar notificaciones push en esta sesión.",
|
||||
"Push notifications": "Notificaciones push",
|
||||
"Operating system": "Sistema operativo",
|
||||
"Model": "Modelo",
|
||||
"URL": "URL",
|
||||
"Version": "Versión",
|
||||
"Application": "Aplicación",
|
||||
"Client": "Cliente",
|
||||
"Rename session": "Renombrar sesión",
|
||||
"Call type": "Tipo de llamada",
|
||||
"Join %(brand)s calls": "Unirte a llamadas de %(brand)s",
|
||||
"Start %(brand)s calls": "Empezar llamadas de %(brand)s",
|
||||
"Voice broadcasts": "Retransmisiones de voz",
|
||||
"Enable notifications for this account": "Activar notificaciones para esta cuenta",
|
||||
"Fill screen": "Llenar la pantalla",
|
||||
"You have already joined this call from another device": "Ya te has unido a la llamada desde otro dispositivo",
|
||||
"Sorry — this call is currently full": "Lo sentimos — la llamada está llena",
|
||||
"Use new session manager": "Usar el nuevo gestor de sesiones",
|
||||
"New session manager": "Nuevo gestor de sesiones",
|
||||
"Voice broadcast (under active development)": "Retransmisión de voz (en desarrollo)",
|
||||
"New group call experience": "Nueva experiencia de llamadas grupales",
|
||||
"Element Call video rooms": "Salas de vídeo Element Call",
|
||||
"Sliding Sync mode (under active development, cannot be disabled)": "Modo de sincronización progresiva (en pleno desarrollo, no puede desactivarse)",
|
||||
"Try out the rich text editor (plain text mode coming soon)": "Prueba el nuevo editor de texto con formato (un modo sin formato estará disponible próximamente)",
|
||||
"Notifications silenced": "Notificaciones silenciadas",
|
||||
"Video call started": "Videollamada iniciada",
|
||||
"Unknown room": "Sala desconocida",
|
||||
"Voice broadcast": "Retransmisión de voz",
|
||||
"stop voice broadcast": "parar retransmisión de voz",
|
||||
"resume voice broadcast": "reanudar retransmisión de voz",
|
||||
"pause voice broadcast": "pausar retransmisión de voz",
|
||||
"Live": "En directo"
|
||||
}
|
||||
|
|
|
@ -3587,5 +3587,49 @@
|
|||
"Push notifications": "Tõuketeavitused",
|
||||
"Toggle push notifications on this session.": "Lülita tõuketeavitused selles sessioonis sisse/välja.",
|
||||
"Enable notifications for this device": "Võta teavitused selles seadmes kasutusele",
|
||||
"Turn off to disable notifications on all your devices and sessions": "Välja lülitades keelad teavitused kõikides oma seadmetes ja sessioonides"
|
||||
"Turn off to disable notifications on all your devices and sessions": "Välja lülitades keelad teavitused kõikides oma seadmetes ja sessioonides",
|
||||
"Room info": "Jututoa teave",
|
||||
"View chat timeline": "Vaata vestluse ajajoont",
|
||||
"Close call": "Lõpeta kõne",
|
||||
"Layout type": "Kujunduse tüüp",
|
||||
"Spotlight": "Rambivalgus",
|
||||
"Freedom": "Vabadus",
|
||||
"Unknown session type": "Tundmatu sessioonitüüp",
|
||||
"Web session": "Veebirakendus",
|
||||
"Mobile session": "Nutirakendus",
|
||||
"Desktop session": "Töölauarakendus",
|
||||
"URL": "URL",
|
||||
"Version": "Versioon",
|
||||
"Application": "Rakendus",
|
||||
"Fill screen": "Täida ekraan",
|
||||
"Record the client name, version, and url to recognise sessions more easily in session manager": "Sessioonide paremaks tuvastamiseks saad nüüd sessioonihalduris salvestada klientrakenduse nime, versiooni ja aadressi",
|
||||
"Video call started": "Videokõne algas",
|
||||
"Unknown room": "Teadmata jututuba",
|
||||
"Live": "Otseeeter",
|
||||
"Video call started in %(roomName)s. (not supported by this browser)": "Videokõne algas %(roomName)s jututoas. (ei ole selles brauseris toetatud)",
|
||||
"Video call started in %(roomName)s.": "Videokõne algas %(roomName)s jututoas.",
|
||||
"Video call (%(brand)s)": "Videokõne (%(brand)s)",
|
||||
"Operating system": "Operatsioonisüsteem",
|
||||
"Model": "Mudel",
|
||||
"Client": "Klient",
|
||||
"Call type": "Kõne tüüp",
|
||||
"You do not have sufficient permissions to change this.": "Sul pole piisavalt õigusi selle muutmiseks.",
|
||||
"%(brand)s is end-to-end encrypted, but is currently limited to smaller numbers of users.": "%(brand)s kasutab läbivat krüptimist, kuid on hetkel piiratud väikese osalejate arvuga ühes kõnes.",
|
||||
"Enable %(brand)s as an additional calling option in this room": "Võta kasutusele %(brand)s kui lisavõimalus kõnedeks selles jututoas",
|
||||
"Join %(brand)s calls": "Liitu %(brand)s kõnedega",
|
||||
"Start %(brand)s calls": "Alusta helistamist %(brand)s abil",
|
||||
"Sorry — this call is currently full": "Vabandust, selles kõnes ei saa rohkem osalejaid olla",
|
||||
"stop voice broadcast": "lõpeta ringhäälingukõne",
|
||||
"resume voice broadcast": "jätka ringhäälingukõnet",
|
||||
"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",
|
||||
"You have already joined this call from another device": "Sa oled selle kõnega juba ühest teisest seadmest liitunud",
|
||||
"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",
|
||||
"Use new session manager": "Kasuta uut sessioonihaldurit",
|
||||
"Try out the rich text editor (plain text mode coming soon)": "Proovi vormindatud teksti alusel töötavat tekstitoimetit (varsti lisandub ka vormindamata teksti režiim)",
|
||||
"Notifications silenced": "Teavitused on summutatud"
|
||||
}
|
||||
|
|
|
@ -2502,5 +2502,12 @@
|
|||
"Jump to the given date in the timeline": "پرش به تاریخ تعیین شده در جدول زمانی",
|
||||
"Failed to invite users to %(roomName)s": "افزودن کاربران به %(roomName)s با شکست روبرو شد",
|
||||
"You're trying to access a community link (%(groupId)s).<br/>Communities are no longer supported and have been replaced by spaces.<br2/><a>Learn more about spaces here.</a>": "شما قصد دسترسی به لینک انجمن%(groupId)s را دارید. <br/> انجمن ها دیگر پشتیبانی نمی شوند و با فضاها جایگزین شده اند.<br2/><a> در مورد فضا بیشتر بدانید</a>",
|
||||
"That link is no longer supported": "لینک موردنظر دیگر پشتیبانی نمی شود"
|
||||
"That link is no longer supported": "لینک موردنظر دیگر پشتیبانی نمی شود",
|
||||
"Inviting %(user)s and %(count)s others|other": "دعوت کردن %(user)s و %(count)s دیگر",
|
||||
"Video call started in %(roomName)s. (not supported by this browser)": "تماس ویدئویی در %(roomName)s شروع شد. (توسط این مرورگر پشتیبانی نمیشود.)",
|
||||
"Video call started in %(roomName)s.": "تماس ویدئویی در %(roomName)s شروع شد.",
|
||||
"No virtual room for this room": "اتاق مجازی برای این اتاق وجود ندارد",
|
||||
"Switches to this room's virtual room, if it has one": "جابجایی به اتاق مجازی این اتاق، اگر یکی وجود داشت",
|
||||
"You need to be able to kick users to do that.": "برای انجام این کار نیاز دارید که بتوانید کاربران را حذف کنید.",
|
||||
"Empty room (was %(oldName)s)": "اتاق خالی (نام قبلی: %(oldName)s)"
|
||||
}
|
||||
|
|
|
@ -3589,5 +3589,47 @@
|
|||
"Enable notifications for this account": "Activer les notifications pour ce compte",
|
||||
"Video call ended": "Appel vidéo terminé",
|
||||
"%(name)s started a video call": "%(name)s a démarré un appel vidéo",
|
||||
"Record the client name, version, and url to recognise sessions more easily in session manager": "Enregistrez le nom, la version et l'URL du client afin de reconnaitre les sessions plus facilement dans le gestionnaire de sessions"
|
||||
"Record the client name, version, and url to recognise sessions more easily in session manager": "Enregistrez le nom, la version et l'URL du client afin de reconnaitre les sessions plus facilement dans le gestionnaire de sessions",
|
||||
"Version": "Version",
|
||||
"Application": "Application",
|
||||
"URL": "URL",
|
||||
"Unknown session type": "Type de session inconnu",
|
||||
"Web session": "session internet",
|
||||
"Mobile session": "Session de téléphone portable",
|
||||
"Desktop session": "Session de bureau",
|
||||
"Video call started": "Appel vidéo commencé",
|
||||
"Unknown room": "Salon inconnu",
|
||||
"Video call started in %(roomName)s. (not supported by this browser)": "Appel vidéo commencé dans %(roomName)s. (non supporté par ce navigateur)",
|
||||
"Video call started in %(roomName)s.": "Appel vidéo commencé dans %(roomName)s.",
|
||||
"Close call": "Terminer l’appel",
|
||||
"Layout type": "Type de mise en page",
|
||||
"Spotlight": "Projecteur",
|
||||
"Freedom": "Liberté",
|
||||
"Fill screen": "Remplir l’écran",
|
||||
"Room info": "Information du salon",
|
||||
"View chat timeline": "Afficher la chronologie du chat",
|
||||
"Video call (%(brand)s)": "Appel vidéo (%(brand)s)",
|
||||
"Operating system": "Système d’exploitation",
|
||||
"Model": "Modèle",
|
||||
"Client": "Client",
|
||||
"Call type": "Type d’appel",
|
||||
"You do not have sufficient permissions to change this.": "Vous n’avez pas assez de permissions pour changer ceci.",
|
||||
"%(brand)s is end-to-end encrypted, but is currently limited to smaller numbers of users.": "%(brand)s est chiffré de bout en bout, mais n’est actuellement utilisable qu’avec un petit nombre d’utilisateurs.",
|
||||
"Enable %(brand)s as an additional calling option in this room": "Activer %(brand)s comme une option supplémentaire d’appel dans ce salon",
|
||||
"Join %(brand)s calls": "Rejoindre des appels %(brand)s",
|
||||
"Start %(brand)s calls": "Démarrer des appels %(brand)s",
|
||||
"Sorry — this call is currently full": "Désolé — Cet appel est actuellement complet",
|
||||
"Our new sessions manager provides better visibility of all your sessions, and greater control over them including the ability to remotely toggle push notifications.": "Notre nouveau gestionnaire de sessions fournit une meilleure visibilité sur toutes vos sessions, et un plus grand contrôle sur ces dernières avec la possibilité de désactiver à distance les notifications push.",
|
||||
"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",
|
||||
"Wysiwyg composer (plain text mode coming soon) (under active development)": "Compositeur Wysiwyg (le mode texte brut arrive prochainement) (en cours de développement)",
|
||||
"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é",
|
||||
"Italic": "Italique",
|
||||
"You have already joined this call from another device": "Vous avez déjà rejoint cet appel depuis un autre appareil",
|
||||
"Try out the rich text editor (plain text mode coming soon)": "Essayer l’éditeur de texte formaté (le mode texte brut arrive bientôt)",
|
||||
"stop voice broadcast": "arrêter la diffusion audio"
|
||||
}
|
||||
|
|
|
@ -2774,5 +2774,6 @@
|
|||
"Start messages with <code>/plain</code> to send without markdown and <code>/md</code> to send with.": "התחילו הודעות עם <code>/plain</code> לשליחה ללא סימון ו-<code>/md</code> לשליחה.",
|
||||
"Get notified only with mentions and keywords as set up in your <a>settings</a>": "קבלו התראה רק עם אזכורים ומילות מפתח כפי שהוגדרו ב<a>הגדרות</a> שלכם",
|
||||
"New keyword": "מילת מפתח חדשה",
|
||||
"Keyword": "מילת מפתח"
|
||||
"Keyword": "מילת מפתח",
|
||||
"Empty room": "חדר ריק"
|
||||
}
|
||||
|
|
|
@ -3588,5 +3588,48 @@
|
|||
"Turn off to disable notifications on all your devices and sessions": "Kikapcsolva az eszközökön és munkamenetekben az értesítések tiltva lesznek",
|
||||
"Enable notifications for this account": "Értesítések engedélyezése ehhez a fiókhoz",
|
||||
"New group call experience": "Új konferenciahívás élmény",
|
||||
"Live": "Élő"
|
||||
"Live": "Élő",
|
||||
"Join %(brand)s calls": "Csatlakozás ebbe a hívásba: %(brand)s",
|
||||
"Start %(brand)s calls": "%(brand)s hívás indítása",
|
||||
"Fill screen": "Képernyő kitöltése",
|
||||
"You have already joined this call from another device": "Már csatlakozott ehhez a híváshoz egy másik eszközön",
|
||||
"Sorry — this call is currently full": "Bocsánat — ez a hívás betelt",
|
||||
"Record the client name, version, and url to recognise sessions more easily in session manager": "Kliens neve, verziója és url felvétele a munkamenet könnyebb azonosításához a munkamenet kezelőben",
|
||||
"Our new sessions manager provides better visibility of all your sessions, and greater control over them including the ability to remotely toggle push notifications.": "Az új munkamenet kezelő jobb rálátást biztosít a munkamenetekre és jobb felügyeletet beleértve, hogy távolról ki-, bekapcsolhatóak a „push” értesítések.",
|
||||
"Have greater visibility and control over all your sessions.": "Jobb áttekintés és felügyelet a munkamenetek felett.",
|
||||
"New session manager": "Új munkamenet kezelő",
|
||||
"Use new session manager": "Új munkamenet kezelő használata",
|
||||
"Try out the rich text editor (plain text mode coming soon)": "Próbálja ki az új szövegbevitelt (hamarosan érkezik a sima szöveges üzemmód)",
|
||||
"Video call started": "Videó hívás elindult",
|
||||
"Unknown room": "Ismeretlen szoba",
|
||||
"stop voice broadcast": "hang közvetítés beállítása",
|
||||
"resume voice broadcast": "hang közvetítés folytatása",
|
||||
"pause voice broadcast": "hang közvetítés szüneteltetése",
|
||||
"Video call started in %(roomName)s. (not supported by this browser)": "Videó hívás indult itt: %(roomName)s. (ebben a böngészőben ez nem támogatott)",
|
||||
"Video call started in %(roomName)s.": "Videó hívás indult itt: %(roomName)s.",
|
||||
"Room info": "Szoba információ",
|
||||
"Underline": "Aláhúzott",
|
||||
"Italic": "Dőlt",
|
||||
"View chat timeline": "Beszélgetés idővonal megjelenítése",
|
||||
"Close call": "Hívás befejezése",
|
||||
"Layout type": "Kinézet típusa",
|
||||
"Spotlight": "Reflektor",
|
||||
"Freedom": "Szabadság",
|
||||
"Video call (%(brand)s)": "Videó hívás (%(brand)s)",
|
||||
"Unknown session type": "Ismeretlen munkamenet típus",
|
||||
"Web session": "Webes munkamenet",
|
||||
"Mobile session": "Mobil munkamenet",
|
||||
"Desktop session": "Asztali munkamenet",
|
||||
"Operating system": "Operációs rendszer",
|
||||
"Model": "Modell",
|
||||
"URL": "URL",
|
||||
"Version": "Verzió",
|
||||
"Application": "Alkalmazás",
|
||||
"Client": "Kliens",
|
||||
"Sign out all other sessions": "Kijelentkezés minden más munkamenetből",
|
||||
"Call type": "Hívás típusa",
|
||||
"You do not have sufficient permissions to change this.": "Nincs megfelelő jogosultság a megváltoztatáshoz.",
|
||||
"%(brand)s is end-to-end encrypted, but is currently limited to smaller numbers of users.": "%(brand)s végpontok között titkosított de jelenleg csak kevés számú résztvevővel működik.",
|
||||
"Enable %(brand)s as an additional calling option in this room": "%(brand)s engedélyezése mint további opció hívásokhoz a szobában",
|
||||
"Notifications silenced": "Értesítések elnémítva"
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
"Favourites": "Favorit",
|
||||
"Import": "Impor",
|
||||
"Incorrect verification code": "Kode verifikasi tidak benar",
|
||||
"Invalid Email Address": "Alamat Email Tidak Valid",
|
||||
"Invalid Email Address": "Alamat Email Tidak Absah",
|
||||
"Invited": "Diundang",
|
||||
"Sign in with": "Masuk dengan",
|
||||
"Leave room": "Tinggalkan ruangan",
|
||||
|
@ -255,7 +255,7 @@
|
|||
"Unbans user with given ID": "Menhilangkan cekalan pengguna dengan ID yang dicantumkan",
|
||||
"Joins room with given address": "Bergabung ke ruangan dengan alamat yang dicantumkan",
|
||||
"Use an identity server to invite by email. Manage in Settings.": "Gunakan server identitas untuk mengundang melalui email. Kelola di Pengaturan.",
|
||||
"Use an identity server to invite by email. Click continue to use the default identity server (%(defaultIdentityServerName)s) or manage in Settings.": "Gunakan server identitas untuk mengundang melalui email. Klik lanjutkan untuk menggunakan server identitas default (%(defaultIdentityServerName)s) atau kelola di Pengaturan.",
|
||||
"Use an identity server to invite by email. Click continue to use the default identity server (%(defaultIdentityServerName)s) or manage in Settings.": "Gunakan server identitas untuk mengundang melalui email. Klik lanjutkan untuk menggunakan server identitas bawaan (%(defaultIdentityServerName)s) atau kelola di Pengaturan.",
|
||||
"Use an identity server": "Gunakan sebuah server identitias",
|
||||
"Invites user with given id to current room": "Mengundang pengguna dengan ID yang dicantumkan ke ruangan saat ini",
|
||||
"Sets the room name": "Mengatur nama ruangan",
|
||||
|
@ -563,7 +563,7 @@
|
|||
"We couldn't log you in": "Kami tidak dapat memasukkan Anda",
|
||||
"Trust": "Percayakan",
|
||||
"Only continue if you trust the owner of the server.": "Hanya lanjutkan jika Anda mempercayai pemilik server ini.",
|
||||
"This action requires accessing the default identity server <server /> to validate an email address or phone number, but the server does not have any terms of service.": "Aksi ini memerlukan mengakses server identitas bawaan <server /> untuk memvalidasi sebuah alamat email atau nomor telepon, tetapi server ini tidak memiliki syarat layanan apapun.",
|
||||
"This action requires accessing the default identity server <server /> to validate an email address or phone number, but the server does not have any terms of service.": "Aksi ini memerlukan mengakses server identitas bawaan <server /> untuk memvalidasi sebuah alamat email atau nomor telepon, tetapi server ini tidak memiliki syarat layanan apa pun.",
|
||||
"Identity server has no terms of service": "Identitas server ini tidak memiliki syarat layanan",
|
||||
"Unnamed Room": "Ruangan Tanpa Nama",
|
||||
"%(date)s at %(time)s": "%(date)s pada %(time)s",
|
||||
|
@ -870,7 +870,7 @@
|
|||
"Keep going...": "Lanjutkan...",
|
||||
"Email (optional)": "Email (opsional)",
|
||||
"Enable encryption?": "Aktifkan enkripsi?",
|
||||
"Notify everyone": "Beritahu semua",
|
||||
"Notify everyone": "Beri tahu semua",
|
||||
"Ban users": "Cekal pengguna",
|
||||
"Change settings": "Ubah pengaturan",
|
||||
"Invite users": "Undang pengguna",
|
||||
|
@ -947,7 +947,7 @@
|
|||
"Reject invitation": "Tolak undangan",
|
||||
"Confirm Removal": "Konfirmasi Penghapusan",
|
||||
"Unknown Address": "Alamat Tidak Dikenal",
|
||||
"Invalid file%(extra)s": "File tidak valid%(extra)s",
|
||||
"Invalid file%(extra)s": "File tidak absah%(extra)s",
|
||||
"not specified": "tidak ditentukan",
|
||||
"Start chat": "Mulai obrolan",
|
||||
"Join Room": "Bergabung ke Ruangan",
|
||||
|
@ -1064,7 +1064,7 @@
|
|||
"%(senderName)s updated a ban rule matching %(glob)s for %(reason)s": "%(senderName)s memperbarui sebuah peraturan pencekalan yang berisi %(glob)s untuk %(reason)s",
|
||||
"%(senderName)s updated the rule banning servers matching %(glob)s for %(reason)s": "%(senderName)s memperbarui peraturan pencekalan server yang berisi %(glob)s untuk %(reason)s",
|
||||
"%(senderName)s updated the rule banning rooms matching %(glob)s for %(reason)s": "%(senderName)s memperbarui peraturan pencekalan ruangan yang berisi %(glob)s untuk %(reason)s",
|
||||
"%(senderName)s updated an invalid ban rule": "%(senderName)s memperbarui sebuah peraturan pencekalan yang tidak valid",
|
||||
"%(senderName)s updated an invalid ban rule": "%(senderName)s memperbarui sebuah peraturan pencekalan yang tidak absah",
|
||||
"%(senderName)s removed a ban rule matching %(glob)s": "%(senderName)s menghapus sebuah peraturan pencekalan yang berisi %(glob)s",
|
||||
"%(senderName)s removed the rule banning servers matching %(glob)s": "%(senderName)s menghapus peraturan pencekalan server yang berisi %(glob)s",
|
||||
"%(senderName)s removed the rule banning rooms matching %(glob)s": "%(senderName)s menghapus peraturan pencekalan ruangan yang berisi %(glob)s",
|
||||
|
@ -1144,7 +1144,7 @@
|
|||
"Can't leave Server Notices room": "Tidak dapat meninggalkan ruangan Pemberitahuan Server",
|
||||
"Unexpected server error trying to leave the room": "Kesalahan server yang tidak terduga saat mencoba untuk meninggalkan ruangannya",
|
||||
"Authentication check failed: incorrect password?": "Pemeriksaan otentikasi gagal: kata sandi salah?",
|
||||
"Not a valid %(brand)s keyfile": "Bukan keyfile %(brand)s yang valid",
|
||||
"Not a valid %(brand)s keyfile": "Bukan keyfile %(brand)s yang absah",
|
||||
"Your browser does not support the required cryptography extensions": "Browser Anda tidak mendukung ekstensi kriptografi yang dibutuhkan",
|
||||
"%(name)s (%(userId)s)": "%(name)s (%(userId)s)",
|
||||
"%(num)s days from now": "%(num)s hari dari sekarang",
|
||||
|
@ -1221,11 +1221,11 @@
|
|||
"Disconnect from the identity server <idserver />?": "Putuskan hubungan dari server identitas <idserver />?",
|
||||
"Disconnect identity server": "Putuskan hubungan server identitas",
|
||||
"The identity server you have chosen does not have any terms of service.": "Server identitas yang Anda pilih tidak memiliki persyaratan layanan.",
|
||||
"Terms of service not accepted or the identity server is invalid.": "Persyaratan layanan tidak diterima atau server identitasnya tidak valid.",
|
||||
"Terms of service not accepted or the identity server is invalid.": "Persyaratan layanan tidak diterima atau server identitasnya tidak absah.",
|
||||
"Disconnect from the identity server <current /> and connect to <new /> instead?": "Putuskan hubungan dari server identitas <current /> dan hubungkan ke <new />?",
|
||||
"Change identity server": "Ubah server identitas",
|
||||
"Could not connect to identity server": "Tidak dapat menghubung ke server identitas",
|
||||
"Not a valid identity server (status code %(code)s)": "Bukan server identitas yang valid (kode status %(code)s)",
|
||||
"Not a valid identity server (status code %(code)s)": "Bukan server identitas yang absah (kode status %(code)s)",
|
||||
"Identity server URL must be HTTPS": "URL server identitas harus HTTPS",
|
||||
"not ready": "belum siap",
|
||||
"Secret storage:": "Penyimpanan rahasia:",
|
||||
|
@ -1242,16 +1242,16 @@
|
|||
"Backup version:": "Versi cadangan:",
|
||||
"This backup is trusted because it has been restored on this session": "Cadangan ini dipercayai karena telah dipulihkan di sesi ini",
|
||||
"Backup is not signed by any of your sessions": "Cadangan tidak ditandatangani oleh sesi-sesi Anda",
|
||||
"Backup has an <validity>invalid</validity> signature from <verify>unverified</verify> session <device></device>": "Cadangan mempunyai tanda tangan yang <validity>tidak valid</validity> dari sesi yang <verify>tidak diverifikasi</verify> <device></device>",
|
||||
"Backup has an <validity>invalid</validity> signature from <verify>verified</verify> session <device></device>": "Cadangan mempunyai tanda tangan yang <validity>tidak valid</validity> dari sesi yang <verify>terverifikasi</verify> <device></device>",
|
||||
"Backup has a <validity>valid</validity> signature from <verify>unverified</verify> session <device></device>": "Cadangan mempunyai tanda tangan yang <validity>valid</validity> dari sesi yang <verify>belum diverifikasi</verify> <device></device>",
|
||||
"Backup has a <validity>valid</validity> signature from <verify>verified</verify> session <device></device>": "Cadangan mempunyai tanda tangan yang <validity>valid</validity> dari sesi yang <verify>terverifikasi</verify> <device></device>",
|
||||
"Backup has an <validity>invalid</validity> signature from this session": "Cadangan mempunyai tanda tangan yang <validity>tidak valid</validity> dari sesi ini",
|
||||
"Backup has a <validity>valid</validity> signature from this session": "Cadangan mempunyai tanda tangan yang <validity>valid</validity> dari sesi ini",
|
||||
"Backup has an <validity>invalid</validity> signature from <verify>unverified</verify> session <device></device>": "Cadangan mempunyai tanda tangan yang <validity>tidak absah</validity> dari sesi yang <verify>tidak diverifikasi</verify> <device></device>",
|
||||
"Backup has an <validity>invalid</validity> signature from <verify>verified</verify> session <device></device>": "Cadangan mempunyai tanda tangan yang <validity>tidak absah</validity> dari sesi yang <verify>terverifikasi</verify> <device></device>",
|
||||
"Backup has a <validity>valid</validity> signature from <verify>unverified</verify> session <device></device>": "Cadangan mempunyai tanda tangan yang <validity>absah</validity> dari sesi yang <verify>belum diverifikasi</verify> <device></device>",
|
||||
"Backup has a <validity>valid</validity> signature from <verify>verified</verify> session <device></device>": "Cadangan mempunyai tanda tangan yang <validity>absah</validity> dari sesi yang <verify>terverifikasi</verify> <device></device>",
|
||||
"Backup has an <validity>invalid</validity> signature from this session": "Cadangan mempunyai tanda tangan yang <validity>tidak absah</validity> dari sesi ini",
|
||||
"Backup has a <validity>valid</validity> signature from this session": "Cadangan mempunyai tanda tangan yang <validity>absah</validity> dari sesi ini",
|
||||
"Backup has a signature from <verify>unknown</verify> session with ID %(deviceId)s": "Cadangan mempunyai tanda tangan dari <verify>sesi tidak dikenal</verify> dengan ID %(deviceId)s",
|
||||
"Backup has a signature from <verify>unknown</verify> user with ID %(deviceId)s": "Cadangan mempunyai tanda tangan dari <verify>seseorang tidak dikenal</verify> dengan ID %(deviceId)s",
|
||||
"Backup has a <validity>invalid</validity> signature from this user": "Cadangan mempunyai tanda tangan yang <validity>tidak valid</validity> dari pengguna ini",
|
||||
"Backup has a <validity>valid</validity> signature from this user": "Cadangan mempunyai tanda tangan yang <validity>valid</validity> dari pengguna ini",
|
||||
"Backup has a <validity>invalid</validity> signature from this user": "Cadangan mempunyai tanda tangan yang <validity>tidak absah</validity> dari pengguna ini",
|
||||
"Backup has a <validity>valid</validity> signature from this user": "Cadangan mempunyai tanda tangan yang <validity>absah</validity> dari pengguna ini",
|
||||
"All keys backed up": "Semua kunci telah dicadangkan",
|
||||
"Backing up %(sessionsRemaining)s keys...": "Mencadangkan %(sessionsRemaining)s kunci...",
|
||||
"Connect this session to Key Backup": "Hubungkan sesi ini ke Pencadangan Kunci",
|
||||
|
@ -1284,7 +1284,7 @@
|
|||
"Loading new room": "Memuat ruangan baru",
|
||||
"Upgrading room": "Meningkatkan ruangan",
|
||||
"This upgrade will allow members of selected spaces access to this room without an invite.": "Peningkatan ini akan mengizinkan anggota di space yang terpilih untuk dapat mengakses ruangan ini tanpa sebuah undangan.",
|
||||
"This room is in some spaces you're not an admin of. In those spaces, the old room will still be shown, but people will be prompted to join the new one.": "Ruangan ini masih ada di dalam space yang Anda bukan admin di sana. Di space-space itu, ruangan yang lama masih terlihat, tetapi orang-orang akan diberitahu untuk bergabung ke ruangan yang baru.",
|
||||
"This room is in some spaces you're not an admin of. In those spaces, the old room will still be shown, but people will be prompted to join the new one.": "Ruangan ini masih ada di dalam space yang Anda bukan admin di sana. Di space itu, ruangan yang lama masih terlihat, tetapi orang akan diberi tahu untuk bergabung ke ruangan yang baru.",
|
||||
"Space members": "Anggota space",
|
||||
"Anyone in a space can find and join. You can select multiple spaces.": "Siapa saja di sebuah space dapat menemukan dan bergabung. Anda dapat memilih beberapa space.",
|
||||
"Anyone in <spaceName/> can find and join. You can select other spaces too.": "Siapa saja di <spaceName/> dapat menemukan dan bergabung. Anda juga dapat memilih space yang lain.",
|
||||
|
@ -1332,7 +1332,7 @@
|
|||
"Your homeserver does not support device management.": "Homeserver Anda tidak mendukung pengelolaan perangkat.",
|
||||
"Session key:": "Kunci sesi:",
|
||||
"Session ID:": "ID Sesi:",
|
||||
"Import E2E room keys": "Impor kunci enkripsi ujung-ke-ujung",
|
||||
"Import E2E room keys": "Impor kunci enkripsi ujung ke ujung",
|
||||
"Homeserver feature support:": "Dukungan fitur homeserver:",
|
||||
"Self signing private key:": "Kunci privat penandatanganan diri:",
|
||||
"User signing private key:": "Kunci rahasia penandatanganan pengguna:",
|
||||
|
@ -1352,7 +1352,7 @@
|
|||
"Your homeserver does not support cross-signing.": "Homeserver Anda tidak mendukung penandatanganan silang.",
|
||||
"Passwords don't match": "Kata sandi tidak cocok",
|
||||
"Do you want to set an email address?": "Apakah Anda ingin menetapkan sebuah alamat email?",
|
||||
"Export E2E room keys": "Ekspor kunci ruangan enkripsi ujung-ke-ujung",
|
||||
"Export E2E room keys": "Ekspor kunci ruangan enkripsi ujung ke ujung",
|
||||
"No display name": "Tidak ada nama tampilan",
|
||||
"Failed to upload profile picture!": "Gagal untuk mengunggah foto profil!",
|
||||
"Channel: <channelLink/>": "Saluran: <channelLink/>",
|
||||
|
@ -1403,7 +1403,7 @@
|
|||
"Accept <policyLink /> to continue:": "Terima <policyLink /> untuk melanjutkan:",
|
||||
"Decline (%(counter)s)": "Tolak (%(counter)s)",
|
||||
"Your server isn't responding to some <a>requests</a>.": "Server Anda tidak menanggapi beberapa <a>permintaan</a>.",
|
||||
"Prompt before sending invites to potentially invalid matrix IDs": "Tanyakan sebelum mengirim undangan ke ID Matrix yang mungkin tidak valid",
|
||||
"Prompt before sending invites to potentially invalid matrix IDs": "Tanyakan sebelum mengirim undangan ke ID Matrix yang mungkin tidak absah",
|
||||
"Update %(brand)s": "Perbarui %(brand)s",
|
||||
"This is the start of export of <roomName/>. Exported by <exporterDetails/> at %(exportDate)s.": "Ini adalah awalan dari ekspor <roomName/>. Diekspor oleh <exporterDetails/> di %(exportDate)s.",
|
||||
"Waiting for %(displayName)s to verify…": "Menunggu %(displayName)s untuk memverifikasi…",
|
||||
|
@ -1413,7 +1413,7 @@
|
|||
"Unable to find a supported verification method.": "Tidak dapat menemukan metode verifikasi yang didukung.",
|
||||
"Verify this user by confirming the following number appears on their screen.": "Verifikasi pengguna ini dengan mengkonfirmasi nomor berikut yang ditampilkan.",
|
||||
"Verify this user by confirming the following emoji appear on their screen.": "Verifikasi pengguna ini dengan mengkonfirmasi emoji berikut yang ditampilkan.",
|
||||
"Secure messages with this user are end-to-end encrypted and not able to be read by third parties.": "Pesan dengan pengguna ini terenkripsi secara ujung-ke-ujung dan tidak dapat dibaca oleh pihak ketiga.",
|
||||
"Secure messages with this user are end-to-end encrypted and not able to be read by third parties.": "Pesan dengan pengguna ini terenkripsi secara ujung ke ujung dan tidak dapat dibaca oleh pihak ketiga.",
|
||||
"You've successfully verified this user.": "Anda berhasil memverifikasi pengguna ini.",
|
||||
"The other party cancelled the verification.": "Pengguna yang lain membatalkan proses verifikasi ini.",
|
||||
"%(name)s on hold": "%(name)s ditahan",
|
||||
|
@ -1470,9 +1470,9 @@
|
|||
"Show rooms with unread notifications first": "Tampilkan ruangan dengan notifikasi yang belum dibaca dulu",
|
||||
"Order rooms by name": "Urutkan ruangan oleh nama",
|
||||
"Enable widget screenshots on supported widgets": "Aktifkan tangkapan layar widget di widget yang didukung",
|
||||
"Enable URL previews by default for participants in this room": "Aktifkan tampilan URL secara default untuk anggota di ruangan ini",
|
||||
"Enable URL previews for this room (only affects you)": "Aktifkan tampilan URL secara default (hanya mempengaruhi Anda)",
|
||||
"Enable inline URL previews by default": "Aktifkan tampilan URL secara default",
|
||||
"Enable URL previews by default for participants in this room": "Aktifkan tampilan URL secara bawaan untuk anggota di ruangan ini",
|
||||
"Enable URL previews for this room (only affects you)": "Aktifkan tampilan URL secara bawaan (hanya memengaruhi Anda)",
|
||||
"Enable inline URL previews by default": "Aktifkan tampilan URL secara bawaan",
|
||||
"Never send encrypted messages to unverified sessions in this room from this session": "Jangan kirim pesan terenkripsi ke sesi yang belum diverifikasi di ruangan ini dari sesi ini",
|
||||
"Never send encrypted messages to unverified sessions from this session": "Jangan kirim pesan terenkripsi ke sesi yang belum diverifikasi dari sesi ini",
|
||||
"Send analytics data": "Kirim data analitik",
|
||||
|
@ -1493,7 +1493,7 @@
|
|||
"Show avatars in user and room mentions": "Tampilkan avatar di sebutan pengguna dan ruangan",
|
||||
"Jump to the bottom of the timeline when you send a message": "Pergi ke bawah linimasa ketika Anda mengirim pesan",
|
||||
"Show line numbers in code blocks": "Tampilkan nomor barisan di blok kode",
|
||||
"Expand code blocks by default": "Buka blok kode secara default",
|
||||
"Expand code blocks by default": "Buka blok kode secara bawaan",
|
||||
"Enable automatic language detection for syntax highlighting": "Aktifkan deteksi bahasa otomatis untuk penyorotan sintaks",
|
||||
"Autoplay videos": "Mainkan video secara otomatis",
|
||||
"Autoplay GIFs": "Mainkan GIF secara otomatis",
|
||||
|
@ -1580,7 +1580,7 @@
|
|||
"Use high contrast": "Gunakan kontras tinggi",
|
||||
"Theme added!": "Tema ditambahkan!",
|
||||
"Error downloading theme information.": "Terjadi kesalahan saat mengunduh informasi tema.",
|
||||
"Invalid theme schema.": "Skema tema tidak valid.",
|
||||
"Invalid theme schema.": "Skema tema tidak absah.",
|
||||
"Integration managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "Manajer integrasi menerima data pengaturan, dan dapat mengubah widget, mengirimkan undangan ruangan, dan mengatur tingkat daya dengan sepengetahuan Anda.",
|
||||
"Manage integrations": "Kelola integrasi",
|
||||
"Use an integration manager to manage bots, widgets, and sticker packs.": "Gunakan sebuah manajer integrasi untuk mengelola bot, widget, dan paket stiker.",
|
||||
|
@ -1609,7 +1609,7 @@
|
|||
"Sidebar": "Bilah Samping",
|
||||
"Manage your signed-in devices below. A device's name is visible to people you communicate with.": "Kelola sesi Anda di bawah. Sebuah nama sesi dapat dilihat oleh siapa saja yang Anda berkomunikasi.",
|
||||
"Where you're signed in": "Di mana Anda masuk",
|
||||
"Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "Admin server Anda telah menonaktifkan enkripsi ujung-ke-ujung secara default di ruangan privat & Pesan Langsung.",
|
||||
"Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "Admin server Anda telah menonaktifkan enkripsi ujung ke ujung secara bawaan di ruangan privat & Pesan Langsung.",
|
||||
"Message search": "Pencarian pesan",
|
||||
"Secure Backup": "Cadangan Aman",
|
||||
"Reject all %(invitedRooms)s invites": "Tolak semua %(invitedRooms)s undangan",
|
||||
|
@ -1624,7 +1624,7 @@
|
|||
"Keyboard shortcuts": "Pintasan keyboard",
|
||||
"Show tray icon and minimise window to it on close": "Tampilkan ikon baki dan minimalkan window ke ikonnya jika ditutup",
|
||||
"Always show the window menu bar": "Selalu tampilkan bilah menu window",
|
||||
"Warn before quitting": "Beritahu sebelum keluar",
|
||||
"Warn before quitting": "Beri tahu sebelum keluar",
|
||||
"Start automatically after system login": "Mulai setelah login sistem secara otomatis",
|
||||
"Room ID or address of ban list": "ID ruangan atau alamat daftar larangan",
|
||||
"If this isn't what you want, please use a different tool to ignore users.": "Jika itu bukan yang Anda ingin, mohon pakai alat yang lain untuk mengabaikan pengguna.",
|
||||
|
@ -1639,9 +1639,9 @@
|
|||
"⚠ These settings are meant for advanced users.": "⚠ Pengaturan ini hanya untuk pengguna berkelanjutan saja.",
|
||||
"You are currently subscribed to:": "Anda saat ini berlangganan:",
|
||||
"View rules": "Tampilkan aturan",
|
||||
"You are not subscribed to any lists": "Anda belum berlangganan daftar apapun",
|
||||
"You are not subscribed to any lists": "Anda belum berlangganan daftar apa pun",
|
||||
"You are currently ignoring:": "Anda saat ini mengabaikan:",
|
||||
"You have not ignored anyone.": "Anda belum mengabaikan siapapun.",
|
||||
"You have not ignored anyone.": "Anda belum mengabaikan siapa pun.",
|
||||
"User rules": "Aturan pengguna",
|
||||
"Server rules": "Aturan server",
|
||||
"Ban list rules - %(roomName)s": "Daftar aturan cekalan — %(roomName)s",
|
||||
|
@ -1697,17 +1697,17 @@
|
|||
"For extra security, verify this user by checking a one-time code on both of your devices.": "Untuk keamanan lebih, verifikasi pengguna ini dengan memeriksa kode satu kali di kedua perangkat Anda.",
|
||||
"Verify User": "Verifikasi Pengguna",
|
||||
"In encrypted rooms, your messages are secured and only you and the recipient have the unique keys to unlock them.": "Di ruangan terenkripsi, pesan Anda diamankan dan hanya Anda dan penerimanya mempunyai kunci yang unik untuk mengaksesnya.",
|
||||
"Messages in this room are not end-to-end encrypted.": "Pesan di ruangan ini tidak dienkripsi secara ujung-ke-ujung.",
|
||||
"Messages in this room are not end-to-end encrypted.": "Pesan di ruangan ini tidak dienkripsi secara ujung ke ujung.",
|
||||
"Your messages are secured and only you and the recipient have the unique keys to unlock them.": "Pesan Anda diamankan dan hanya Anda dan penerimanya mempunyai kunci yang unik untuk mengaksesnya.",
|
||||
"Messages in this room are end-to-end encrypted.": "Pesan di ruangan ini terenkripsi secara ujung-ke-ujung.",
|
||||
"Messages in this room are end-to-end encrypted.": "Pesan di ruangan ini terenkripsi secara ujung ke ujung.",
|
||||
"Start Verification": "Mulai Verifikasi",
|
||||
"Waiting for %(displayName)s to accept…": "Menunggu untuk %(displayName)s untuk menerima…",
|
||||
"When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.": "Ketika seseorang menambahkan URL di pesannya, sebuah tampilan URL dapat ditampilkan untuk memberikan informasi lainnya tentang tautan itu seperti judul, deskripsi, dan sebuah gambar dari website.",
|
||||
"In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.": "Di ruangan terenkripsi, seperti ruangan ini, tampilan URL dinonaktifkan untuk memastikan homeserver Anda (di mana tampilannya dibuat) tidak mendapatkan informasi tentang tautan yang Anda lihat di ruangan ini.",
|
||||
"URL previews are disabled by default for participants in this room.": "Tampilan URL dinonaktifkan secara default untuk anggota di ruangan ini.",
|
||||
"URL previews are enabled by default for participants in this room.": "Tampilan URL diaktifkan secara default untuk anggota di ruangan ini.",
|
||||
"You have <a>disabled</a> URL previews by default.": "Anda telah <a>menonaktifkan</a> tampilan URL secara default.",
|
||||
"You have <a>enabled</a> URL previews by default.": "Anda telah <a>mengaktifkan</a> tampilan URL secara default.",
|
||||
"In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.": "Di ruangan terenkripsi, seperti ruangan ini, tampilan URL dinonaktifkan secara bawaan untuk memastikan homeserver Anda (di mana tampilannya dibuat) tidak mendapatkan informasi tentang tautan yang Anda lihat di ruangan ini.",
|
||||
"URL previews are disabled by default for participants in this room.": "Tampilan URL dinonaktifkan secara bawaan untuk anggota di ruangan ini.",
|
||||
"URL previews are enabled by default for participants in this room.": "Tampilan URL diaktifkan secara bawaan untuk anggota di ruangan ini.",
|
||||
"You have <a>disabled</a> URL previews by default.": "Anda telah <a>menonaktifkan</a> tampilan URL secara bawaan.",
|
||||
"You have <a>enabled</a> URL previews by default.": "Anda telah <a>mengaktifkan</a> tampilan URL secara bawaan.",
|
||||
"Publish this room to the public in %(domain)s's room directory?": "Publikasi ruangan ini ke publik di direktori ruangan %(domain)s?",
|
||||
"Show more": "Tampilkan lebih banyak",
|
||||
"Set addresses for this room so users can find this room through your homeserver (%(localDomain)s)": "Tetapkan alamat untuk ruangan ini supaya pengguna dapat menemukan space ini melalui homeserver Anda (%(localDomain)s)",
|
||||
|
@ -1756,7 +1756,7 @@
|
|||
"Forget Room": "Lupakan Ruangan",
|
||||
"Notification options": "Opsi notifikasi",
|
||||
"Mentions & Keywords": "Sebutan & Kata Kunci",
|
||||
"Use default": "Gunakan default",
|
||||
"Use default": "Gunakan bawaan",
|
||||
"Show less": "Tampilkan lebih sedikit",
|
||||
"Show %(count)s more|one": "Tampilkan %(count)s lagi",
|
||||
"Show %(count)s more|other": "Tampilkan %(count)s lagi",
|
||||
|
@ -1813,7 +1813,7 @@
|
|||
"Online for %(duration)s": "Daring selama %(duration)s",
|
||||
"View message": "Tampilkan pesan",
|
||||
"Message didn't send. Click for info.": "Pesan tidak terkirim. Klik untuk informasi.",
|
||||
"End-to-end encryption isn't enabled": "Enkripsi ujung-ke-ujung belum diaktifkan",
|
||||
"End-to-end encryption isn't enabled": "Enkripsi ujung ke ujung tidak diaktifkan",
|
||||
"Enable encryption in settings.": "Aktifkan enkripsi di pengaturan.",
|
||||
"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.": "Pesan privat Anda biasanya dienkripsi, tetapi di ruangan ini tidak terenkripsi. Biasanya ini disebabkan oleh perangkat yang tidak mendukung atau metode yang sedang digunakan, seperti undangan email.",
|
||||
"This is the start of <roomName/>.": "Ini adalah awal dari <roomName/>.",
|
||||
|
@ -1877,14 +1877,14 @@
|
|||
"Unknown Command": "Perintah Tidak Diketahui",
|
||||
"Server unavailable, overloaded, or something else went wrong.": "Server tidak tersedia, terlalu penuh, atau ada sesuatu yang salah.",
|
||||
"Everyone in this room is verified": "Semuanya di ruangan ini telah terverifikasi",
|
||||
"This room is end-to-end encrypted": "Ruangan ini dienkripsi secara ujung-ke-ujung",
|
||||
"This room is end-to-end encrypted": "Ruangan ini dienkripsi secara ujung ke ujung",
|
||||
"Someone is using an unknown session": "Seseorang menggunakan sesi yang tidak dikenal",
|
||||
"You have verified this user. This user has verified all of their sessions.": "Anda telah memverifikasi pengguna ini. Pengguna ini telah memverifikasi semua sesinya.",
|
||||
"You have not verified this user.": "Anda belum memverifikasi pengguna ini.",
|
||||
"This user has not verified all of their sessions.": "Pengguna ini belum memverifikasi semua sesinya.",
|
||||
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.": "Sebuah teks pesan telah dikirim ke +%(msisdn)s. Silakan masukkan kode verifikasinya.",
|
||||
"We've sent you an email to verify your address. Please follow the instructions there and then click the button below.": "Kami telah mengirim sebuah email untuk memverifikasi alamat Anda. Silakan ikuti instruksinya dan klik tombol di bawah.",
|
||||
"This doesn't appear to be a valid email address": "Ini sepertinya bukan alamat email yang valid",
|
||||
"This doesn't appear to be a valid email address": "Ini sepertinya bukan alamat email yang absah",
|
||||
"Unable to remove contact information": "Tidak dapat menghapus informasi kontak",
|
||||
"Discovery options will appear once you have added a phone number above.": "Opsi penemuan akan tersedia setelah Anda telah menambahkan sebuah nomor telepon di atas.",
|
||||
"Discovery options will appear once you have added an email above.": "Opsi penemuan akan tersedia setelah Anda telah menambahkan sebuah email di atas.",
|
||||
|
@ -2002,8 +2002,8 @@
|
|||
"Encryption not enabled": "Enkripsi tidak diaktifkan",
|
||||
"Ignored attempt to disable encryption": "Mengabaikan percobaan untuk menonaktifkan enkripsi",
|
||||
"Encryption enabled": "Enkripsi diaktifkan",
|
||||
"Messages in this room are end-to-end encrypted. When people join, you can verify them in their profile, just tap on their avatar.": "Pesan di ruangan ini terenkripsi secara ujung-ke-ujung. Ketika orang-orang bergabung, Anda dapat memverifikasi mereka di profil mereka — klik pada avatar mereka.",
|
||||
"Messages here are end-to-end encrypted. Verify %(displayName)s in their profile - tap on their avatar.": "Pesan di pesan langsung ini terenkripsi secara ujung-ke-ujung. Verifikasi %(displayName)s di profilnya — klik pada avatarnya.",
|
||||
"Messages in this room are end-to-end encrypted. When people join, you can verify them in their profile, just tap on their avatar.": "Pesan di ruangan ini terenkripsi secara ujung ke ujung. Ketika orang-orang bergabung, Anda dapat memverifikasi mereka di profil mereka — klik pada avatar mereka.",
|
||||
"Messages here are end-to-end encrypted. Verify %(displayName)s in their profile - tap on their avatar.": "Pesan di pesan langsung ini terenkripsi secara ujung ke ujung. Verifikasi %(displayName)s di profilnya — klik pada avatarnya.",
|
||||
"Some encryption parameters have been changed.": "Beberapa parameter enkripsi telah diubah.",
|
||||
"The call is in an unknown state!": "Panggilan ini berada di status yang tidak diketahui!",
|
||||
"Missed call": "Panggilan terlewat",
|
||||
|
@ -2037,7 +2037,7 @@
|
|||
"Compare unique emoji": "Bandingkan emoji unik",
|
||||
"Scan this unique code": "Pindai kode unik ini",
|
||||
"Edit devices": "Edit perangkat",
|
||||
"This client does not support end-to-end encryption.": "Klien ini tidak mendukung enkripsi ujung-ke-ujung.",
|
||||
"This client does not support end-to-end encryption.": "Klien ini tidak mendukung enkripsi ujung ke ujung.",
|
||||
"Role in <RoomName/>": "Peran di <RoomName/>",
|
||||
"Failed to deactivate user": "Gagal untuk menonaktifkan pengguna",
|
||||
"Deactivate user": "Nonaktifkan pengguna",
|
||||
|
@ -2097,14 +2097,14 @@
|
|||
"Setting ID": "ID Pengaturan",
|
||||
"There was an error finding this widget.": "Terjadi sebuah kesalahan menemukan widget ini.",
|
||||
"Active Widgets": "Widget Aktif",
|
||||
"Server did not return valid authentication information.": "Server tidak memberikan informasi otentikasi yang valid.",
|
||||
"Server did not return valid authentication information.": "Server tidak memberikan informasi otentikasi yang absah.",
|
||||
"Server did not require any authentication": "Server tidak membutuhkan otentikasi apa pun",
|
||||
"There was a problem communicating with the server. Please try again.": "Terjadi sebuah masalah ketika berkomunikasi dengan server. Mohon coba lagi.",
|
||||
"Confirm account deactivation": "Konfirmasi penonaktifan akun",
|
||||
"Confirm your account deactivation by using Single Sign On to prove your identity.": "Konfirmasi penonaktifan akun Anda dengan menggunakan Single Sign On untuk membuktikan identitas Anda.",
|
||||
"Are you sure you want to deactivate your account? This is irreversible.": "Apakah Anda yakin ingin menonaktifkan akun Anda? Ini tidak dapat dibatalkan.",
|
||||
"Continue With Encryption Disabled": "Lanjutkan Dengan Enkripsi Dinonaktifkan",
|
||||
"You've previously used a newer version of %(brand)s with this session. To use this version again with end to end encryption, you will need to sign out and back in again.": "Anda sebelumnya menggunakan sebuah versi %(brand)s yang baru dengan sesi ini. Untuk menggunakan versi ini lagi dengan enkripsi ujung-ke-ujung, Anda harus keluar dan masuk lagi.",
|
||||
"You've previously used a newer version of %(brand)s with this session. To use this version again with end to end encryption, you will need to sign out and back in again.": "Anda sebelumnya menggunakan sebuah versi %(brand)s yang baru dengan sesi ini. Untuk menggunakan versi ini lagi dengan enkripsi ujung ke ujung, Anda harus keluar dan masuk lagi.",
|
||||
"To avoid losing your chat history, you must export your room keys before logging out. You will need to go back to the newer version of %(brand)s to do this": "Untuk menghindari kehilangan riwayat obrolan, Anda harus mengekspor kunci ruangan Anda sebelum keluar. Anda harus kembali ke versi %(brand)s yang baru untuk melakukannya",
|
||||
"Want to add an existing space instead?": "Ingin menambahkan sebuah space yang sudah ada saja?",
|
||||
"Add a space to a space you manage.": "Tambahkan sebuah space ke space yang Anda kelola.",
|
||||
|
@ -2125,7 +2125,7 @@
|
|||
"Create a room": "Buat sebuah ruangan",
|
||||
"You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Anda mungkin menonaktifkannya jika ruangan ini akan digunakan untuk berkolabroasi dengan tim eksternal yang mempunyai homeserver sendiri. Ini tidak dapat diubah nanti.",
|
||||
"You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "Anda mungkin aktifkan jika ruangan ini hanya digunakan untuk berkolabroasi dengan tim internal di homeserver Anda. Ini tidak dapat diubah nanti.",
|
||||
"Enable end-to-end encryption": "Aktifkan enkripsi ujung-ke-ujung",
|
||||
"Enable end-to-end encryption": "Aktifkan enkripsi ujung ke ujung",
|
||||
"Your server requires encryption to be enabled in private rooms.": "Server Anda memerlukan mengaktifkan enkripsi di ruangan privat.",
|
||||
"You can't disable this later. Bridges & most bots won't work yet.": "Anda tidak dapat menonaktifkannya nanti. Jembatan & kebanyakan bot belum dapat digunakan.",
|
||||
"Anyone will be able to find and join this room.": "Siapa saja dapat menemukan dan bergabung ke ruangan ini.",
|
||||
|
@ -2147,7 +2147,7 @@
|
|||
"Preparing to download logs": "Mempersiapkan untuk mengunduh catatan",
|
||||
"Failed to send logs: ": "Gagal untuk mengirimkan catatan: ",
|
||||
"Preparing to send logs": "Mempersiapkan untuk mengirimkan catatan",
|
||||
"Please tell us what went wrong or, better, create a GitHub issue that describes the problem.": "Mohon beritahu kami apa saja yang salah atau, lebih baik, buat sebuah issue GitHub yang menjelaskan masalahnya.",
|
||||
"Please tell us what went wrong or, better, create a GitHub issue that describes the problem.": "Mohon beri tahu kami apa saja yang salah atau, lebih baik, buat sebuah issue GitHub yang menjelaskan masalahnya.",
|
||||
"To leave the beta, visit your settings.": "Untuk keluar dari beta, pergi ke pengaturan Anda.",
|
||||
"Close dialog": "Tutup dialog",
|
||||
"Invite anyway and never warn me again": "Undang saja dan jangan peringatkan saya lagi",
|
||||
|
@ -2283,7 +2283,7 @@
|
|||
"Sign into your homeserver": "Masuk ke homeserver Anda",
|
||||
"Matrix.org is the biggest public homeserver in the world, so it's a good place for many.": "Matrix.org adalah homeserver publik terbesar di dunia, jadi itu adalah tempat yang bagus untuk banyak orang.",
|
||||
"Specify a homeserver": "Tentukan sebuah homeserver",
|
||||
"Invalid URL": "URL tidak valid",
|
||||
"Invalid URL": "URL tidak absah",
|
||||
"Unable to validate homeserver": "Tidak dapat memvalidasi homeserver",
|
||||
"Recent changes that have not yet been received": "Perubahan terbaru yang belum diterima",
|
||||
"The server is not configured to indicate what the problem is (CORS).": "Server tidak diatur untuk menandakan apa masalahnya (CORS).",
|
||||
|
@ -2305,7 +2305,7 @@
|
|||
"Upgrade private room": "Tingkatkan ruangan privat",
|
||||
"Automatically invite members from this room to the new one": "Mengundang pengguna dari ruangan ini ke yang baru secara otomatis",
|
||||
"Put a link back to the old room at the start of the new room so people can see old messages": "Letakkan sebuah tautan kembali ke ruangan yang lama di awal ruangan baru supaya orang-orang dapat melihat pesan-pesan lama",
|
||||
"Stop users from speaking in the old version of the room, and post a message advising users to move to the new room": "Menghentikan pengguna dari berbicara di versi ruangan yang lama, dan mengirimkan sebuah pesan memberitahu pengguna untuk pindah ke ruangan yang baru",
|
||||
"Stop users from speaking in the old version of the room, and post a message advising users to move to the new room": "Menghentikan pengguna dari berbicara di versi ruangan yang lama, dan mengirimkan sebuah pesan memberi tahu pengguna untuk pindah ke ruangan yang baru",
|
||||
"Update any local room aliases to point to the new room": "Memperbarui alias ruangan lokal apa saja untuk diarahkan ke ruangan yang baru",
|
||||
"Create a new room with the same name, description and avatar": "Membuat ruangan baru dengan nama, deskripsi, dan avatar yang sama",
|
||||
"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:": "Meningkatkan ruangan ini membutuhkan penutupan instansi ruangan saat ini dan membuat ruangan yang baru di tempatnya. Untuk memberikan anggota ruangan pengalaman yang baik, kami akan:",
|
||||
|
@ -2386,13 +2386,13 @@
|
|||
"This account has been deactivated.": "Akun ini telah dinonaktifkan.",
|
||||
"Please <a>contact your service administrator</a> to continue using this service.": "Mohon <a>hubungi administrator layanan Anda</a> untuk melanjutkan menggunakan layanannya.",
|
||||
"This homeserver does not support login using email address.": "Homeserver ini tidak mendukung login menggunakan alamat email.",
|
||||
"Identity server URL does not appear to be a valid identity server": "URL server identitas terlihat bukan sebagai server identitas yang valid",
|
||||
"Invalid base_url for m.identity_server": "base_url tidak valid untuk m.identity_server",
|
||||
"Invalid identity server discovery response": "Respons penemuan server identitas tidak valid",
|
||||
"Homeserver URL does not appear to be a valid Matrix homeserver": "URL homeserver sepertinya bukan sebagai homeserver Matrix yang valid",
|
||||
"Invalid base_url for m.homeserver": "base_url tidak valid untuk m.homeserver",
|
||||
"Identity server URL does not appear to be a valid identity server": "URL server identitas terlihat bukan sebagai server identitas yang absah",
|
||||
"Invalid base_url for m.identity_server": "base_url tidak absah untuk m.identity_server",
|
||||
"Invalid identity server discovery response": "Respons penemuan server identitas tidak absah",
|
||||
"Homeserver URL does not appear to be a valid Matrix homeserver": "URL homeserver sepertinya bukan sebagai homeserver Matrix yang absah",
|
||||
"Invalid base_url for m.homeserver": "base_url tidak absah untuk m.homeserver",
|
||||
"Failed to get autodiscovery configuration from server": "Gagal untuk mendapatkan konfigurasi penemuan otomatis dari server",
|
||||
"Invalid homeserver discovery response": "Respons penemuan homeserver tidak valid",
|
||||
"Invalid homeserver discovery response": "Respons penemuan homeserver tidak absah",
|
||||
"Set a new password": "Tetapkan kata sandi baru",
|
||||
"Your password has been reset.": "Kata sandi Anda telah diatur ulang.",
|
||||
"I have verified my email address": "Saya telah memverifikasi alamat email saya",
|
||||
|
@ -2400,7 +2400,7 @@
|
|||
"Sign in instead": "Masuk saja",
|
||||
"A verification email will be sent to your inbox to confirm setting your new password.": "Sebuah email verifikasi akan dikirim ke kotak masuk Anda untuk mengkonfirmasi mengatur kata sandi Anda yang baru.",
|
||||
"New passwords must match each other.": "Kata sandi baru harus cocok.",
|
||||
"The email address doesn't appear to be valid.": "Alamat email ini tidak terlihat valid.",
|
||||
"The email address doesn't appear to be valid.": "Alamat email ini tidak terlihat absah.",
|
||||
"The email address linked to your account must be entered.": "Alamat email yang tertaut ke akun Anda harus dimasukkan.",
|
||||
"Skip verification for now": "Lewatkan verifikasi untuk sementara",
|
||||
"Really reset verification keys?": "Benar-benar ingin mengatur ulang kunci-kunci verifikasi?",
|
||||
|
@ -2446,7 +2446,7 @@
|
|||
"Go to my first room": "Pergi ke ruangan pertama saya",
|
||||
"Share %(name)s": "Bagikan %(name)s",
|
||||
"Search for rooms or spaces": "Cari untuk ruangan atau space",
|
||||
"Pick rooms or conversations to add. This is just a space for you, no one will be informed. You can add more later.": "Pilih ruangan atau percakapan untuk ditambahkan. Ini adalah hanya sebuah space untuk Anda, tidak ada siapa saja yang diberitahu. Anda dapat menambahkan lagi nanti.",
|
||||
"Pick rooms or conversations to add. This is just a space for you, no one will be informed. You can add more later.": "Pilih ruangan atau percakapan untuk ditambahkan. Ini adalah hanya sebuah space untuk Anda, tidak ada siapa pun yang diberi tahu. Anda dapat menambahkan lagi nanti.",
|
||||
"What do you want to organise?": "Apa saja yang Anda ingin organisirkan?",
|
||||
"Creating rooms...": "Membuat ruangan...",
|
||||
"Skip for now": "Lewat untuk sementara",
|
||||
|
@ -2497,7 +2497,7 @@
|
|||
"%(creator)s created and configured the room.": "%(creator)s membuat dan mengatur ruangan ini.",
|
||||
"%(creator)s created this DM.": "%(creator)s membuat pesan langsung ini.",
|
||||
"Verification requested": "Verifikasi diminta",
|
||||
"Data from an older version of %(brand)s has been detected. This will have caused end-to-end cryptography to malfunction in the older version. End-to-end encrypted messages exchanged recently whilst using the older version may not be decryptable in this version. This may also cause messages exchanged with this version to fail. If you experience problems, log out and back in again. To retain message history, export and re-import your keys.": "Data dari %(brand)s versi lama telah terdeteksi. Ini akan menyebabkan kriptografi ujung-ke-ujung tidak berfungsi di versi yang lebih lama. Pesan terenkripsi secara ujung-ke-ujung yang dipertukarkan baru-baru ini saat menggunakan versi yang lebih lama mungkin tidak dapat didekripsi dalam versi ini. Ini juga dapat menyebabkan pesan yang dipertukarkan dengan versi ini gagal. Jika Anda mengalami masalah, keluar dan masuk kembali. Untuk menyimpan riwayat pesan, ekspor dan impor ulang kunci Anda.",
|
||||
"Data from an older version of %(brand)s has been detected. This will have caused end-to-end cryptography to malfunction in the older version. End-to-end encrypted messages exchanged recently whilst using the older version may not be decryptable in this version. This may also cause messages exchanged with this version to fail. If you experience problems, log out and back in again. To retain message history, export and re-import your keys.": "Data dari %(brand)s versi lama telah terdeteksi. Ini akan menyebabkan kriptografi ujung ke ujung tidak berfungsi di versi yang lebih lama. Pesan terenkripsi secara ujung ke ujung yang dipertukarkan baru-baru ini saat menggunakan versi yang lebih lama mungkin tidak dapat didekripsi dalam versi ini. Ini juga dapat menyebabkan pesan yang dipertukarkan dengan versi ini gagal. Jika Anda mengalami masalah, keluar dan masuk kembali. Untuk menyimpan riwayat pesan, ekspor dan impor ulang kunci Anda.",
|
||||
"Old cryptography data detected": "Data kriptografi lama terdeteksi",
|
||||
"Review terms and conditions": "Lihat syarat dan ketentuan",
|
||||
"To continue using the %(homeserverDomain)s homeserver you must review and agree to our terms and conditions.": "Untuk melanjutkan menggunakan homeserver %(homeserverDomain)s Anda harus lihat dan terima ke syarat dan ketentuan kami.",
|
||||
|
@ -2549,7 +2549,7 @@
|
|||
"Please review and accept all of the homeserver's policies": "Mohon lihat dan terima semua kebijakan homeserver ini",
|
||||
"Missing captcha public key in homeserver configuration. Please report this to your homeserver administrator.": "Tidak ada kunci publik captcha di konfigurasi homeserver. Mohon melaporkannya ke administrator homeserver Anda.",
|
||||
"Confirm your identity by entering your account password below.": "Konfirmasi identitas Anda dengan memasukkan kata sandi akun Anda di bawah.",
|
||||
"Doesn't look like a valid email address": "Kelihatannya bukan sebuah alamat email yang valid",
|
||||
"Doesn't look like a valid email address": "Kelihatannya bukan sebuah alamat email yang absah",
|
||||
"Enter email address": "Masukkan alamat email",
|
||||
"Country Dropdown": "Dropdown Negara",
|
||||
"This homeserver would like to make sure you are not a robot.": "Homeserver ini memastikan Anda bahwa Anda bukan sebuah robot.",
|
||||
|
@ -2581,8 +2581,8 @@
|
|||
"If you've forgotten your Security Key you can <button>set up new recovery options</button>": "Jika Anda lupa Kunci Keamanan, Anda dapat <button>menyiapkan opsi pemulihan baru</button>",
|
||||
"Access your secure message history and set up secure messaging by entering your Security Key.": "Akses riwayat pesan aman Anda dan siapkan perpesanan aman dengan memasukkan Kunci Keamanan Anda.",
|
||||
"<b>Warning</b>: You should only set up key backup from a trusted computer.": "<b>Peringatan</b>: Anda seharusnya menyiapkan cadangan kunci di komputer yang dipercayai.",
|
||||
"Not a valid Security Key": "Bukan Kunci Keamanan yang valid",
|
||||
"This looks like a valid Security Key!": "Ini sepertinya Kunci Keamanan yang valid!",
|
||||
"Not a valid Security Key": "Bukan Kunci Keamanan yang absah",
|
||||
"This looks like a valid Security Key!": "Ini sepertinya Kunci Keamanan yang absah!",
|
||||
"Enter Security Key": "Masukkan Kunci Keamanan",
|
||||
"If you've forgotten your Security Phrase you can <button1>use your Security Key</button1> or <button2>set up new recovery options</button2>": "Jika Anda lupa Frasa Keamanan, Anda dapat <button1>menggunakan Kunci Keamanan Anda</button1> atau <button2>siapkan opsi pemulihan baru</button2>",
|
||||
"Access your secure message history and set up secure messaging by entering your Security Phrase.": "Akses riwayat pesan aman Anda dan siapkan perpesanan aman dengan memasukkan Frasa Keamanan Anda.",
|
||||
|
@ -2616,7 +2616,7 @@
|
|||
"Only do this if you have no other device to complete verification with.": "Hanya lakukan ini jika Anda tidak memiliki perangkat yang lain untuk menyelesaikan verifikasi.",
|
||||
"Reset everything": "Atur ulang semuanya",
|
||||
"Forgotten or lost all recovery methods? <a>Reset all</a>": "Lupa atau kehilangan semua metode pemulihan? <a>Atur ulang semuanya</a>",
|
||||
"Invalid Security Key": "Kunci Keamanan tidak valid",
|
||||
"Invalid Security Key": "Kunci Keamanan tidak absah",
|
||||
"Wrong Security Key": "Kunci Keamanan salah",
|
||||
"Looks good!": "Kelihatannya bagus!",
|
||||
"Wrong file type": "Tipe file salah",
|
||||
|
@ -2669,7 +2669,7 @@
|
|||
"Space Autocomplete": "Penyelesaian Space Otomatis",
|
||||
"Room Autocomplete": "Penyelesaian Ruangan Otomatis",
|
||||
"Notification Autocomplete": "Penyelesaian Notifikasi Otomatis",
|
||||
"Notify the whole room": "Beritahu seluruh ruangan",
|
||||
"Notify the whole room": "Beri tahu seluruh ruangan",
|
||||
"Command Autocomplete": "Penyelesaian Perintah Otomatis",
|
||||
"Warning: Your personal data (including encryption keys) is still stored in this session. Clear it if you're finished using this session, or want to sign in to another account.": "Peringatan: Data personal Anda (termasuk kunci enkripsi) masih disimpan di sesi ini. Hapus jika Anda selesai menggunakan sesi ini, atau jika ingin masuk ke akun yang lain.",
|
||||
"Clear personal data": "Hapus data personal",
|
||||
|
@ -2753,7 +2753,7 @@
|
|||
"Search spaces": "Cari space",
|
||||
"Decide which spaces can access this room. If a space is selected, its members can find and join <RoomName/>.": "Tentukan space mana yang dapat mengakses ruangan ini. Jika sebuah space dipilih, anggotanya dapat menemukan dan bergabung <RoomName/>.",
|
||||
"Select spaces": "Pilih space",
|
||||
"You're removing all spaces. Access will default to invite only": "Anda menghilangkan semua space. Akses secara default ke undangan saja",
|
||||
"You're removing all spaces. Access will default to invite only": "Anda menghilangkan semua space. Akses secara bawaan ke undangan saja",
|
||||
"%(count)s rooms|one": "%(count)s ruangan",
|
||||
"%(count)s rooms|other": "%(count)s ruangan",
|
||||
"%(count)s members|one": "%(count)s anggota",
|
||||
|
@ -2763,7 +2763,7 @@
|
|||
"Manually export keys": "Ekspor kunci secara manual",
|
||||
"I don't want my encrypted messages": "Saya tidak ingin pesan-pesan terenkripsi saya",
|
||||
"Start using Key Backup": "Mulai menggunakan Cadangan Kunci",
|
||||
"Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Pesan terenkripsi diamankan dengan enkripsi ujung-ke-ujung. Hanya Anda dan penerima punya kuncinya untuk membaca pesan-pesan ini.",
|
||||
"Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Pesan terenkripsi diamankan dengan enkripsi ujung ke ujung. Hanya Anda dan penerima punya kuncinya untuk membaca pesan ini.",
|
||||
"Leave space": "Tinggalkan space",
|
||||
"Leave some rooms": "Tinggalkan beberapa ruangan",
|
||||
"Leave all rooms": "Tinggalkan semua ruangan",
|
||||
|
@ -2807,7 +2807,7 @@
|
|||
"Start a conversation with someone using their name or username (like <userId/>).": "Mulai sebuah obrolan dengan seseorang menggunakan namanya atau nama pengguna (seperti <userId/>).",
|
||||
"Recently Direct Messaged": "Pesan Langsung Kini",
|
||||
"Recent Conversations": "Obrolan Terkini",
|
||||
"The following users might not exist or are invalid, and cannot be invited: %(csvNames)s": "Pengguna berikut ini mungkin tidak ada atau tidak valid, dan tidak dapat diundang: %(csvNames)s",
|
||||
"The following users might not exist or are invalid, and cannot be invited: %(csvNames)s": "Pengguna berikut ini mungkin tidak ada atau tidak absah, dan tidak dapat diundang: %(csvNames)s",
|
||||
"Failed to find the following users": "Gagal untuk mencari pengguna berikut ini",
|
||||
"A call can only be transferred to a single user.": "Sebuah panggilan dapat dipindah ke sebuah pengguna.",
|
||||
"We couldn't invite those users. Please check the users you want to invite and try again.": "Kami tidak dapat mengundang penggunanya. Mohon periksa pengguna yang Anda ingin undang dan coba lagi.",
|
||||
|
@ -2824,9 +2824,9 @@
|
|||
"Incoming Verification Request": "Permintaan Verifikasi Masuk",
|
||||
"Waiting for partner to confirm...": "Menunggu pengguna lain untuk mengkonfirmasi...",
|
||||
"Verifying this device will mark it as trusted, and users who have verified with you will trust this device.": "Memverifikasi perangkat ini akan menandainya sebagai terpercaya, dan pengguna yang telah diverifikasi dengan Anda akan mempercayai perangkat ini.",
|
||||
"Verify this device to mark it as trusted. Trusting this device gives you and other users extra peace of mind when using end-to-end encrypted messages.": "Verifikasi perangkat ini untuk menandainya sebagai terpercaya. Mempercayai perangkat ini akan memberikan Anda dan pengguna lain ketenangan saat menggunakan pesan-pesan terenkripsi secara ujung-ke-ujung.",
|
||||
"Verify this device to mark it as trusted. Trusting this device gives you and other users extra peace of mind when using end-to-end encrypted messages.": "Verifikasi perangkat ini untuk menandainya sebagai terpercaya. Mempercayai perangkat ini akan memberikan Anda dan pengguna lain ketenangan saat menggunakan pesan terenkripsi secara ujung ke ujung.",
|
||||
"Verifying this user will mark their session as trusted, and also mark your session as trusted to them.": "Memverifikasi pengguna ini akan menandai sesinya sebagai terpercaya, dan juga menandai sesi Anda sebagai terpercaya kepadanya.",
|
||||
"Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.": "Verifikasi pengguna ini untuk menandainya sebagai terpercaya. Mempercayai pengguna memberikan Anda ketenangan saat menggunakan pesan-pesan terenkripsi secara ujung-ke-ujung.",
|
||||
"Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.": "Verifikasi pengguna ini untuk menandainya sebagai terpercaya. Mempercayai pengguna memberikan Anda ketenangan saat menggunakan pesan terenkripsi secara ujung ke ujung.",
|
||||
"Based on %(count)s votes|one": "Berdasarkan oleh %(count)s suara",
|
||||
"Based on %(count)s votes|other": "Berdasarkan oleh %(count)s suara",
|
||||
"%(count)s votes|one": "%(count)s suara",
|
||||
|
@ -2907,7 +2907,7 @@
|
|||
"Spaces you know that contain this space": "Space yang Anda tahu yang berisi space ini",
|
||||
"Chat": "Obrolan",
|
||||
"Clear": "Hapus",
|
||||
"You may contact me if you want to follow up or to let me test out upcoming ideas": "Anda mungkin hubungi saya jika Anda ingin menindaklanjuti atau memberitahu saya untuk menguji ide-ide baru",
|
||||
"You may contact me if you want to follow up or to let me test out upcoming ideas": "Anda mungkin hubungi saya jika Anda ingin menindaklanjuti atau memberi tahu saya untuk menguji ide baru",
|
||||
"Home options": "Opsi Beranda",
|
||||
"%(spaceName)s menu": "Menu %(spaceName)s",
|
||||
"Join public room": "Bergabung ke ruangan publik",
|
||||
|
@ -2923,7 +2923,7 @@
|
|||
"To view all keyboard shortcuts, <a>click here</a>.": "Untuk melihat semua shortcut keyboard, <a>klik di sini</a>.",
|
||||
"You can turn this off anytime in settings": "Anda dapat mematikannya kapan saja di pengaturan",
|
||||
"We <Bold>don't</Bold> share information with third parties": "Kami <Bold>tidak</Bold> membagikan informasi ini dengan pihak ketiga",
|
||||
"We <Bold>don't</Bold> record or profile any account data": "Kami <Bold>tidak</Bold> merekam atau memprofil data akun apapun",
|
||||
"We <Bold>don't</Bold> record or profile any account data": "Kami <Bold>tidak</Bold> merekam atau memprofil data akun apa pun",
|
||||
"You can read all our terms <PrivacyPolicyUrl>here</PrivacyPolicyUrl>": "Anda dapat membaca kebijakan kami <PrivacyPolicyUrl>di sini</PrivacyPolicyUrl>",
|
||||
"Share anonymous data to help us identify issues. Nothing personal. No third parties.": "Bagikan data anonim untuk membantu kami mengidentifikasi masalah-masalah. Tidak ada yang pribadi. Tidak ada pihak ketiga.",
|
||||
"Okay": "Ok",
|
||||
|
@ -2983,7 +2983,7 @@
|
|||
"Dial": "Dial",
|
||||
"Missing room name or separator e.g. (my-room:domain.org)": "Kurang nama ruangan atau pemisah mis. (ruangan-saya:domain.org)",
|
||||
"Missing domain separator e.g. (:domain.org)": "Kurang pemisah domain mis. (:domain.org)",
|
||||
"This address had invalid server or is already in use": "Alamat ini memiliki server yang tidak valid atau telah digunakan",
|
||||
"This address had invalid server or is already in use": "Alamat ini memiliki server yang tidak absah atau telah digunakan",
|
||||
"Back to thread": "Kembali ke utasan",
|
||||
"Room members": "Anggota ruangan",
|
||||
"Back to chat": "Kembali ke obrolan",
|
||||
|
@ -3076,7 +3076,7 @@
|
|||
"Spaces are ways to group rooms and people. Alongside the spaces you're in, you can use some pre-built ones too.": "Space adalah cara untuk mengelompokkan ruangan dan orang. Di sampingnya space yang Anda berada, Anda dapat menggunakan space yang sudah dibuat.",
|
||||
"IRC (Experimental)": "IRC (Eksperimental)",
|
||||
"Call": "Panggil",
|
||||
"Right panel stays open (defaults to room member list)": "Panel kanan tetap terbuka (menampilkan daftar anggota ruangan secara default)",
|
||||
"Right panel stays open (defaults to room member list)": "Panel kanan tetap terbuka (menampilkan daftar anggota ruangan secara bawaan)",
|
||||
"Toggle hidden event visibility": "Alih visibilitas peristiwa tersembunyi",
|
||||
"Redo edit": "Ulangi editan",
|
||||
"Force complete": "Selesaikan dengan paksa",
|
||||
|
@ -3220,7 +3220,7 @@
|
|||
"Capabilities": "Kemampuan",
|
||||
"Send custom state event": "Kirim peristiwa status kustom",
|
||||
"Failed to send event!": "Gagal mengirimkan pertistiwa!",
|
||||
"Doesn't look like valid JSON.": "Tidak terlihat seperti JSON yang valid.",
|
||||
"Doesn't look like valid JSON.": "Tidak terlihat seperti JSON yang absah.",
|
||||
"Send custom room account data event": "Kirim peristiwa data akun ruangan kustom",
|
||||
"Send custom account data event": "Kirim peristiwa data akun kustom",
|
||||
"Room ID: %(roomId)s": "ID ruangan: %(roomId)s",
|
||||
|
@ -3442,7 +3442,7 @@
|
|||
"You're in": "Anda dalam",
|
||||
"You need to have the right permissions in order to share locations in this room.": "Anda harus mempunyai izin yang diperlukan untuk membagikan lokasi di ruangan ini.",
|
||||
"You don't have permission to share locations": "Anda tidak memiliki izin untuk membagikan lokasi",
|
||||
"Messages in this chat will be end-to-end encrypted.": "Pesan di obrolan ini akan dienkripsi secara ujung-ke-ujung.",
|
||||
"Messages in this chat will be end-to-end encrypted.": "Pesan di obrolan ini akan dienkripsi secara ujung ke ujung.",
|
||||
"Send your first message to invite <displayName/> to chat": "Kirim pesan pertama Anda untuk mengundang <displayName/> ke obrolan",
|
||||
"Favourite Messages (under active development)": "Pesan Favorit (dalam pengembangan aktif)",
|
||||
"Saved Items": "Item yang Tersimpan",
|
||||
|
@ -3473,7 +3473,7 @@
|
|||
"Find your co-workers": "Temukan rekan kerja Anda",
|
||||
"Secure messaging for work": "Perpesanan aman untuk berkerja",
|
||||
"Start your first chat": "Mulai obrolan pertama Anda",
|
||||
"With free end-to-end encrypted messaging, and unlimited voice and video calls, %(brand)s is a great way to stay in touch.": "Dengan perpesanan terenkripsi ujung-ke-ujung gratis, dan panggilan suara & video tidak terbatas, %(brand)s adalah cara yang baik untuk tetap terhubung.",
|
||||
"With free end-to-end encrypted messaging, and unlimited voice and video calls, %(brand)s is a great way to stay in touch.": "Dengan perpesanan terenkripsi ujung ke ujung gratis, dan panggilan suara & video tidak terbatas, %(brand)s adalah cara yang baik untuk tetap terhubung.",
|
||||
"Secure messaging for friends and family": "Perpesanan aman untuk teman dan keluarga",
|
||||
"We’d appreciate any feedback on how you’re finding Element.": "Kami akan menghargai masukan apa pun tentang bagaimana Anda menemukan Element.",
|
||||
"How are you finding Element so far?": "Bagaimana Anda menemukan Element sejauh ini?",
|
||||
|
@ -3588,5 +3588,49 @@
|
|||
"Turn off to disable notifications on all your devices and sessions": "Matikan untuk menonaktifkan notifikasi pada semua perangkat dan sesi Anda",
|
||||
"Enable notifications for this account": "Aktifkan notifikasi untuk akun ini",
|
||||
"Video call ended": "Panggilan video berakhir",
|
||||
"%(name)s started a video call": "%(name)s memulai sebuah panggilan video"
|
||||
"%(name)s started a video call": "%(name)s memulai sebuah panggilan video",
|
||||
"URL": "URL",
|
||||
"Version": "Versi",
|
||||
"Application": "Aplikasi",
|
||||
"Record the client name, version, and url to recognise sessions more easily in session manager": "Rekam nama, versi, dan URL klien untuk dapat mengenal sesi dengan lebih muda dalam pengelola sesi",
|
||||
"%(brand)s is end-to-end encrypted, but is currently limited to smaller numbers of users.": "%(brand)s terenkripsi secara ujung ke ujung, tetapi saat ini terbatas jumlah penggunanya.",
|
||||
"Room info": "Informasi ruangan",
|
||||
"View chat timeline": "Tampilkan linimasa obrolan",
|
||||
"Close call": "Tutup panggilan",
|
||||
"Layout type": "Jenis tata letak",
|
||||
"Spotlight": "Sorotan",
|
||||
"Freedom": "Bebas",
|
||||
"Video call (%(brand)s)": "Panggilan video (%(brand)s)",
|
||||
"Unknown session type": "Jenis sesi tidak diketahui",
|
||||
"Web session": "Sesi web",
|
||||
"Mobile session": "Sesi ponsel",
|
||||
"Desktop session": "Sesi desktop",
|
||||
"Operating system": "Sistem operasi",
|
||||
"Model": "Model",
|
||||
"Client": "Klien",
|
||||
"Call type": "Jenis panggilan",
|
||||
"You do not have sufficient permissions to change this.": "Anda tidak memiliki izin untuk mengubah ini.",
|
||||
"Enable %(brand)s as an additional calling option in this room": "Aktifkan %(brand)s sebagai opsi panggilan tambahan di ruangan ini",
|
||||
"Start %(brand)s calls": "Mulai panggilan %(brand)s",
|
||||
"Join %(brand)s calls": "Bergabung panggilan %(brand)s",
|
||||
"Fill screen": "Penuhi layar",
|
||||
"Sorry — this call is currently full": "Maaf — panggilan ini saat ini penuh",
|
||||
"Video call started": "Panggilan video dimulai",
|
||||
"Unknown room": "Ruangan yang tidak diketahui",
|
||||
"Video call started in %(roomName)s. (not supported by this browser)": "Panggilan video dimulai di %(roomName)s. (tidak didukung oleh peramban ini)",
|
||||
"Video call started in %(roomName)s.": "Panggilan video dimulai di %(roomName)s.",
|
||||
"Wysiwyg composer (plain text mode coming soon) (under active development)": "Komposer WYSIWYG (mode teks biasa akan datang) (dalam pengembangan aktif)",
|
||||
"Our new sessions manager provides better visibility of all your sessions, and greater control over them including the ability to remotely toggle push notifications.": "Pengelola sesi kami yang baru memberikan pengelihatan yang lebih baik pada semua sesi Anda, dan pengendalian yang lebih baik pada semua sesi, termasuk kemampuan untuk mensaklar notifikasi dorongan.",
|
||||
"Have greater visibility and control over all your sessions.": "Miliki pengelihatan dan pengendalian yang lebih baik pada semua sesi Anda.",
|
||||
"New session manager": "Pengelola sesi baru",
|
||||
"Use new session manager": "Gunakan pengelola sesi baru",
|
||||
"Sign out all other sessions": "Keluarkan semua sesi lain",
|
||||
"resume voice broadcast": "lanjutkan siaran suara",
|
||||
"pause voice broadcast": "jeda siaran suara",
|
||||
"Underline": "Garis Bawah",
|
||||
"Italic": "Miring",
|
||||
"Try out the rich text editor (plain text mode coming soon)": "Coba editor teks kaya (mode teks biasa akan datang)",
|
||||
"You have already joined this call from another device": "Anda telah bergabung ke panggilan ini dari perangkat lain",
|
||||
"stop voice broadcast": "hentikan siaran suara",
|
||||
"Notifications silenced": "Notifikasi dibisukan"
|
||||
}
|
||||
|
|
|
@ -420,9 +420,9 @@
|
|||
"Share Link to User": "Deila Hlekk að Notanda",
|
||||
"You have verified this user. This user has verified all of their sessions.": "Þú hefur sannreynt þennan notanda. Þessi notandi hefur sannreynt öll tæki þeirra.",
|
||||
"This user has not verified all of their sessions.": "Þessi notandi hefur ekki sannreynt öll tæki þeirra.",
|
||||
"%(count)s verified sessions|one": "1 sannreynt tæki",
|
||||
"%(count)s verified sessions|other": "%(count)s sannreyn tæki",
|
||||
"Hide verified sessions": "Fela sannreynd tæki",
|
||||
"%(count)s verified sessions|one": "1 sannreynd seta",
|
||||
"%(count)s verified sessions|other": "%(count)s sannreyndar setur",
|
||||
"Hide verified sessions": "Fela sannreyndar setur",
|
||||
"Remove recent messages": "Fjarlægja nýleg skilaboð",
|
||||
"Remove recent messages by %(user)s": "Fjarlægja nýleg skilaboð frá %(user)s",
|
||||
"Messages in this room are not end-to-end encrypted.": "Skilaboð í þessari spjallrás eru ekki enda-í-enda dulrituð.",
|
||||
|
@ -1094,7 +1094,7 @@
|
|||
"Topic: %(topic)s ": "Umfjöllunarefni: %(topic)s ",
|
||||
"Insert link": "Setja inn tengil",
|
||||
"Code block": "Kóðablokk",
|
||||
"Poll": "Athuga",
|
||||
"Poll": "Könnun",
|
||||
"Voice Message": "Talskilaboð",
|
||||
"Sticker": "Límmerki",
|
||||
"Hide stickers": "Fela límmerki",
|
||||
|
@ -2971,7 +2971,7 @@
|
|||
"Sends the given message with hearts": "Sendir skilaboðin með hjörtum",
|
||||
"Enable hardware acceleration": "Virkja vélbúnaðarhröðun",
|
||||
"Enable Markdown": "Virkja Markdown",
|
||||
"Live Location Sharing (temporary implementation: locations persist in room history)": "Deiling staðsetninga í rautíma(tímabundið haldast staðsetningar í ferli spjallrása)",
|
||||
"Live Location Sharing (temporary implementation: locations persist in room history)": "Deiling staðsetninga í rautíma (tímabundið haldast staðsetningar í ferli spjallrása)",
|
||||
"Location sharing - pin drop": "Deiling staðsetninga - festipinni",
|
||||
"To leave, return to this page and use the “%(leaveTheBeta)s” button.": "Til að hætta kemurðu einfaldlega aftur á þessa síðu og notar “%(leaveTheBeta)s” hnappinn.",
|
||||
"Use “%(replyInThread)s” when hovering over a message.": "Notaðu “%(replyInThread)s” þegar bendillinn svífur yfir skilaboðum.",
|
||||
|
@ -3131,5 +3131,99 @@
|
|||
"Inviting %(user1)s and %(user2)s": "Býð %(user1)s og %(user2)s",
|
||||
"%(user)s and %(count)s others|one": "%(user)s og 1 annar",
|
||||
"%(user)s and %(count)s others|other": "%(user)s og %(count)s til viðbótar",
|
||||
"%(user1)s and %(user2)s": "%(user1)s og %(user2)s"
|
||||
"%(user1)s and %(user2)s": "%(user1)s og %(user2)s",
|
||||
"%(downloadButton)s or %(copyButton)s": "%(downloadButton)s eða %(copyButton)s",
|
||||
"Did not receive it? <a>Resend it</a>": "Fékkstu hann ekki? <a>Endursenda hann</a>",
|
||||
"Unread email icon": "Táknmynd fyrir ólesinn tölvupóst",
|
||||
"No live locations": "Engar staðsetningar í rauntíma",
|
||||
"Live location error": "Villa í rauntímastaðsetningu",
|
||||
"Live location ended": "Staðsetningu í rauntíma lauk",
|
||||
"Loading live location...": "Hleð inn rauntímastaðsetningu...",
|
||||
"Interactively verify by emoji": "Sannprófa gagnvirkt með táknmyndum",
|
||||
"Manually verify by text": "Sannreyna handvirkt með texta",
|
||||
"%(featureName)s Beta feedback": "%(featureName)s beta umsögn",
|
||||
"Show: %(instance)s rooms (%(server)s)": "Sýna: %(instance)s spjallrásir (%(server)s)",
|
||||
"Who will you chat to the most?": "Við hverja muntu helst spjalla?",
|
||||
"You're in": "Þú ert inni",
|
||||
"Popout widget": "Sprettviðmótshluti",
|
||||
"Un-maximise": "Ekki-hámarka",
|
||||
"Live location sharing": "Deiling staðsetningar í rauntíma",
|
||||
"View live location": "Skoða staðsetningu í rauntíma",
|
||||
"%(name)s started a video call": "%(name)s hóf myndsímtal",
|
||||
"To view %(roomName)s, you need an invite": "Til að skoða %(roomName)s þarftu boð",
|
||||
"Ongoing call": "Símtal í gangi",
|
||||
"Video call (Element Call)": "Myndsímtal (Element Call)",
|
||||
"Video call (Jitsi)": "Myndsímtal (Jitsi)",
|
||||
"Seen by %(count)s people|one": "Séð af %(count)s aðila",
|
||||
"Seen by %(count)s people|other": "Séð af %(count)s aðilum",
|
||||
"Send your first message to invite <displayName/> to chat": "Sendu fyrstu skilaboðin þín til að bjóða <displayName/> að spjalla",
|
||||
"Security recommendations": "Ráðleggingar varðandi öryggi",
|
||||
"%(selectedDeviceCount)s sessions selected": "%(selectedDeviceCount)s setur valdar",
|
||||
"Filter devices": "Sía tæki",
|
||||
"Inactive sessions": "Óvirkar setur",
|
||||
"Verified sessions": "Sannreyndar setur",
|
||||
"Sign out of this session": "Skrá út úr þessari setu",
|
||||
"Receive push notifications on this session.": "Taka á móti ýti-tilkynningum á þessu tæki.",
|
||||
"Toggle push notifications on this session.": "Víxla af/á ýti-tilkynningum á þessu tæki.",
|
||||
"Download %(brand)s": "Sækja %(brand)s",
|
||||
"Start a conversation with someone using their name or username (like <userId/>).": "Byrjaðu samtal með einhverjum með því að nota nafn viðkomandi eða notandanafn (eins og <userId/>).",
|
||||
"Start a conversation with someone using their name, email address or username (like <userId/>).": "Byrjaðu samtal með einhverjum með því að nota nafn viðkomandi, tölvupóstfang eða notandanafn (eins og <userId/>).",
|
||||
"Use an identity server to invite by email. Manage in <settings>Settings</settings>.": "Notaðu auðkennisþjón til að geta boðið með tölvupósti. Sýslaðu með þetta í <settings>stillingunum</settings>.",
|
||||
"Something went wrong trying to invite the users.": "Eitthvað fór úrskeiðis við að bjóða notendunum.",
|
||||
"Upgrade to %(hostSignupBrand)s": "Uppfæra í %(hostSignupBrand)s",
|
||||
"Size can only be a number between %(min)s MB and %(max)s MB": "Stærð getur aðeins verið tala á milli %(min)s og %(max)s",
|
||||
"The poll has ended. Top answer: %(topAnswer)s": "Könnuninni er lokið. Efsta svarið: %(topAnswer)s",
|
||||
"Send custom timeline event": "Senda sérsniðinn tímalínuatburð",
|
||||
"You will no longer be able to log in": "Þú munt ekki lengur geta skráð þig inn",
|
||||
"You will not be able to reactivate your account": "Þú munt ekki geta endurvirkjað aðganginn þinn",
|
||||
"Google Play and the Google Play logo are trademarks of Google LLC.": "Google Play og Google Play táknmerkið eru vörumerki í eigu Google LLC.",
|
||||
"App Store® and the Apple logo® are trademarks of Apple Inc.": "App Store® og Apple logo® eru vörumerki í eigu Apple Inc.",
|
||||
"%(severalUsers)ssent %(count)s hidden messages|one": "%(severalUsers)ssendu falin skilaboð",
|
||||
"%(severalUsers)ssent %(count)s hidden messages|other": "%(severalUsers)ssendu %(count)s falin skilaboð",
|
||||
"%(severalUsers)schanged the <a>pinned messages</a> for the room %(count)s times|one": "%(severalUsers)sbreyttu <a>föstum skilaboðum</a> fyrir spjallrásina",
|
||||
"%(severalUsers)schanged the <a>pinned messages</a> for the room %(count)s times|other": "%(severalUsers)sbreyttu <a>föstum skilaboðum</a> fyrir spjallrásina %(count)s sinnum",
|
||||
"%(oneUser)srejected their invitation %(count)s times|other": "%(oneUser)shafnaði boði sínu %(count)s sinnum",
|
||||
"%(severalUsers)srejected their invitations %(count)s times|other": "%(severalUsers)shöfnuðu boðum þeirra %(count)s sinnum",
|
||||
"Using this widget may share data <helpIcon /> with %(widgetDomain)s.": "Að nota þennan viðmótshluta gæti deilt gögnum <helpIcon /> með %(widgetDomain)s.",
|
||||
"Any of the following data may be shared:": "Eftirfarandi gögnum gæti verið deilt:",
|
||||
"You don't have permission to share locations": "Þú hefur ekki heimildir til að deila staðsetningum",
|
||||
"Enable live location sharing": "Virkja deilingu rauntímastaðsetninga",
|
||||
"Messages in this chat will be end-to-end encrypted.": "Skilaboð í þessu spjalli verða enda-í-enda dulrituð.",
|
||||
"%(qrCode)s or %(emojiCompare)s": "%(qrCode)s eða %(emojiCompare)s",
|
||||
"If you can't find the room you're looking for, ask for an invite or <a>create a new room</a>.": "Ef þú finnur ekki spjallrásina sem þú leitar að, skaltu biðja um boð eða <a>útbúa nýja spjallrás</a>.",
|
||||
"Try different words or check for typos. Some results may not be visible as they're private and you need an invite to join them.": "Prófaðu önnur orð og aðgættu stafsetningu. Sumar niðurstöður gætu verið faldar þar sem þær eru einkamál og þá þarftu boð til að geta séð þær.",
|
||||
"Joining the beta will reload %(brand)s.": "Ef tekið er þátt í beta-prófunum verður %(brand)s endurhlaðið.",
|
||||
"Results not as expected? Please <a>give feedback</a>.": "Eru leitarniðurstöður ekki eins og þú áttir von á? <a>Láttu okkur vita</a>.",
|
||||
"If you can't find the room you're looking for, ask for an invite or create a new room.": "Ef þú finnur ekki spjallrásina sem þú leitar að, skaltu biðja um boð eða útbúa nýja spjallrás.",
|
||||
"If you can't see who you're looking for, send them your invite link.": "Ef þú sérð ekki þann sem þú ert að leita að, ættirðu að senda viðkomandi boðstengil.",
|
||||
"Some results may be hidden for privacy": "Sumar niðurstöður gætu verið faldar þar sem þær eru einkamál",
|
||||
"Add widgets, bridges & bots": "Bæta við viðmótshlutum, brúm og vélmennum",
|
||||
"Edit widgets, bridges & bots": "Breyta viðmótshlutum, brúm og vélmennum",
|
||||
"Close this widget to view it in this panel": "Lokaðu þessum viðmótshluta til að sjá hann á þessu spjaldi",
|
||||
"Unpin this widget to view it in this panel": "Losaðu þennan viðmótshluta til að sjá hann á þessu spjaldi",
|
||||
"Explore public spaces in the new search dialog": "Kannaðu opimber svæði í nýja leitarglugganum",
|
||||
"Yes, the chat timeline is displayed alongside the video.": "Já, tímalína spjallsins birtist við hlið myndmerkisins.",
|
||||
"Can I use text chat alongside the video call?": "Get ég notað textaspjall samhliða myndsímtali?",
|
||||
"Use the “+” button in the room section of the left panel.": "Notaðu “+” hnappinn í spjallrásarhluta hliðarspjaldsins vinstra megin.",
|
||||
"Video rooms are always-on VoIP channels embedded within a room in %(brand)s.": "Myndspjallrásir eru sívirkar VoIP-rásir sem ívafðar eru í spjallrásir innan %(brand)s.",
|
||||
"A new way to chat over voice and video in %(brand)s.": "Ný leið til að spjalla með tali og myndmerki í %(brand)s.",
|
||||
"How are you finding %(brand)s so far?": "Hvernig líst þér á %(brand)s hingað til?",
|
||||
"Turn on notifications": "Kveikja á tilkynningum",
|
||||
"Make sure people know it’s really you": "Láttu fólk vita að þetta sért þú",
|
||||
"Set up your profile": "Settu upp notandasniðið þitt",
|
||||
"Find and invite your co-workers": "Finndu og bjóddu samstarfsaðilum þínum",
|
||||
"Do you want to enable threads anyway?": "Viltu samt virkja spjallþræði?",
|
||||
"Partial Support for Threads": "Hlutastuðningur við þræði",
|
||||
"Use new session manager (under active development)": "Ný setustýring (í virkri þróun)",
|
||||
"Voice broadcast (under active development)": "Útvörpun tals (í virkri þróun)",
|
||||
"Favourite Messages (under active development)": "Eftirlætisskilaboð (í virkri þróun)",
|
||||
"Show HTML representation of room topics": "Birta HTML-framsetningu umfjöllunarefnis spjallrása",
|
||||
"You were disconnected from the call. (Error: %(message)s)": "Þú varst aftengd/ur frá samtalinu. (Villa: %(message)s)",
|
||||
"Reset bearing to north": "Frumstilla stefnu á norður",
|
||||
"Toggle attribution": "Víxla tilvísun af/á",
|
||||
"Unable to look up room ID from server": "Get ekki flett upp auðkenni spjallrásar á þjóninum",
|
||||
"In %(spaceName)s and %(count)s other spaces.|one": "Á %(spaceName)s og %(count)s svæði til viðbótar.",
|
||||
"In %(spaceName)s and %(count)s other spaces.|zero": "Á svæðinu %(spaceName)s.",
|
||||
"In %(spaceName)s and %(count)s other spaces.|other": "Á %(spaceName)s og %(count)s svæðum til viðbótar.",
|
||||
"In spaces %(space1Name)s and %(space2Name)s.": "Á svæðunum %(space1Name)s og %(space2Name)s."
|
||||
}
|
||||
|
|
|
@ -1634,7 +1634,7 @@
|
|||
"Looks good!": "Sembra giusta!",
|
||||
"Use custom size": "Usa dimensione personalizzata",
|
||||
"Hey you. You're the best!": "Ehi tu. Sei il migliore!",
|
||||
"Message layout": "Layout messaggio",
|
||||
"Message layout": "Disposizione del messaggio",
|
||||
"Modern": "Moderno",
|
||||
"Use a system font": "Usa un carattere di sistema",
|
||||
"System font name": "Nome carattere di sistema",
|
||||
|
@ -3589,5 +3589,51 @@
|
|||
"Enable notifications for this account": "Attiva le notifiche per questo account",
|
||||
"Video call ended": "Videochiamata terminata",
|
||||
"%(name)s started a video call": "%(name)s ha iniziato una videochiamata",
|
||||
"Record the client name, version, and url to recognise sessions more easily in session manager": "Registra il nome, la versione e l'url del client per riconoscere le sessioni più facilmente nel gestore di sessioni"
|
||||
"Record the client name, version, and url to recognise sessions more easily in session manager": "Registra il nome, la versione e l'url del client per riconoscere le sessioni più facilmente nel gestore di sessioni",
|
||||
"URL": "URL",
|
||||
"Version": "Versione",
|
||||
"Application": "Applicazione",
|
||||
"Unknown session type": "Tipo di sessione sconosciuta",
|
||||
"Web session": "Sessione web",
|
||||
"Mobile session": "Sessione mobile",
|
||||
"Desktop session": "Sessione desktop",
|
||||
"Video call started in %(roomName)s. (not supported by this browser)": "Videochiamata iniziata in %(roomName)s. (non supportata da questo browser)",
|
||||
"Video call started in %(roomName)s.": "Videochiamata iniziata in %(roomName)s.",
|
||||
"Room info": "Info stanza",
|
||||
"View chat timeline": "Vedi linea temporale chat",
|
||||
"Close call": "Chiudi chiamata",
|
||||
"Layout type": "Tipo di disposizione",
|
||||
"Spotlight": "Riflettore",
|
||||
"Freedom": "Libertà",
|
||||
"Operating system": "Sistema operativo",
|
||||
"Model": "Modello",
|
||||
"Client": "Client",
|
||||
"Fill screen": "Riempi schermo",
|
||||
"Video call started": "Videochiamata iniziata",
|
||||
"Unknown room": "Stanza sconosciuta",
|
||||
"Video call (%(brand)s)": "Videochiamata (%(brand)s)",
|
||||
"Call type": "Tipo chiamata",
|
||||
"You do not have sufficient permissions to change this.": "Non hai autorizzazioni sufficienti per cambiarlo.",
|
||||
"%(brand)s is end-to-end encrypted, but is currently limited to smaller numbers of users.": "%(brand)s è crittografato end-to-end, ma attualmente è limitato ad un minore numero di utenti.",
|
||||
"Enable %(brand)s as an additional calling option in this room": "Attiva %(brand)s come opzione di chiamata aggiuntiva in questa stanza",
|
||||
"Join %(brand)s calls": "Entra in chiamate di %(brand)s",
|
||||
"Start %(brand)s calls": "Inizia chiamate di %(brand)s",
|
||||
"Sorry — this call is currently full": "Spiacenti — questa chiamata è piena",
|
||||
"Wysiwyg composer (plain text mode coming soon) (under active development)": "Compositore wysiwyg (modalità a testo semplice in arrivo) (in sviluppo attivo)",
|
||||
"Sign out all other sessions": "Disconnetti tutte le altre sessioni",
|
||||
"Our new sessions manager provides better visibility of all your sessions, and greater control over them including the ability to remotely toggle push notifications.": "Il nostro nuovo gestore di sessioni offre una migliore visibilità e un maggiore controllo sulle tue sessioni, inclusa la possibilità di attivare/disattivare da remoto le notifiche push.",
|
||||
"Have greater visibility and control over all your sessions.": "Maggiore visibilità e controllo su tutte le tue sessioni.",
|
||||
"New session manager": "Nuovo gestore di sessioni",
|
||||
"Use new session manager": "Usa nuovo gestore di sessioni",
|
||||
"Underline": "Sottolineato",
|
||||
"Italic": "Corsivo",
|
||||
"Try out the rich text editor (plain text mode coming soon)": "Prova l'editor in rich text (il testo semplice è in arrivo)",
|
||||
"resume voice broadcast": "riprendi broadcast voce",
|
||||
"pause voice broadcast": "sospendi broadcast voce",
|
||||
"You have already joined this call from another device": "Sei già in questa chiamata in un altro dispositivo",
|
||||
"Notifications silenced": "Notifiche silenziose",
|
||||
"stop voice broadcast": "ferma broadcast voce",
|
||||
"Yes, stop broadcast": "Sì, ferma il broadcast",
|
||||
"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.": "Vuoi davvero fermare il tuo broadcast in diretta? Verrà terminato il broadcast e la registrazione completa sarà disponibile nella stanza.",
|
||||
"Stop live broadcasting?": "Fermare il broadcast in diretta?"
|
||||
}
|
||||
|
|
1
src/i18n/strings/lb.json
Normal file
1
src/i18n/strings/lb.json
Normal file
|
@ -0,0 +1 @@
|
|||
{}
|
|
@ -2828,5 +2828,86 @@
|
|||
"Inviting %(user1)s and %(user2)s": "Convidando %(user1)s e %(user2)s",
|
||||
"%(user1)s and %(user2)s": "%(user1)s e %(user2)s",
|
||||
"%(value)sh": "%(value)sh",
|
||||
"%(value)sd": "%(value)sd"
|
||||
"%(value)sd": "%(value)sd",
|
||||
"Live": "Ao vivo",
|
||||
"%(senderName)s has ended a poll": "%(senderName)s encerrou uma enquete",
|
||||
"%(senderName)s has started a poll - %(pollQuestion)s": "%(senderName)s começou uma enquete - %(pollQuestion)s",
|
||||
"%(senderName)s has shared their location": "%(senderName)s compartilhou sua localização",
|
||||
"%(senderName)s removed %(targetName)s": "%(senderName)s removeu %(targetName)s",
|
||||
"%(senderName)s removed %(targetName)s: %(reason)s": "%(senderName)s removeu %(targetName)s: %(reason)s",
|
||||
"Video call started in %(roomName)s. (not supported by this browser)": "Chamada de vídeo iniciada em %(roomName)s. (não compatível com este navegador)",
|
||||
"Video call started in %(roomName)s.": "Chamada de vídeo iniciada em %(roomName)s.",
|
||||
"No active call in this room": "Nenhuma chamada ativa nesta sala",
|
||||
"Unable to find Matrix ID for phone number": "Não foi possível encontrar o ID Matrix pelo número de telefone",
|
||||
"Unknown (user, session) pair: (%(userId)s, %(deviceId)s)": "Par desconhecido (usuário, sessão): (%(userId)s, %(deviceId)s)",
|
||||
"Command failed: Unable to find room (%(roomId)s": "Falha no comando: Não foi possível encontrar sala %(roomId)s",
|
||||
"Removes user with given id from this room": "Remove desta sala o usuário com o ID determinado",
|
||||
"Unrecognised room address: %(roomAlias)s": "Endereço da sala não reconhecido: %(roomAlias)s",
|
||||
"Failed to get room topic: Unable to find room (%(roomId)s": "Falha ao obter tópico da sala: Não foi possível encontrar a sala %(roomId)s",
|
||||
"We were unable to understand the given date (%(inputDate)s). Try using the format YYYY-MM-DD.": "Não foi possível entender a data fornecida (%(inputDate)s). Tente usando o formato AAAA-MM-DD.",
|
||||
"You need to be able to kick users to do that.": "Você precisa ter permissão de expulsar usuários para fazer isso.",
|
||||
"Failed to invite users to %(roomName)s": "Falha ao convidar usuários para %(roomName)s",
|
||||
"Inviting %(user)s and %(count)s others|one": "Convidando %(user)s e 1 outro",
|
||||
"Inviting %(user)s and %(count)s others|other": "Convidando %(user)s e %(count)s outros",
|
||||
"%(user)s and %(count)s others|one": "%(user)s e 1 outro",
|
||||
"%(user)s and %(count)s others|other": "%(user)s e %(count)s outros",
|
||||
"You were disconnected from the call. (Error: %(message)s)": "Você foi desconectado da chamada. (Erro: %(message)s)",
|
||||
"Remove messages sent by me": "",
|
||||
"Welcome": "Boas-vindas",
|
||||
"%(count)s people joined|one": "%(count)s pessoa entrou",
|
||||
"%(count)s people joined|other": "%(count)s pessoas entraram",
|
||||
"Audio devices": "Dispositivos de áudio",
|
||||
"Unmute microphone": "Habilitar microfone",
|
||||
"Mute microphone": "Silenciar microfone",
|
||||
"Video devices": "Dispositivos de vídeo",
|
||||
"Make sure people know it’s really you": "Certifique-se de que as pessoas saibam que é realmente você",
|
||||
"Set up your profile": "Configure seu perfil",
|
||||
"Video rooms": "Salas de vídeo",
|
||||
"Room members": "Membros da sala",
|
||||
"Back to chat": "Voltar ao chat",
|
||||
"Connection lost": "Conexão perdida",
|
||||
"Failed to join": "Falha ao entrar",
|
||||
"The person who invited you has already left, or their server is offline.": "A pessoa que o convidou já saiu ou o servidor dela está offline.",
|
||||
"The person who invited you has already left.": "A pessoa que o convidou já saiu.",
|
||||
"There was an error joining.": "Ocorreu um erro ao entrar.",
|
||||
"Video": "Vídeo",
|
||||
"Video call started": "Videochamada iniciada",
|
||||
"Unknown room": "Sala desconhecida",
|
||||
"Location not available": "Local não disponível",
|
||||
"Find my location": "Encontrar minha localização",
|
||||
"Exit fullscreen": "Sair da tela cheia",
|
||||
"Enter fullscreen": "Entrar em tela cheia",
|
||||
"User may or may not exist": "O usuário pode ou não existir",
|
||||
"User is already invited to the room": "O usuário já foi convidado para a sala",
|
||||
"User is already invited to the space": "O usuário já foi convidado para o espaço",
|
||||
"You do not have permission to invite people to this space.": "Você não tem permissão para convidar pessoas para este espaço.",
|
||||
"In %(spaceName)s and %(count)s other spaces.|one": "Em %(spaceName)s e %(count)s outro espaço.",
|
||||
"In %(spaceName)s and %(count)s other spaces.|zero": "No espaço %(spaceName)s.",
|
||||
"In %(spaceName)s and %(count)s other spaces.|other": "Em %(spaceName)s e %(count)s outros espaços.",
|
||||
"In spaces %(space1Name)s and %(space2Name)s.": "Nos espaços %(space1Name)s e %(space2Name)s.",
|
||||
"Empty room (was %(oldName)s)": "Sala vazia (era %(oldName)s)",
|
||||
"Unknown session type": "Tipo de sessão desconhecido",
|
||||
"Mobile session": "Sessão móvel",
|
||||
"Desktop session": "Sessão desktop",
|
||||
"Web session": "Sessão web",
|
||||
"Sign out of this session": "Sair desta sessão",
|
||||
"Operating system": "Sistema operacional",
|
||||
"Model": "Modelo",
|
||||
"URL": "URL",
|
||||
"Version": "Versão",
|
||||
"Application": "Aplicação",
|
||||
"Last activity": "Última atividade",
|
||||
"Client": "Cliente",
|
||||
"Confirm signing out these devices|other": "Confirme a saída destes dispositivos",
|
||||
"Confirm signing out these devices|one": "Confirme a saída deste dispositivo",
|
||||
"Sign out all other sessions": "Sair de todas as outras sessões",
|
||||
"Current session": "Sessão atual",
|
||||
"Developer tools": "Ferramentas de desenvolvimento",
|
||||
"Welcome to %(brand)s": "Boas-vindas ao",
|
||||
"Processing event %(number)s out of %(total)s": "Processando evento %(number)s de %(total)s",
|
||||
"Exported %(count)s events in %(seconds)s seconds|one": "%(count)s evento exportado em %(seconds)s segundos",
|
||||
"Exported %(count)s events in %(seconds)s seconds|other": "%(count)s eventos exportados em %(seconds)s segundos",
|
||||
"Creating output...": "Criando resultado...",
|
||||
"Fetching events...": "Buscando eventos...",
|
||||
"Starting export process...": "Iniciando processo de exportação..."
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
"Logout": "Выйти",
|
||||
"Low priority": "Маловажные",
|
||||
"Moderator": "Модератор",
|
||||
"Name": "Имя",
|
||||
"Name": "Название",
|
||||
"New passwords must match each other.": "Новые пароли должны совпадать.",
|
||||
"Notifications": "Уведомления",
|
||||
"<not supported>": "<не поддерживается>",
|
||||
|
@ -187,7 +187,7 @@
|
|||
"You may need to manually permit %(brand)s to access your microphone/webcam": "Вам необходимо предоставить %(brand)s доступ к микрофону или веб-камере вручную",
|
||||
"Anyone": "Все",
|
||||
"Are you sure you want to leave the room '%(roomName)s'?": "Уверены, что хотите покинуть '%(roomName)s'?",
|
||||
"%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s удалил(а) имя комнаты.",
|
||||
"%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s удалил(а) название комнаты.",
|
||||
"Custom level": "Специальные права",
|
||||
"Email address": "Электронная почта",
|
||||
"Error decrypting attachment": "Ошибка расшифровки вложения",
|
||||
|
@ -195,7 +195,7 @@
|
|||
"Import": "Импорт",
|
||||
"Incorrect username and/or password.": "Неверное имя пользователя и/или пароль.",
|
||||
"Invalid file%(extra)s": "Недопустимый файл%(extra)s",
|
||||
"Invited": "Приглашен",
|
||||
"Invited": "Приглашены",
|
||||
"Jump to first unread message.": "Перейти к первому непрочитанному сообщению.",
|
||||
"Privileged Users": "Привилегированные пользователи",
|
||||
"Register": "Зарегистрироваться",
|
||||
|
@ -613,7 +613,7 @@
|
|||
"Timeline": "Лента сообщений",
|
||||
"Autocomplete delay (ms)": "Задержка автодополнения (мс)",
|
||||
"Roles & Permissions": "Роли и права",
|
||||
"Security & Privacy": "Безопасность и приватность",
|
||||
"Security & Privacy": "Безопасность",
|
||||
"Encryption": "Шифрование",
|
||||
"Encrypted": "Зашифровано",
|
||||
"Ignored users": "Игнорируемые пользователи",
|
||||
|
@ -1087,7 +1087,7 @@
|
|||
"%(count)s unread messages including mentions.|other": "%(count)s непрочитанных сообщения(-й), включая упоминания.",
|
||||
"Failed to deactivate user": "Не удалось деактивировать пользователя",
|
||||
"This client does not support end-to-end encryption.": "Этот клиент не поддерживает сквозное шифрование.",
|
||||
"Messages in this room are not end-to-end encrypted.": "Сообщения в этой комнате не шифруются сквозным шифрованием.",
|
||||
"Messages in this room are not end-to-end encrypted.": "Сообщения в этой комнате не защищены сквозным шифрованием.",
|
||||
"Please <newIssueLink>create a new issue</newIssueLink> on GitHub so that we can investigate this bug.": "Пожалуйста, <newIssueLink> создайте новую проблему/вопрос </newIssueLink> на GitHub, чтобы мы могли расследовать эту ошибку.",
|
||||
"Use an identity server to invite by email. <default>Use the default (%(defaultIdentityServerName)s)</default> or manage in <settings>Settings</settings>.": "Используйте идентификационный сервер для приглашения по электронной почте. <default>Используйте значение по умолчанию (%(defaultIdentityServerName)s)</default> или управляйте в <settings>Настройках</settings>.",
|
||||
"Use an identity server to invite by email. Manage in <settings>Settings</settings>.": "Используйте идентификационный сервер для приглашения по электронной почте. Управление в <settings>Настройки</settings>.",
|
||||
|
@ -1352,7 +1352,7 @@
|
|||
"Waiting for %(displayName)s to accept…": "Ожидание принятия от %(displayName)s…",
|
||||
"Accepting…": "Принятие…",
|
||||
"Start Verification": "Начать проверку",
|
||||
"Messages in this room are end-to-end encrypted.": "Сообщения в этой комнате зашифрованы сквозным шифрованием.",
|
||||
"Messages in this room are end-to-end encrypted.": "Сообщения в этой комнате защищены сквозным шифрованием.",
|
||||
"Verify User": "Подтвердить пользователя",
|
||||
"Your messages are not secure": "Ваши сообщения не защищены",
|
||||
"Your homeserver": "Ваш домашний сервер",
|
||||
|
@ -1516,7 +1516,7 @@
|
|||
"Favourited": "В избранном",
|
||||
"Room options": "Настройки комнаты",
|
||||
"Welcome to %(appName)s": "Добро пожаловать в %(appName)s",
|
||||
"Create a Group Chat": "Создать групповой чат",
|
||||
"Create a Group Chat": "Создать комнату",
|
||||
"All settings": "Все настройки",
|
||||
"Feedback": "Отзыв",
|
||||
"* %(senderName)s %(emote)s": "* %(senderName)s %(emote)s",
|
||||
|
@ -1615,8 +1615,8 @@
|
|||
"Wrong file type": "Неправильный тип файла",
|
||||
"Looks good!": "Выглядит неплохо!",
|
||||
"Security Phrase": "Секретная фраза",
|
||||
"Security Key": "Ключ безопасности",
|
||||
"Use your Security Key to continue.": "Чтобы продолжить, используйте свой ключ безопасности.",
|
||||
"Security Key": "Бумажный ключ",
|
||||
"Use your Security Key to continue.": "Чтобы продолжить, используйте свой бумажный ключ.",
|
||||
"Restoring keys from backup": "Восстановление ключей из резервной копии",
|
||||
"Fetching keys from server...": "Получение ключей с сервера...",
|
||||
"%(completed)s of %(total)s keys restored": "%(completed)s из %(total)s ключей восстановлено",
|
||||
|
@ -2753,7 +2753,7 @@
|
|||
"Resetting your verification keys cannot be undone. After resetting, you won't have access to old encrypted messages, and any friends who have previously verified you will see security warnings until you re-verify with them.": "Сброс ключей проверки нельзя отменить. После сброса вы не сможете получить доступ к старым зашифрованным сообщениям, а друзья, которые ранее проверили вас, будут видеть предупреждения о безопасности, пока вы не пройдете повторную проверку.",
|
||||
"Skip verification for now": "Пока пропустить проверку",
|
||||
"I'll verify later": "Я проверю позже",
|
||||
"Verify with Security Key": "Заверить с помощью ключа безопасности",
|
||||
"Verify with Security Key": "Заверить бумажным ключом",
|
||||
"Verify with Security Key or Phrase": "Проверка с помощью ключа безопасности или фразы",
|
||||
"Proceed with reset": "Выполнить сброс",
|
||||
"It looks like you don't have a Security Key or any other devices you can verify against. This device will not be able to access old encrypted messages. In order to verify your identity on this device, you'll need to reset your verification keys.": "Похоже, у вас нет ключа шифрования, или каких-либо других устройств, которые вы можете проверить. Это устройство не сможет получить доступ к старым зашифрованным сообщениям. Чтобы подтвердить свою личность на этом устройстве, вам потребуется сбросить ключи подтверждения.",
|
||||
|
@ -2827,11 +2827,11 @@
|
|||
"We'll generate a Security Key for you to store somewhere safe, like a password manager or a safe.": "Мы создадим ключ безопасности для вас, чтобы вы могли хранить его в надежном месте, например, в менеджере паролей или сейфе.",
|
||||
"Regain access to your account and recover encryption keys stored in this session. Without them, you won't be able to read all of your secure messages in any session.": "Восстановите доступ к своей учетной записи и восстановите ключи шифрования, сохраненные в этом сеансе. Без них вы не сможете прочитать все свои защищенные сообщения в любой сессии.",
|
||||
"Without verifying, you won't have access to all your messages and may appear as untrusted to others.": "Без проверки вы не сможете получить доступ ко всем своим сообщениям и можете показаться другим людям недоверенным.",
|
||||
"Your new device is now verified. Other users will see it as trusted.": "Теперь ваше новое устройство проверено. Другие пользователи будут видеть его как доверенное.",
|
||||
"Your new device is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Теперь ваше новое устройство проверено. Оно имеет доступ к вашим зашифрованным сообщениям, и другие пользователи будут воспринимать его как доверенное.",
|
||||
"Your new device is now verified. Other users will see it as trusted.": "Ваша новая сессия подтверждена. Другие пользователи будут воспринимать её как заверенную.",
|
||||
"Your new device is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Ваша новая сессия подтверждена. Она имеет доступ к вашим зашифрованным сообщениям, и другие пользователи будут воспринимать её как заверенную.",
|
||||
"Verify with another device": "Заверить с помощью другого устройства",
|
||||
"Someone already has that username, please try another.": "У кого-то уже есть такое имя пользователя, пожалуйста, попробуйте другое.",
|
||||
"Device verified": "Устройство заверено",
|
||||
"Device verified": "Сессия заверена",
|
||||
"Verify this device": "Заверьте эту сессию",
|
||||
"Unable to verify this device": "Не удалось проверить это устройство",
|
||||
"Show all threads": "Показать все обсуждения",
|
||||
|
@ -3547,12 +3547,50 @@
|
|||
"%(downloadButton)s or %(copyButton)s": "%(downloadButton)s или %(copyButton)s",
|
||||
"Sign out of this session": "Выйти из этой сессии",
|
||||
"Please be aware that session names are also visible to people you communicate with": "Пожалуйста, имейте в виду, что названия сессий также видны людям, с которыми вы общаетесь",
|
||||
"Push notifications": "Push-уведомления",
|
||||
"Push notifications": "Уведомления",
|
||||
"Receive push notifications on this session.": "Получать push-уведомления в этой сессии.",
|
||||
"Toggle push notifications on this session.": "Push-уведомления для этой сессии.",
|
||||
"Enable notifications for this device": "Уведомления для этой сессии",
|
||||
"Enable notifications for this account": "Уведомления для этой учётной записи",
|
||||
"Turn off to disable notifications on all your devices and sessions": "Выключите, чтобы отключить уведомления во всех своих сессиях",
|
||||
"Failed to set pusher state": "Не удалось установить состояние push-службы",
|
||||
"%(selectedDeviceCount)s sessions selected": "Выбрано сессий: %(selectedDeviceCount)s"
|
||||
"%(selectedDeviceCount)s sessions selected": "Выбрано сессий: %(selectedDeviceCount)s",
|
||||
"Application": "Приложение",
|
||||
"Version": "Версия",
|
||||
"URL": "URL-адрес",
|
||||
"Client": "Клиент",
|
||||
"Room info": "О комнате",
|
||||
"Wysiwyg composer (plain text mode coming soon) (under active development)": "Редактор «Что видишь, то и получишь» (скоро появится режим обычного текста) (в активной разработке)",
|
||||
"New session manager": "Новый менеджер сессий",
|
||||
"Operating system": "Операционная система",
|
||||
"Element Call video rooms": "Видеокомнаты Element Call",
|
||||
"Video call (Jitsi)": "Видеозвонок (Jitsi)",
|
||||
"Unknown session type": "Неизвестный тип сессии",
|
||||
"Unknown room": "Неизвестная комната",
|
||||
"View chat timeline": "Посмотреть ленту сообщений",
|
||||
"Model": "Модель",
|
||||
"Live": "В эфире",
|
||||
"Video call (%(brand)s)": "Видеозвонок (%(brand)s)",
|
||||
"Voice broadcast (under active development)": "Аудиовещание (в активной разработке)",
|
||||
"Use new session manager": "Использовать новый менеджер сессий",
|
||||
"Sign out all other sessions": "Выйти из всех других сессий",
|
||||
"Voice broadcasts": "Аудиопередачи",
|
||||
"Voice broadcast": "Аудиопередача",
|
||||
"Have greater visibility and control over all your sessions.": "Получите наилучшую видимость и контроль над всеми вашими сеансами.",
|
||||
"New group call experience": "Новый опыт группового вызова",
|
||||
"Sliding Sync mode (under active development, cannot be disabled)": "Скользящий режим синхронизации (в активной разработке, не может быть отключен)",
|
||||
"Video call started": "Начался видеозвонок",
|
||||
"Video call started in %(roomName)s. (not supported by this browser)": "Видеовызов начался в %(roomName)s. (не поддерживается этим браузером)",
|
||||
"Video call started in %(roomName)s.": "Видеовызов начался в %(roomName)s.",
|
||||
"You need to be able to kick users to do that.": "Вы должны иметь возможность пинать пользователей, чтобы сделать это.",
|
||||
"Inviting %(user)s and %(count)s others|one": "Приглашающий %(user)s и 1 других",
|
||||
"Inviting %(user)s and %(count)s others|other": "Приглашение %(user)s и %(count)s других",
|
||||
"Inviting %(user1)s and %(user2)s": "Приглашение %(user1)s и %(user2)s",
|
||||
"Fill screen": "Заполнить экран",
|
||||
"Sorry — this call is currently full": "Извините — этот вызов в настоящее время заполнен",
|
||||
"Record the client name, version, and url to recognise sessions more easily in session manager": "Записывать название клиента, версию и URL-адрес для более лёгкого распознавания сессий в менеджере сессий",
|
||||
"Our new sessions manager provides better visibility of all your sessions, and greater control over them including the ability to remotely toggle push notifications.": "Наш новый менеджер сеансов обеспечивает лучшую видимость всех ваших сеансов и больший контроль над ними, включая возможность удаленного переключения push-уведомлений.",
|
||||
"Try out the rich text editor (plain text mode coming soon)": "Попробуйте визуальный редактор текста (скоро появится обычный текстовый режим)",
|
||||
"Italic": "Курсив",
|
||||
"Underline": "Подчёркнутый"
|
||||
}
|
||||
|
|
|
@ -3504,7 +3504,7 @@
|
|||
"Session details": "Podrobnosti o relácii",
|
||||
"IP address": "IP adresa",
|
||||
"Device": "Zariadenie",
|
||||
"For best security, verify your sessions and sign out from any session that you don't recognize or use anymore.": "V záujme čo najlepšieho zabezpečenia overte svoje relácie a odhláste sa z každej relácie, ktorú už nepoznáte alebo nepoužívate.",
|
||||
"For best security, verify your sessions and sign out from any session that you don't recognize or use anymore.": "V záujme čo najlepšieho zabezpečenia, overte svoje relácie a odhláste sa z každej relácie, ktorú už nepoznáte alebo nepoužívate.",
|
||||
"Other sessions": "Iné relácie",
|
||||
"Verify or sign out from this session for best security and reliability.": "V záujme čo najvyššej bezpečnosti a spoľahlivosti túto reláciu overte alebo sa z nej odhláste.",
|
||||
"Unverified session": "Neoverená relácia",
|
||||
|
@ -3588,5 +3588,49 @@
|
|||
"Enable notifications for this account": "Povoliť oznámenia pre tento účet",
|
||||
"%(selectedDeviceCount)s sessions selected": "%(selectedDeviceCount)s vybratých relácií",
|
||||
"Video call ended": "Videohovor ukončený",
|
||||
"%(name)s started a video call": "%(name)s začal/a videohovor"
|
||||
"%(name)s started a video call": "%(name)s začal/a videohovor",
|
||||
"URL": "URL",
|
||||
"Version": "Verzia",
|
||||
"Application": "Aplikácia",
|
||||
"Record the client name, version, and url to recognise sessions more easily in session manager": "Zaznamenať názov klienta, verziu a url, aby bolo možné ľahšie rozpoznať relácie v správcovi relácií",
|
||||
"Unknown session type": "Neznámy typ relácie",
|
||||
"Web session": "Webová relácia",
|
||||
"Mobile session": "Relácia na mobile",
|
||||
"Desktop session": "Relácia stolného počítača",
|
||||
"Video call started": "Videohovor bol spustený",
|
||||
"Unknown room": "Neznáma miestnosť",
|
||||
"Video call started in %(roomName)s. (not supported by this browser)": "Videohovor sa začal v %(roomName)s. (nie je podporované v tomto prehliadači)",
|
||||
"Video call started in %(roomName)s.": "Videohovor sa začal v %(roomName)s.",
|
||||
"Close call": "Zavrieť hovor",
|
||||
"Room info": "Informácie o miestnosti",
|
||||
"View chat timeline": "Zobraziť časovú os konverzácie",
|
||||
"Layout type": "Typ rozmiestnenia",
|
||||
"Spotlight": "Stredobod",
|
||||
"Freedom": "Sloboda",
|
||||
"Video call (%(brand)s)": "Videohovor (%(brand)s)",
|
||||
"Operating system": "Operačný systém",
|
||||
"Model": "Model",
|
||||
"Client": "Klient",
|
||||
"Call type": "Typ hovoru",
|
||||
"You do not have sufficient permissions to change this.": "Nemáte dostatočné oprávnenia na to, aby ste toto mohli zmeniť.",
|
||||
"%(brand)s is end-to-end encrypted, but is currently limited to smaller numbers of users.": "%(brand)s je end-to-end šifrovaný, ale v súčasnosti je obmedzený pre menší počet používateľov.",
|
||||
"Enable %(brand)s as an additional calling option in this room": "Zapnúť %(brand)s ako ďalšiu možnosť volania v tejto miestnosti",
|
||||
"Join %(brand)s calls": "Pripojiť sa k %(brand)s hovorom",
|
||||
"Start %(brand)s calls": "Spustiť %(brand)s hovory",
|
||||
"Fill screen": "Vyplniť obrazovku",
|
||||
"Sorry — this call is currently full": "Prepáčte — tento hovor je momentálne obsadený",
|
||||
"Our new sessions manager provides better visibility of all your sessions, and greater control over them including the ability to remotely toggle push notifications.": "Náš nový správca relácií poskytuje lepší prehľad o všetkých vašich reláciách a lepšiu kontrolu nad nimi vrátane možnosti vzdialene prepínať push oznámenia.",
|
||||
"Have greater visibility and control over all your sessions.": "Majte lepší prehľad a kontrolu nad všetkými reláciami.",
|
||||
"New session manager": "Nový správca relácií",
|
||||
"Use new session manager": "Použiť nového správcu relácií",
|
||||
"Wysiwyg composer (plain text mode coming soon) (under active development)": "Wysiwyg composer (textový režim už čoskoro) (v štádiu aktívneho vývoja)",
|
||||
"Sign out all other sessions": "Odhlásenie zo všetkých ostatných relácií",
|
||||
"Underline": "Podčiarknuté",
|
||||
"Italic": "Kurzíva",
|
||||
"You have already joined this call from another device": "K tomuto hovoru ste sa už pripojili z iného zariadenia",
|
||||
"Try out the rich text editor (plain text mode coming soon)": "Vyskúšajte rozšírený textový editor (čistý textový režim sa objaví čoskoro)",
|
||||
"stop voice broadcast": "zastaviť hlasové vysielanie",
|
||||
"resume voice broadcast": "obnoviť hlasové vysielanie",
|
||||
"pause voice broadcast": "pozastaviť hlasové vysielanie",
|
||||
"Notifications silenced": "Oznámenia stlmené"
|
||||
}
|
||||
|
|
|
@ -213,7 +213,7 @@
|
|||
"%(senderName)s removed the main address for this room.": "%(senderName)s вилучає основу адресу цієї кімнати.",
|
||||
"Someone": "Хтось",
|
||||
"%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s надіслав(-ла) запрошення %(targetDisplayName)s приєднатися до кімнати.",
|
||||
"Default": "Типово",
|
||||
"Default": "Типовий",
|
||||
"%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s зробив(-ла) майбутню історію кімнати видимою для всіх учасників з моменту, коли вони приєдналися.",
|
||||
"%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s робить майбутню історію кімнати видимою для всіх учасників від часу їхнього приєднання.",
|
||||
"%(senderName)s made future room history visible to all room members.": "%(senderName)s зробив(-ла) майбутню історію видимою для всіх учасників кімнати.",
|
||||
|
@ -2162,7 +2162,7 @@
|
|||
"Feeling experimental? Labs are the best way to get things early, test out new features and help shape them before they actually launch. <a>Learn more</a>.": "Почуваєтесь допитливо? Лабораторія дає змогу отримувати нову функціональність раніше всіх, випробовувати й допомагати допрацьовувати її перед запуском. <a>Докладніше</a>.",
|
||||
"Render LaTeX maths in messages": "Форматувати LaTeX-формули в повідомленнях",
|
||||
"Share anonymous data to help us identify issues. Nothing personal. No third parties. <LearnMoreLink>Learn More</LearnMoreLink>": "Збір анонімних даних дає нам змогу дізнаватися про збої. Жодних особистих даних. Жодних третіх сторін. <LearnMoreLink>Докладніше</LearnMoreLink>",
|
||||
"Low bandwidth mode (requires compatible homeserver)": "Заощаджувати трафік (потрібен сумісний сервер)",
|
||||
"Low bandwidth mode (requires compatible homeserver)": "Режим низької пропускної здатності (потрібен сумісний домашній сервер)",
|
||||
"Developer": "Розробка",
|
||||
"Moderation": "Модерування",
|
||||
"Experimental": "Експериментально",
|
||||
|
@ -3570,7 +3570,7 @@
|
|||
"Voice broadcast": "Голосове мовлення",
|
||||
"Voice broadcast (under active development)": "Голосове мовлення (в активній розробці)",
|
||||
"Element Call video rooms": "Відео кімнати Element Call",
|
||||
"Voice broadcasts": "Голосові передачі",
|
||||
"Voice broadcasts": "Голосове мовлення",
|
||||
"You do not have permission to start voice calls": "У вас немає дозволу розпочинати голосові виклики",
|
||||
"There's no one here to call": "Тут немає кого викликати",
|
||||
"You do not have permission to start video calls": "У вас немає дозволу розпочинати відеовиклики",
|
||||
|
@ -3588,5 +3588,49 @@
|
|||
"Enable notifications for this account": "Увімкнути сповіщення для цього облікового запису",
|
||||
"Video call ended": "Відеовиклик завершено",
|
||||
"%(name)s started a video call": "%(name)s розпочинає відеовиклик",
|
||||
"%(selectedDeviceCount)s sessions selected": "Вибрано %(selectedDeviceCount)s сеансів"
|
||||
"%(selectedDeviceCount)s sessions selected": "Вибрано %(selectedDeviceCount)s сеансів",
|
||||
"URL": "URL",
|
||||
"Version": "Версія",
|
||||
"Application": "Застосунок",
|
||||
"Record the client name, version, and url to recognise sessions more easily in session manager": "Записуйте назву клієнта, версію та URL-адресу, щоб легше розпізнавати сеанси в менеджері сеансів",
|
||||
"Unknown session type": "Невідомий тип сеансу",
|
||||
"Web session": "Сеанс у браузері",
|
||||
"Mobile session": "Сеанс на мобільному",
|
||||
"Desktop session": "Сеанс на комп'ютері",
|
||||
"Video call started": "Відеовиклик розпочато",
|
||||
"Unknown room": "Невідома кімната",
|
||||
"Video call started in %(roomName)s. (not supported by this browser)": "Відеовиклик розпочато о %(roomName)s. (не підтримується цим браузером)",
|
||||
"Video call started in %(roomName)s.": "Відеовиклик розпочато о %(roomName)s.",
|
||||
"Room info": "Відомості про кімнату",
|
||||
"View chat timeline": "Переглянути стрічку бесіди",
|
||||
"Close call": "Закрити виклик",
|
||||
"Layout type": "Тип макета",
|
||||
"Spotlight": "У фокусі",
|
||||
"Freedom": "Свобода",
|
||||
"Operating system": "Операційна система",
|
||||
"Model": "Модель",
|
||||
"Client": "Клієнт",
|
||||
"Fill screen": "Заповнити екран",
|
||||
"Video call (%(brand)s)": "Відеовиклик (%(brand)s)",
|
||||
"Call type": "Тип викликів",
|
||||
"You do not have sufficient permissions to change this.": "Ви не маєте достатніх повноважень, щоб змінити це.",
|
||||
"%(brand)s is end-to-end encrypted, but is currently limited to smaller numbers of users.": "%(brand)s наскрізно зашифровано, але наразі обмежений меншою кількістю користувачів.",
|
||||
"Enable %(brand)s as an additional calling option in this room": "Увімкнути %(brand)s додатковою опцією викликів у цій кімнаті",
|
||||
"Join %(brand)s calls": "Приєднатися до %(brand)s викликів",
|
||||
"Start %(brand)s calls": "Розпочати %(brand)s викликів",
|
||||
"Sorry — this call is currently full": "Перепрошуємо, цей виклик заповнено",
|
||||
"Wysiwyg composer (plain text mode coming soon) (under active development)": "Редактор Wysiwyg (скоро з'явиться режим звичайного тексту) (в активній розробці)",
|
||||
"Our new sessions manager provides better visibility of all your sessions, and greater control over them including the ability to remotely toggle push notifications.": "Наш новий менеджер сеансів забезпечує кращу видимість всіх ваших сеансів і більший контроль над ними, зокрема можливість віддаленого перемикання push-сповіщень.",
|
||||
"Have greater visibility and control over all your sessions.": "Майте кращу видимість і контроль над усіма вашими сеансами.",
|
||||
"New session manager": "Новий менеджер сеансів",
|
||||
"Use new session manager": "Використовувати новий менеджер сеансів",
|
||||
"Sign out all other sessions": "Вийти з усіх інших сеансів",
|
||||
"Underline": "Підкреслений",
|
||||
"Italic": "Курсив",
|
||||
"Try out the rich text editor (plain text mode coming soon)": "Спробуйте розширений текстовий редактор (незабаром з'явиться режим звичайного тексту)",
|
||||
"resume voice broadcast": "поновити голосове мовлення",
|
||||
"pause voice broadcast": "призупинити голосове мовлення",
|
||||
"You have already joined this call from another device": "Ви вже приєдналися до цього виклику з іншого пристрою",
|
||||
"stop voice broadcast": "припинити голосове мовлення",
|
||||
"Notifications silenced": "Сповіщення стишено"
|
||||
}
|
||||
|
|
|
@ -3588,5 +3588,49 @@
|
|||
"Enable notifications for this account": "為此帳號啟用通知",
|
||||
"%(selectedDeviceCount)s sessions selected": "已選取 %(selectedDeviceCount)s 個工作階段",
|
||||
"Video call ended": "視訊通話已結束",
|
||||
"%(name)s started a video call": "%(name)s 開始了視訊通話"
|
||||
"%(name)s started a video call": "%(name)s 開始了視訊通話",
|
||||
"URL": "URL",
|
||||
"Version": "版本",
|
||||
"Application": "應用程式",
|
||||
"Record the client name, version, and url to recognise sessions more easily in session manager": "記錄客戶端名稱、版本與 URL,以便在工作階段管理程式更輕鬆地識別工作階段",
|
||||
"Unknown session type": "未知工作階段類型",
|
||||
"Web session": "網頁工作階段",
|
||||
"Mobile session": "行動裝置工作階段",
|
||||
"Desktop session": "桌面工作階段",
|
||||
"Video call started": "視訊通話已開始",
|
||||
"Unknown room": "未知的聊天室",
|
||||
"Video call started in %(roomName)s. (not supported by this browser)": "視訊通話在 %(roomName)s 開始。(此瀏覽器不支援)",
|
||||
"Video call started in %(roomName)s.": "視訊通話在 %(roomName)s 開始。",
|
||||
"Room info": "聊天室資訊",
|
||||
"View chat timeline": "檢視聊天時間軸",
|
||||
"Close call": "關閉通話",
|
||||
"Layout type": "佈局類型",
|
||||
"Spotlight": "聚焦",
|
||||
"Freedom": "自由",
|
||||
"Video call (%(brand)s)": "視訊通話 (%(brand)s)",
|
||||
"Operating system": "作業系統",
|
||||
"Model": "模型",
|
||||
"Client": "客戶端",
|
||||
"Call type": "通話類型",
|
||||
"You do not have sufficient permissions to change this.": "您沒有足夠的權限來變更此設定。",
|
||||
"%(brand)s is end-to-end encrypted, but is currently limited to smaller numbers of users.": "%(brand)s 是端到端加密的,但目前僅限於少數使用者。",
|
||||
"Enable %(brand)s as an additional calling option in this room": "啟用 %(brand)s 作為此聊天室的額外通話選項",
|
||||
"Join %(brand)s calls": "加入 %(brand)s 通話",
|
||||
"Start %(brand)s calls": "開始 %(brand)s 通話",
|
||||
"Fill screen": "填滿螢幕",
|
||||
"Sorry — this call is currently full": "抱歉 — 此通話目前已滿",
|
||||
"Wysiwyg composer (plain text mode coming soon) (under active development)": "所見即所得編輯器(純文字模式即將推出)(正在積極開發中)",
|
||||
"Our new sessions manager provides better visibility of all your sessions, and greater control over them including the ability to remotely toggle push notifications.": "我們的新工作階段管理程式可讓您更好地了解您的所有工作階段,並更好地控制它們,包含遠端切換推播通知的能力。",
|
||||
"Have greater visibility and control over all your sessions.": "對您所有的工作階段有更大的能見度與控制。",
|
||||
"New session manager": "新的工作階段管理程式",
|
||||
"Use new session manager": "使用新的工作階段管理程式",
|
||||
"Sign out all other sessions": "登出其他所有工作階段",
|
||||
"Underline": "底線",
|
||||
"Italic": "義式斜體",
|
||||
"You have already joined this call from another device": "您已從另一台裝置加入了此通話",
|
||||
"Try out the rich text editor (plain text mode coming soon)": "試用格式化文字編輯器(純文字模式即將推出)",
|
||||
"stop voice broadcast": "停止語音廣播",
|
||||
"resume voice broadcast": "恢復語音廣播",
|
||||
"pause voice broadcast": "暫停語音廣播",
|
||||
"Notifications silenced": "通知已靜音"
|
||||
}
|
||||
|
|
|
@ -494,6 +494,16 @@ export const SETTINGS: {[setting: string]: ISetting} = {
|
|||
</>,
|
||||
},
|
||||
},
|
||||
"feature_qr_signin_reciprocate_show": {
|
||||
isFeature: true,
|
||||
labsGroup: LabGroup.Experimental,
|
||||
supportedLevels: LEVELS_FEATURE,
|
||||
displayName: _td(
|
||||
"Allow a QR code to be shown in session manager to sign in another device " +
|
||||
"(requires compatible homeserver)",
|
||||
),
|
||||
default: false,
|
||||
},
|
||||
"baseFontSize": {
|
||||
displayName: _td("Font size"),
|
||||
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React, { ReactNode } from "react";
|
||||
import * as utils from 'matrix-js-sdk/src/utils';
|
||||
import { MatrixError } from "matrix-js-sdk/src/http-api";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { ViewRoom as ViewRoomEvent } from "@matrix-org/analytics-events/types/typescript/ViewRoom";
|
||||
|
@ -27,7 +28,7 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
|||
import { Optional } from "matrix-events-sdk";
|
||||
import EventEmitter from "events";
|
||||
|
||||
import { defaultDispatcher, MatrixDispatcher } from '../dispatcher/dispatcher';
|
||||
import { MatrixDispatcher } from '../dispatcher/dispatcher';
|
||||
import { MatrixClientPeg } from '../MatrixClientPeg';
|
||||
import Modal from '../Modal';
|
||||
import { _t } from '../languageHandler';
|
||||
|
@ -35,10 +36,8 @@ import { getCachedRoomIDForAlias, storeRoomAliasInCache } from '../RoomAliasCach
|
|||
import { Action } from "../dispatcher/actions";
|
||||
import { retry } from "../utils/promise";
|
||||
import { TimelineRenderingType } from "../contexts/RoomContext";
|
||||
import { PosthogAnalytics } from "../PosthogAnalytics";
|
||||
import { ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload";
|
||||
import DMRoomMap from "../utils/DMRoomMap";
|
||||
import SpaceStore from "./spaces/SpaceStore";
|
||||
import { isMetaSpace, MetaSpace } from "./spaces";
|
||||
import { JoinRoomPayload } from "../dispatcher/payloads/JoinRoomPayload";
|
||||
import { JoinRoomReadyPayload } from "../dispatcher/payloads/JoinRoomReadyPayload";
|
||||
|
@ -47,9 +46,9 @@ import { ViewRoomErrorPayload } from "../dispatcher/payloads/ViewRoomErrorPayloa
|
|||
import ErrorDialog from "../components/views/dialogs/ErrorDialog";
|
||||
import { ActiveRoomChangedPayload } from "../dispatcher/payloads/ActiveRoomChangedPayload";
|
||||
import SettingsStore from "../settings/SettingsStore";
|
||||
import { SlidingSyncManager } from "../SlidingSyncManager";
|
||||
import { awaitRoomDownSync } from "../utils/RoomUpgrade";
|
||||
import { UPDATE_EVENT } from "./AsyncStore";
|
||||
import { SdkContextClass } from "../contexts/SDKContext";
|
||||
import { CallStore } from "./CallStore";
|
||||
|
||||
const NUM_JOIN_RETRY = 5;
|
||||
|
@ -131,17 +130,16 @@ type Listener = (isActive: boolean) => void;
|
|||
* A class for storing application state for RoomView.
|
||||
*/
|
||||
export class RoomViewStore extends EventEmitter {
|
||||
// Important: This cannot be a dynamic getter (lazily-constructed instance) because
|
||||
// otherwise we'll miss view_room dispatches during startup, breaking relaunches of
|
||||
// the app. We need to eagerly create the instance.
|
||||
public static readonly instance = new RoomViewStore(defaultDispatcher);
|
||||
|
||||
private state: State = INITIAL_STATE; // initialize state
|
||||
// initialize state as a copy of the initial state. We need to copy else one RVS can talk to
|
||||
// another RVS via INITIAL_STATE as they share the same underlying object. Mostly relevant for tests.
|
||||
private state = utils.deepCopy(INITIAL_STATE);
|
||||
|
||||
private dis: MatrixDispatcher;
|
||||
private dispatchToken: string;
|
||||
|
||||
public constructor(dis: MatrixDispatcher) {
|
||||
public constructor(
|
||||
dis: MatrixDispatcher, private readonly stores: SdkContextClass,
|
||||
) {
|
||||
super();
|
||||
this.resetDispatcher(dis);
|
||||
}
|
||||
|
@ -248,7 +246,7 @@ export class RoomViewStore extends EventEmitter {
|
|||
: numMembers > 1 ? "Two"
|
||||
: "One";
|
||||
|
||||
PosthogAnalytics.instance.trackEvent<JoinedRoomEvent>({
|
||||
this.stores.posthogAnalytics.trackEvent<JoinedRoomEvent>({
|
||||
eventName: "JoinedRoom",
|
||||
trigger: payload.metricsTrigger,
|
||||
roomSize,
|
||||
|
@ -291,17 +289,17 @@ export class RoomViewStore extends EventEmitter {
|
|||
|
||||
if (payload.metricsTrigger !== null && payload.room_id !== this.state.roomId) {
|
||||
let activeSpace: ViewRoomEvent["activeSpace"];
|
||||
if (SpaceStore.instance.activeSpace === MetaSpace.Home) {
|
||||
if (this.stores.spaceStore.activeSpace === MetaSpace.Home) {
|
||||
activeSpace = "Home";
|
||||
} else if (isMetaSpace(SpaceStore.instance.activeSpace)) {
|
||||
} else if (isMetaSpace(this.stores.spaceStore.activeSpace)) {
|
||||
activeSpace = "Meta";
|
||||
} else {
|
||||
activeSpace = SpaceStore.instance.activeSpaceRoom.getJoinRule() === JoinRule.Public
|
||||
activeSpace = this.stores.spaceStore.activeSpaceRoom?.getJoinRule() === JoinRule.Public
|
||||
? "Public"
|
||||
: "Private";
|
||||
}
|
||||
|
||||
PosthogAnalytics.instance.trackEvent<ViewRoomEvent>({
|
||||
this.stores.posthogAnalytics.trackEvent<ViewRoomEvent>({
|
||||
eventName: "ViewRoom",
|
||||
trigger: payload.metricsTrigger,
|
||||
viaKeyboard: payload.metricsViaKeyboard,
|
||||
|
@ -314,7 +312,7 @@ export class RoomViewStore extends EventEmitter {
|
|||
if (SettingsStore.getValue("feature_sliding_sync") && this.state.roomId !== payload.room_id) {
|
||||
if (this.state.subscribingRoomId && this.state.subscribingRoomId !== payload.room_id) {
|
||||
// unsubscribe from this room, but don't await it as we don't care when this gets done.
|
||||
SlidingSyncManager.instance.setRoomVisible(this.state.subscribingRoomId, false);
|
||||
this.stores.slidingSyncManager.setRoomVisible(this.state.subscribingRoomId, false);
|
||||
}
|
||||
this.setState({
|
||||
subscribingRoomId: payload.room_id,
|
||||
|
@ -332,11 +330,11 @@ export class RoomViewStore extends EventEmitter {
|
|||
});
|
||||
// set this room as the room subscription. We need to await for it as this will fetch
|
||||
// all room state for this room, which is required before we get the state below.
|
||||
await SlidingSyncManager.instance.setRoomVisible(payload.room_id, true);
|
||||
await this.stores.slidingSyncManager.setRoomVisible(payload.room_id, true);
|
||||
// Whilst we were subscribing another room was viewed, so stop what we're doing and
|
||||
// unsubscribe
|
||||
if (this.state.subscribingRoomId !== payload.room_id) {
|
||||
SlidingSyncManager.instance.setRoomVisible(payload.room_id, false);
|
||||
this.stores.slidingSyncManager.setRoomVisible(payload.room_id, false);
|
||||
return;
|
||||
}
|
||||
// Re-fire the payload: we won't re-process it because the prev room ID == payload room ID now
|
||||
|
@ -599,7 +597,7 @@ export class RoomViewStore extends EventEmitter {
|
|||
// // Not joined
|
||||
// }
|
||||
// } else {
|
||||
// if (RoomViewStore.instance.isJoining()) {
|
||||
// if (this.stores.roomViewStore.isJoining()) {
|
||||
// // show spinner
|
||||
// } else {
|
||||
// // show join prompt
|
||||
|
|
|
@ -32,16 +32,15 @@ import { ThreadsRoomNotificationState } from "./ThreadsRoomNotificationState";
|
|||
export class RoomNotificationState extends NotificationState implements IDestroyable {
|
||||
constructor(public readonly room: Room, private readonly threadsState?: ThreadsRoomNotificationState) {
|
||||
super();
|
||||
this.room.on(RoomEvent.Receipt, this.handleReadReceipt);
|
||||
this.room.on(RoomEvent.Timeline, this.handleRoomEventUpdate);
|
||||
this.room.on(RoomEvent.Redaction, this.handleRoomEventUpdate);
|
||||
this.room.on(RoomEvent.MyMembership, this.handleMembershipUpdate);
|
||||
this.room.on(RoomEvent.LocalEchoUpdated, this.handleLocalEchoUpdated);
|
||||
this.room.on(RoomEvent.Receipt, this.handleReadReceipt); // for unread indicators
|
||||
this.room.on(RoomEvent.MyMembership, this.handleMembershipUpdate); // for redness on invites
|
||||
this.room.on(RoomEvent.LocalEchoUpdated, this.handleLocalEchoUpdated); // for redness on unsent messages
|
||||
this.room.on(RoomEvent.UnreadNotifications, this.handleNotificationCountUpdate); // for server-sent counts
|
||||
if (threadsState) {
|
||||
threadsState.on(NotificationStateEvents.Update, this.handleThreadsUpdate);
|
||||
}
|
||||
MatrixClientPeg.get().on(MatrixEventEvent.Decrypted, this.onEventDecrypted);
|
||||
MatrixClientPeg.get().on(ClientEvent.AccountData, this.handleAccountDataUpdate);
|
||||
MatrixClientPeg.get().on(MatrixEventEvent.Decrypted, this.onEventDecrypted); // for local count calculation
|
||||
MatrixClientPeg.get().on(ClientEvent.AccountData, this.handleAccountDataUpdate); // for push rules
|
||||
this.updateNotificationState();
|
||||
}
|
||||
|
||||
|
@ -52,10 +51,9 @@ export class RoomNotificationState extends NotificationState implements IDestroy
|
|||
public destroy(): void {
|
||||
super.destroy();
|
||||
this.room.removeListener(RoomEvent.Receipt, this.handleReadReceipt);
|
||||
this.room.removeListener(RoomEvent.Timeline, this.handleRoomEventUpdate);
|
||||
this.room.removeListener(RoomEvent.Redaction, this.handleRoomEventUpdate);
|
||||
this.room.removeListener(RoomEvent.MyMembership, this.handleMembershipUpdate);
|
||||
this.room.removeListener(RoomEvent.LocalEchoUpdated, this.handleLocalEchoUpdated);
|
||||
this.room.removeListener(RoomEvent.UnreadNotifications, this.handleNotificationCountUpdate);
|
||||
if (this.threadsState) {
|
||||
this.threadsState.removeListener(NotificationStateEvents.Update, this.handleThreadsUpdate);
|
||||
}
|
||||
|
@ -83,14 +81,12 @@ export class RoomNotificationState extends NotificationState implements IDestroy
|
|||
this.updateNotificationState();
|
||||
};
|
||||
|
||||
private onEventDecrypted = (event: MatrixEvent) => {
|
||||
if (event.getRoomId() !== this.room.roomId) return; // ignore - not for us or notifications timeline
|
||||
|
||||
private handleNotificationCountUpdate = () => {
|
||||
this.updateNotificationState();
|
||||
};
|
||||
|
||||
private handleRoomEventUpdate = (event: MatrixEvent, room: Room | null) => {
|
||||
if (room?.roomId !== this.room.roomId) return; // ignore - not for us or notifications timeline
|
||||
private onEventDecrypted = (event: MatrixEvent) => {
|
||||
if (event.getRoomId() !== this.room.roomId) return; // ignore - not for us or notifications timeline
|
||||
|
||||
this.updateNotificationState();
|
||||
};
|
||||
|
|
|
@ -34,7 +34,7 @@ import {
|
|||
import { ActionPayload } from "../../dispatcher/payloads";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
import { ActiveRoomChangedPayload } from "../../dispatcher/payloads/ActiveRoomChangedPayload";
|
||||
import { RoomViewStore } from "../RoomViewStore";
|
||||
import { SdkContextClass } from "../../contexts/SDKContext";
|
||||
|
||||
/**
|
||||
* A class for tracking the state of the right panel between layouts and
|
||||
|
@ -64,7 +64,7 @@ export default class RightPanelStore extends ReadyWatchingStore {
|
|||
}
|
||||
|
||||
protected async onReady(): Promise<any> {
|
||||
this.viewedRoomId = RoomViewStore.instance.getRoomId();
|
||||
this.viewedRoomId = SdkContextClass.instance.roomViewStore.getRoomId();
|
||||
this.matrixClient.on(CryptoEvent.VerificationRequest, this.onVerificationRequestUpdate);
|
||||
this.loadCacheFromSettings();
|
||||
this.emitAndUpdateSettings();
|
||||
|
|
|
@ -27,7 +27,6 @@ import { ActionPayload } from "../../dispatcher/payloads";
|
|||
import defaultDispatcher from "../../dispatcher/dispatcher";
|
||||
import { readReceiptChangeIsFor } from "../../utils/read-receipts";
|
||||
import { FILTER_CHANGED, IFilterCondition } from "./filters/IFilterCondition";
|
||||
import { RoomViewStore } from "../RoomViewStore";
|
||||
import { Algorithm, LIST_UPDATED_EVENT } from "./algorithms/Algorithm";
|
||||
import { EffectiveMembership, getEffectiveMembership } from "../../utils/membership";
|
||||
import RoomListLayoutStore from "./RoomListLayoutStore";
|
||||
|
@ -40,6 +39,7 @@ import { IRoomTimelineActionPayload } from "../../actions/MatrixActionCreators";
|
|||
import { RoomListStore as Interface, RoomListStoreEvent } from "./Interface";
|
||||
import { SlidingRoomListStoreClass } from "./SlidingRoomListStore";
|
||||
import { UPDATE_EVENT } from "../AsyncStore";
|
||||
import { SdkContextClass } from "../../contexts/SDKContext";
|
||||
|
||||
interface IState {
|
||||
// state is tracked in underlying classes
|
||||
|
@ -105,7 +105,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> implements
|
|||
this.readyStore.useUnitTestClient(forcedClient);
|
||||
}
|
||||
|
||||
RoomViewStore.instance.addListener(UPDATE_EVENT, () => this.handleRVSUpdate({}));
|
||||
SdkContextClass.instance.roomViewStore.addListener(UPDATE_EVENT, () => this.handleRVSUpdate({}));
|
||||
this.algorithm.on(LIST_UPDATED_EVENT, this.onAlgorithmListUpdated);
|
||||
this.algorithm.on(FILTER_CHANGED, this.onAlgorithmFilterUpdated);
|
||||
this.setupWatchers();
|
||||
|
@ -128,7 +128,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> implements
|
|||
private handleRVSUpdate({ trigger = true }) {
|
||||
if (!this.matrixClient) return; // We assume there won't be RVS updates without a client
|
||||
|
||||
const activeRoomId = RoomViewStore.instance.getRoomId();
|
||||
const activeRoomId = SdkContextClass.instance.roomViewStore.getRoomId();
|
||||
if (!activeRoomId && this.algorithm.stickyRoom) {
|
||||
this.algorithm.setStickyRoom(null);
|
||||
} else if (activeRoomId) {
|
||||
|
|
|
@ -29,8 +29,8 @@ import { SlidingSyncManager } from "../../SlidingSyncManager";
|
|||
import SpaceStore from "../spaces/SpaceStore";
|
||||
import { MetaSpace, SpaceKey, UPDATE_SELECTED_SPACE } from "../spaces";
|
||||
import { LISTS_LOADING_EVENT } from "./RoomListStore";
|
||||
import { RoomViewStore } from "../RoomViewStore";
|
||||
import { UPDATE_EVENT } from "../AsyncStore";
|
||||
import { SdkContextClass } from "../../contexts/SDKContext";
|
||||
|
||||
interface IState {
|
||||
// state is tracked in underlying classes
|
||||
|
@ -207,7 +207,7 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient<IState> impl
|
|||
|
||||
// this room will not move due to it being viewed: it is sticky. This can be null to indicate
|
||||
// no sticky room if you aren't viewing a room.
|
||||
this.stickyRoomId = RoomViewStore.instance.getRoomId();
|
||||
this.stickyRoomId = SdkContextClass.instance.roomViewStore.getRoomId();
|
||||
let stickyRoomNewIndex = -1;
|
||||
const stickyRoomOldIndex = (tagMap[tagId] || []).findIndex((room) => {
|
||||
return room.roomId === this.stickyRoomId;
|
||||
|
@ -273,7 +273,7 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient<IState> impl
|
|||
|
||||
private onRoomViewStoreUpdated() {
|
||||
// we only care about this to know when the user has clicked on a room to set the stickiness value
|
||||
if (RoomViewStore.instance.getRoomId() === this.stickyRoomId) {
|
||||
if (SdkContextClass.instance.roomViewStore.getRoomId() === this.stickyRoomId) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -303,7 +303,7 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient<IState> impl
|
|||
}
|
||||
}
|
||||
// in the event we didn't call refreshOrderedLists, it helps to still remember the sticky room ID.
|
||||
this.stickyRoomId = RoomViewStore.instance.getRoomId();
|
||||
this.stickyRoomId = SdkContextClass.instance.roomViewStore.getRoomId();
|
||||
|
||||
if (hasUpdatedAnyList) {
|
||||
this.emit(LISTS_UPDATE_EVENT);
|
||||
|
@ -314,7 +314,7 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient<IState> impl
|
|||
logger.info("SlidingRoomListStore.onReady");
|
||||
// permanent listeners: never get destroyed. Could be an issue if we want to test this in isolation.
|
||||
SlidingSyncManager.instance.slidingSync.on(SlidingSyncEvent.List, this.onSlidingSyncListUpdate.bind(this));
|
||||
RoomViewStore.instance.addListener(UPDATE_EVENT, this.onRoomViewStoreUpdated.bind(this));
|
||||
SdkContextClass.instance.roomViewStore.addListener(UPDATE_EVENT, this.onRoomViewStoreUpdated.bind(this));
|
||||
SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdated.bind(this));
|
||||
if (SpaceStore.instance.activeSpace) {
|
||||
this.onSelectedSpaceUpdated(SpaceStore.instance.activeSpace, false);
|
||||
|
|
|
@ -34,7 +34,6 @@ import { RoomNotificationStateStore } from "../notifications/RoomNotificationSta
|
|||
import { DefaultTagID } from "../room-list/models";
|
||||
import { EnhancedMap, mapDiff } from "../../utils/maps";
|
||||
import { setDiff, setHasDiff } from "../../utils/sets";
|
||||
import { RoomViewStore } from "../RoomViewStore";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
import { arrayHasDiff, arrayHasOrderChange } from "../../utils/arrays";
|
||||
import { reorderLexicographically } from "../../utils/stringOrderField";
|
||||
|
@ -64,6 +63,7 @@ import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
|
|||
import { ViewHomePagePayload } from "../../dispatcher/payloads/ViewHomePagePayload";
|
||||
import { SwitchSpacePayload } from "../../dispatcher/payloads/SwitchSpacePayload";
|
||||
import { AfterLeaveRoomPayload } from "../../dispatcher/payloads/AfterLeaveRoomPayload";
|
||||
import { SdkContextClass } from "../../contexts/SDKContext";
|
||||
|
||||
interface IState { }
|
||||
|
||||
|
@ -797,7 +797,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
|||
this.updateNotificationStates(notificationStatesToUpdate);
|
||||
};
|
||||
|
||||
private switchSpaceIfNeeded = (roomId = RoomViewStore.instance.getRoomId()) => {
|
||||
private switchSpaceIfNeeded = (roomId = SdkContextClass.instance.roomViewStore.getRoomId()) => {
|
||||
if (!this.isRoomInSpace(this.activeSpace, roomId) && !this.matrixClient.getRoom(roomId)?.isSpaceRoom()) {
|
||||
this.switchToRelatedSpace(roomId);
|
||||
}
|
||||
|
@ -848,7 +848,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
|||
}
|
||||
|
||||
// if the room currently being viewed was just joined then switch to its related space
|
||||
if (newMembership === "join" && room.roomId === RoomViewStore.instance.getRoomId()) {
|
||||
if (newMembership === "join" && room.roomId === SdkContextClass.instance.roomViewStore.getRoomId()) {
|
||||
this.switchSpaceIfNeeded(room.roomId);
|
||||
}
|
||||
}
|
||||
|
@ -875,7 +875,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
|||
this.emit(room.roomId);
|
||||
}
|
||||
|
||||
if (membership === "join" && room.roomId === RoomViewStore.instance.getRoomId()) {
|
||||
if (membership === "join" && room.roomId === SdkContextClass.instance.roomViewStore.getRoomId()) {
|
||||
// if the user was looking at the space and then joined: select that space
|
||||
this.setActiveSpace(room.roomId, false);
|
||||
} else if (membership === "leave" && room.roomId === this.activeSpace) {
|
||||
|
|
|
@ -41,7 +41,6 @@ import { ClientEvent } from "matrix-js-sdk/src/client";
|
|||
import { _t } from "../../languageHandler";
|
||||
import { StopGapWidgetDriver } from "./StopGapWidgetDriver";
|
||||
import { WidgetMessagingStore } from "./WidgetMessagingStore";
|
||||
import { RoomViewStore } from "../RoomViewStore";
|
||||
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||
import { OwnProfileStore } from "../OwnProfileStore";
|
||||
import WidgetUtils from '../../utils/WidgetUtils';
|
||||
|
@ -65,6 +64,7 @@ import { arrayFastClone } from "../../utils/arrays";
|
|||
import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
|
||||
import Modal from "../../Modal";
|
||||
import ErrorDialog from "../../components/views/dialogs/ErrorDialog";
|
||||
import { SdkContextClass } from "../../contexts/SDKContext";
|
||||
import { VoiceBroadcastRecordingsStore } from "../../voice-broadcast";
|
||||
|
||||
// TODO: Destroy all of this code
|
||||
|
@ -185,7 +185,7 @@ export class StopGapWidget extends EventEmitter {
|
|||
|
||||
if (this.roomId) return this.roomId;
|
||||
|
||||
return RoomViewStore.instance.getRoomId();
|
||||
return SdkContextClass.instance.roomViewStore.getRoomId();
|
||||
}
|
||||
|
||||
public get widgetApi(): ClientWidgetApi {
|
||||
|
@ -381,7 +381,7 @@ export class StopGapWidget extends EventEmitter {
|
|||
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
IntegrationManagers.sharedInstance().getPrimaryManager().open(
|
||||
this.client.getRoom(RoomViewStore.instance.getRoomId()),
|
||||
this.client.getRoom(SdkContextClass.instance.roomViewStore.getRoomId()),
|
||||
`type_${integType}`,
|
||||
integId,
|
||||
);
|
||||
|
|
|
@ -53,9 +53,9 @@ import { CHAT_EFFECTS } from "../../effects";
|
|||
import { containsEmoji } from "../../effects/utils";
|
||||
import dis from "../../dispatcher/dispatcher";
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
import { RoomViewStore } from "../RoomViewStore";
|
||||
import { ElementWidgetCapabilities } from "./ElementWidgetCapabilities";
|
||||
import { navigateToPermalink } from "../../utils/permalinks/navigator";
|
||||
import { SdkContextClass } from "../../contexts/SDKContext";
|
||||
|
||||
// TODO: Purge this from the universe
|
||||
|
||||
|
@ -210,7 +210,7 @@ export class StopGapWidgetDriver extends WidgetDriver {
|
|||
targetRoomId: string = null,
|
||||
): Promise<ISendEventDetails> {
|
||||
const client = MatrixClientPeg.get();
|
||||
const roomId = targetRoomId || RoomViewStore.instance.getRoomId();
|
||||
const roomId = targetRoomId || SdkContextClass.instance.roomViewStore.getRoomId();
|
||||
|
||||
if (!client || !roomId) throw new Error("Not in a room or not attached to a client");
|
||||
|
||||
|
@ -291,7 +291,7 @@ export class StopGapWidgetDriver extends WidgetDriver {
|
|||
|
||||
const targetRooms = roomIds
|
||||
? (roomIds.includes(Symbols.AnyRoom) ? client.getVisibleRooms() : roomIds.map(r => client.getRoom(r)))
|
||||
: [client.getRoom(RoomViewStore.instance.getRoomId())];
|
||||
: [client.getRoom(SdkContextClass.instance.roomViewStore.getRoomId())];
|
||||
return targetRooms.filter(r => !!r);
|
||||
}
|
||||
|
||||
|
@ -430,7 +430,7 @@ export class StopGapWidgetDriver extends WidgetDriver {
|
|||
): Promise<IReadEventRelationsResult> {
|
||||
const client = MatrixClientPeg.get();
|
||||
const dir = direction as Direction;
|
||||
roomId = roomId ?? RoomViewStore.instance.getRoomId() ?? undefined;
|
||||
roomId = roomId ?? SdkContextClass.instance.roomViewStore.getRoomId() ?? undefined;
|
||||
|
||||
if (typeof roomId !== "string") {
|
||||
throw new Error('Error while reading the current room');
|
||||
|
|
|
@ -20,7 +20,6 @@ import defaultDispatcher from "../dispatcher/dispatcher";
|
|||
import { ActionPayload } from "../dispatcher/payloads";
|
||||
import Modal from "../Modal";
|
||||
import RoomSettingsDialog from "../components/views/dialogs/RoomSettingsDialog";
|
||||
import { RoomViewStore } from "../stores/RoomViewStore";
|
||||
import ForwardDialog from "../components/views/dialogs/ForwardDialog";
|
||||
import { MatrixClientPeg } from "../MatrixClientPeg";
|
||||
import { Action } from "../dispatcher/actions";
|
||||
|
@ -32,6 +31,7 @@ import AddExistingToSpaceDialog from "../components/views/dialogs/AddExistingToS
|
|||
import { ButtonEvent } from "../components/views/elements/AccessibleButton";
|
||||
import PosthogTrackers from "../PosthogTrackers";
|
||||
import { showAddExistingSubspace, showCreateNewRoom } from "./space";
|
||||
import { SdkContextClass } from "../contexts/SDKContext";
|
||||
|
||||
/**
|
||||
* Auxiliary class to listen for dialog opening over the dispatcher and
|
||||
|
@ -58,7 +58,7 @@ export class DialogOpener {
|
|||
switch (payload.action) {
|
||||
case 'open_room_settings':
|
||||
Modal.createDialog(RoomSettingsDialog, {
|
||||
roomId: payload.room_id || RoomViewStore.instance.getRoomId(),
|
||||
roomId: payload.room_id || SdkContextClass.instance.roomViewStore.getRoomId(),
|
||||
initialTabId: payload.initial_tab_id,
|
||||
}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
|
||||
break;
|
||||
|
@ -108,7 +108,7 @@ export class DialogOpener {
|
|||
onAddSubspaceClick: () => showAddExistingSubspace(space),
|
||||
space,
|
||||
onFinished: (added: boolean) => {
|
||||
if (added && RoomViewStore.instance.getRoomId() === space.roomId) {
|
||||
if (added && SdkContextClass.instance.roomViewStore.getRoomId() === space.roomId) {
|
||||
defaultDispatcher.fire(Action.UpdateSpaceHierarchy);
|
||||
}
|
||||
},
|
||||
|
|
55
src/utils/UserInteractiveAuth.ts
Normal file
55
src/utils/UserInteractiveAuth.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
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 { IAuthData } from "matrix-js-sdk/src/interactive-auth";
|
||||
import { UIAResponse } from "matrix-js-sdk/src/@types/uia";
|
||||
|
||||
import Modal from "../Modal";
|
||||
import InteractiveAuthDialog, { InteractiveAuthDialogProps } from "../components/views/dialogs/InteractiveAuthDialog";
|
||||
|
||||
type FunctionWithUIA<R, A> = (auth?: IAuthData, ...args: A[]) => Promise<UIAResponse<R>>;
|
||||
|
||||
export function wrapRequestWithDialog<R, A = any>(
|
||||
requestFunction: FunctionWithUIA<R, A>,
|
||||
opts: Omit<InteractiveAuthDialogProps, "makeRequest" | "onFinished">,
|
||||
): ((...args: A[]) => Promise<R>) {
|
||||
return async function(...args): Promise<R> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const boundFunction = requestFunction.bind(opts.matrixClient) as FunctionWithUIA<R, A>;
|
||||
boundFunction(undefined, ...args)
|
||||
.then((res) => resolve(res as R))
|
||||
.catch(error => {
|
||||
if (error.httpStatus !== 401 || !error.data?.flows) {
|
||||
// doesn't look like an interactive-auth failure
|
||||
return reject(error);
|
||||
}
|
||||
|
||||
Modal.createDialog(InteractiveAuthDialog, {
|
||||
...opts,
|
||||
authData: error.data,
|
||||
makeRequest: (authData) => boundFunction(authData, ...args),
|
||||
onFinished: (success, result) => {
|
||||
if (success) {
|
||||
resolve(result);
|
||||
} else {
|
||||
reject(result);
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
|
@ -27,7 +27,6 @@ import { _t } from "../languageHandler";
|
|||
import ErrorDialog from "../components/views/dialogs/ErrorDialog";
|
||||
import { isMetaSpace } from "../stores/spaces";
|
||||
import SpaceStore from "../stores/spaces/SpaceStore";
|
||||
import { RoomViewStore } from "../stores/RoomViewStore";
|
||||
import dis from "../dispatcher/dispatcher";
|
||||
import { ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload";
|
||||
import { Action } from "../dispatcher/actions";
|
||||
|
@ -35,6 +34,7 @@ import { ViewHomePagePayload } from "../dispatcher/payloads/ViewHomePagePayload"
|
|||
import LeaveSpaceDialog from "../components/views/dialogs/LeaveSpaceDialog";
|
||||
import { AfterLeaveRoomPayload } from "../dispatcher/payloads/AfterLeaveRoomPayload";
|
||||
import { bulkSpaceBehaviour } from "./space";
|
||||
import { SdkContextClass } from "../contexts/SDKContext";
|
||||
|
||||
export async function leaveRoomBehaviour(roomId: string, retry = true, spinner = true) {
|
||||
let spinnerModal: IHandle<any>;
|
||||
|
@ -130,7 +130,7 @@ export async function leaveRoomBehaviour(roomId: string, retry = true, spinner =
|
|||
|
||||
if (!isMetaSpace(SpaceStore.instance.activeSpace) &&
|
||||
SpaceStore.instance.activeSpace !== roomId &&
|
||||
RoomViewStore.instance.getRoomId() === roomId
|
||||
SdkContextClass.instance.roomViewStore.getRoomId() === roomId
|
||||
) {
|
||||
dis.dispatch<ViewRoomPayload>({
|
||||
action: Action.ViewRoom,
|
||||
|
|
|
@ -30,7 +30,6 @@ import { showRoomInviteDialog } from "../RoomInvite";
|
|||
import CreateSubspaceDialog from "../components/views/dialogs/CreateSubspaceDialog";
|
||||
import AddExistingSubspaceDialog from "../components/views/dialogs/AddExistingSubspaceDialog";
|
||||
import defaultDispatcher from "../dispatcher/dispatcher";
|
||||
import { RoomViewStore } from "../stores/RoomViewStore";
|
||||
import { Action } from "../dispatcher/actions";
|
||||
import Spinner from "../components/views/elements/Spinner";
|
||||
import { shouldShowComponent } from "../customisations/helpers/UIComponents";
|
||||
|
@ -38,6 +37,7 @@ import { UIComponent } from "../settings/UIFeature";
|
|||
import { OpenSpacePreferencesPayload, SpacePreferenceTab } from "../dispatcher/payloads/OpenSpacePreferencesPayload";
|
||||
import { OpenSpaceSettingsPayload } from "../dispatcher/payloads/OpenSpaceSettingsPayload";
|
||||
import { OpenAddExistingToSpaceDialogPayload } from "../dispatcher/payloads/OpenAddExistingToSpaceDialogPayload";
|
||||
import { SdkContextClass } from "../contexts/SDKContext";
|
||||
|
||||
export const shouldShowSpaceSettings = (space: Room) => {
|
||||
const userId = space.client.getUserId();
|
||||
|
@ -113,7 +113,7 @@ export const showAddExistingSubspace = (space: Room): void => {
|
|||
space,
|
||||
onCreateSubspaceClick: () => showCreateNewSubspace(space),
|
||||
onFinished: (added: boolean) => {
|
||||
if (added && RoomViewStore.instance.getRoomId() === space.roomId) {
|
||||
if (added && SdkContextClass.instance.roomViewStore.getRoomId() === space.roomId) {
|
||||
defaultDispatcher.fire(Action.UpdateSpaceHierarchy);
|
||||
}
|
||||
},
|
||||
|
@ -125,7 +125,7 @@ export const showCreateNewSubspace = (space: Room): void => {
|
|||
space,
|
||||
onAddExistingSpaceClick: () => showAddExistingSubspace(space),
|
||||
onFinished: (added: boolean) => {
|
||||
if (added && RoomViewStore.instance.getRoomId() === space.roomId) {
|
||||
if (added && SdkContextClass.instance.roomViewStore.getRoomId() === space.roomId) {
|
||||
defaultDispatcher.fire(Action.UpdateSpaceHierarchy);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -35,6 +35,7 @@ export * from "./components/molecules/VoiceBroadcastRecordingPip";
|
|||
export * from "./hooks/useVoiceBroadcastRecording";
|
||||
export * from "./stores/VoiceBroadcastPlaybacksStore";
|
||||
export * from "./stores/VoiceBroadcastRecordingsStore";
|
||||
export * from "./utils/hasRoomLiveVoiceBroadcast";
|
||||
export * from "./utils/shouldDisplayAsVoiceBroadcastRecordingTile";
|
||||
export * from "./utils/shouldDisplayAsVoiceBroadcastTile";
|
||||
export * from "./utils/startNewVoiceBroadcastRecording";
|
||||
|
|
54
src/voice-broadcast/utils/hasRoomLiveVoiceBroadcast.ts
Normal file
54
src/voice-broadcast/utils/hasRoomLiveVoiceBroadcast.ts
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
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 { MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from "..";
|
||||
|
||||
interface Result {
|
||||
hasBroadcast: boolean;
|
||||
startedByUser: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds out whether there is a live broadcast in a room.
|
||||
* Also returns if the user started the broadcast (if any).
|
||||
*/
|
||||
export const hasRoomLiveVoiceBroadcast = (room: Room, userId: string): Result => {
|
||||
let hasBroadcast = false;
|
||||
let startedByUser = false;
|
||||
|
||||
const stateEvents = room.currentState.getStateEvents(VoiceBroadcastInfoEventType);
|
||||
stateEvents.forEach((event: MatrixEvent) => {
|
||||
const state = event.getContent()?.state;
|
||||
|
||||
if (state && state !== VoiceBroadcastInfoState.Stopped) {
|
||||
hasBroadcast = true;
|
||||
|
||||
// state key = sender's MXID
|
||||
if (event.getStateKey() === userId) {
|
||||
startedByUser = true;
|
||||
// break here, because more than true / true is not possible
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
hasBroadcast,
|
||||
startedByUser,
|
||||
};
|
||||
};
|
|
@ -1,76 +0,0 @@
|
|||
/*
|
||||
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 { ISendEventResponse, MatrixClient, RoomStateEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { defer } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import {
|
||||
VoiceBroadcastInfoEventContent,
|
||||
VoiceBroadcastInfoEventType,
|
||||
VoiceBroadcastInfoState,
|
||||
VoiceBroadcastRecordingsStore,
|
||||
VoiceBroadcastRecording,
|
||||
} from "..";
|
||||
|
||||
/**
|
||||
* Starts a new Voice Broadcast Recording.
|
||||
* Sends a voice_broadcast_info state event and waits for the event to actually appear in the room state.
|
||||
*/
|
||||
export const startNewVoiceBroadcastRecording = async (
|
||||
roomId: string,
|
||||
client: MatrixClient,
|
||||
recordingsStore: VoiceBroadcastRecordingsStore,
|
||||
): Promise<VoiceBroadcastRecording> => {
|
||||
const room = client.getRoom(roomId);
|
||||
const { promise, resolve } = defer<VoiceBroadcastRecording>();
|
||||
let result: ISendEventResponse = null;
|
||||
|
||||
const onRoomStateEvents = () => {
|
||||
if (!result) return;
|
||||
|
||||
const voiceBroadcastEvent = room.currentState.getStateEvents(
|
||||
VoiceBroadcastInfoEventType,
|
||||
client.getUserId(),
|
||||
);
|
||||
|
||||
if (voiceBroadcastEvent?.getId() === result.event_id) {
|
||||
room.off(RoomStateEvent.Events, onRoomStateEvents);
|
||||
const recording = new VoiceBroadcastRecording(
|
||||
voiceBroadcastEvent,
|
||||
client,
|
||||
);
|
||||
recordingsStore.setCurrent(recording);
|
||||
recording.start();
|
||||
resolve(recording);
|
||||
}
|
||||
};
|
||||
|
||||
room.on(RoomStateEvent.Events, onRoomStateEvents);
|
||||
|
||||
// XXX Michael W: refactor to live event
|
||||
result = await client.sendStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoEventType,
|
||||
{
|
||||
device_id: client.getDeviceId(),
|
||||
state: VoiceBroadcastInfoState.Started,
|
||||
chunk_length: 300,
|
||||
} as VoiceBroadcastInfoEventContent,
|
||||
client.getUserId(),
|
||||
);
|
||||
|
||||
return promise;
|
||||
};
|
136
src/voice-broadcast/utils/startNewVoiceBroadcastRecording.tsx
Normal file
136
src/voice-broadcast/utils/startNewVoiceBroadcastRecording.tsx
Normal file
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
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 { ISendEventResponse, MatrixClient, Room, RoomStateEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { defer } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import { _t } from "../../languageHandler";
|
||||
import InfoDialog from "../../components/views/dialogs/InfoDialog";
|
||||
import Modal from "../../Modal";
|
||||
import {
|
||||
VoiceBroadcastInfoEventContent,
|
||||
VoiceBroadcastInfoEventType,
|
||||
VoiceBroadcastInfoState,
|
||||
VoiceBroadcastRecordingsStore,
|
||||
VoiceBroadcastRecording,
|
||||
hasRoomLiveVoiceBroadcast,
|
||||
} from "..";
|
||||
|
||||
const startBroadcast = async (
|
||||
room: Room,
|
||||
client: MatrixClient,
|
||||
recordingsStore: VoiceBroadcastRecordingsStore,
|
||||
): Promise<VoiceBroadcastRecording> => {
|
||||
const { promise, resolve } = defer<VoiceBroadcastRecording>();
|
||||
let result: ISendEventResponse = null;
|
||||
|
||||
const onRoomStateEvents = () => {
|
||||
if (!result) return;
|
||||
|
||||
const voiceBroadcastEvent = room.currentState.getStateEvents(
|
||||
VoiceBroadcastInfoEventType,
|
||||
client.getUserId(),
|
||||
);
|
||||
|
||||
if (voiceBroadcastEvent?.getId() === result.event_id) {
|
||||
room.off(RoomStateEvent.Events, onRoomStateEvents);
|
||||
const recording = new VoiceBroadcastRecording(
|
||||
voiceBroadcastEvent,
|
||||
client,
|
||||
);
|
||||
recordingsStore.setCurrent(recording);
|
||||
recording.start();
|
||||
resolve(recording);
|
||||
}
|
||||
};
|
||||
|
||||
room.on(RoomStateEvent.Events, onRoomStateEvents);
|
||||
|
||||
// XXX Michael W: refactor to live event
|
||||
result = await client.sendStateEvent(
|
||||
room.roomId,
|
||||
VoiceBroadcastInfoEventType,
|
||||
{
|
||||
device_id: client.getDeviceId(),
|
||||
state: VoiceBroadcastInfoState.Started,
|
||||
chunk_length: 300,
|
||||
} as VoiceBroadcastInfoEventContent,
|
||||
client.getUserId(),
|
||||
);
|
||||
|
||||
return promise;
|
||||
};
|
||||
|
||||
const showAlreadyRecordingDialog = () => {
|
||||
Modal.createDialog(InfoDialog, {
|
||||
title: _t("Can't start a new voice broadcast"),
|
||||
description: <p>{ _t("You are already recording a voice broadcast. "
|
||||
+ "Please end your current voice broadcast to start a new one.") }</p>,
|
||||
hasCloseButton: true,
|
||||
});
|
||||
};
|
||||
|
||||
const showInsufficientPermissionsDialog = () => {
|
||||
Modal.createDialog(InfoDialog, {
|
||||
title: _t("Can't start a new voice broadcast"),
|
||||
description: <p>{ _t("You don't have the required permissions to start a voice broadcast in this room. "
|
||||
+ "Contact a room administrator to upgrade your permissions.") }</p>,
|
||||
hasCloseButton: true,
|
||||
});
|
||||
};
|
||||
|
||||
const showOthersAlreadyRecordingDialog = () => {
|
||||
Modal.createDialog(InfoDialog, {
|
||||
title: _t("Can't start a new voice broadcast"),
|
||||
description: <p>{ _t("Someone else is already recording a voice broadcast. "
|
||||
+ "Wait for their voice broadcast to end to start a new one.") }</p>,
|
||||
hasCloseButton: true,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Starts a new Voice Broadcast Recording, if
|
||||
* - the user has the permissions to do so in the room
|
||||
* - there is no other broadcast being recorded in the room, yet
|
||||
* Sends a voice_broadcast_info state event and waits for the event to actually appear in the room state.
|
||||
*/
|
||||
export const startNewVoiceBroadcastRecording = async (
|
||||
room: Room,
|
||||
client: MatrixClient,
|
||||
recordingsStore: VoiceBroadcastRecordingsStore,
|
||||
): Promise<VoiceBroadcastRecording | null> => {
|
||||
const currentUserId = client.getUserId();
|
||||
|
||||
if (!room.currentState.maySendStateEvent(VoiceBroadcastInfoEventType, currentUserId)) {
|
||||
showInsufficientPermissionsDialog();
|
||||
return null;
|
||||
}
|
||||
|
||||
const { hasBroadcast, startedByUser } = hasRoomLiveVoiceBroadcast(room, currentUserId);
|
||||
|
||||
if (hasBroadcast && startedByUser) {
|
||||
showAlreadyRecordingDialog();
|
||||
return null;
|
||||
}
|
||||
|
||||
if (hasBroadcast) {
|
||||
showOthersAlreadyRecordingDialog();
|
||||
return null;
|
||||
}
|
||||
|
||||
return startBroadcast(room, client, recordingsStore);
|
||||
};
|
|
@ -21,9 +21,9 @@ import { Command, Commands, getCommand } from '../src/SlashCommands';
|
|||
import { createTestClient } from './test-utils';
|
||||
import { MatrixClientPeg } from '../src/MatrixClientPeg';
|
||||
import { LocalRoom, LOCAL_ROOM_ID_PREFIX } from '../src/models/LocalRoom';
|
||||
import { RoomViewStore } from '../src/stores/RoomViewStore';
|
||||
import SettingsStore from '../src/settings/SettingsStore';
|
||||
import LegacyCallHandler from '../src/LegacyCallHandler';
|
||||
import { SdkContextClass } from '../src/contexts/SDKContext';
|
||||
|
||||
describe('SlashCommands', () => {
|
||||
let client: MatrixClient;
|
||||
|
@ -38,14 +38,14 @@ describe('SlashCommands', () => {
|
|||
};
|
||||
|
||||
const setCurrentRoom = (): void => {
|
||||
mocked(RoomViewStore.instance.getRoomId).mockReturnValue(roomId);
|
||||
mocked(SdkContextClass.instance.roomViewStore.getRoomId).mockReturnValue(roomId);
|
||||
mocked(client.getRoom).mockImplementation((rId: string): Room => {
|
||||
if (rId === roomId) return room;
|
||||
});
|
||||
};
|
||||
|
||||
const setCurrentLocalRoon = (): void => {
|
||||
mocked(RoomViewStore.instance.getRoomId).mockReturnValue(localRoomId);
|
||||
mocked(SdkContextClass.instance.roomViewStore.getRoomId).mockReturnValue(localRoomId);
|
||||
mocked(client.getRoom).mockImplementation((rId: string): Room => {
|
||||
if (rId === localRoomId) return localRoom;
|
||||
});
|
||||
|
@ -60,7 +60,7 @@ describe('SlashCommands', () => {
|
|||
room = new Room(roomId, client, client.getUserId());
|
||||
localRoom = new LocalRoom(localRoomId, client, client.getUserId());
|
||||
|
||||
jest.spyOn(RoomViewStore.instance, "getRoomId");
|
||||
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId");
|
||||
});
|
||||
|
||||
describe('/topic', () => {
|
||||
|
|
44
test/TestStores.ts
Normal file
44
test/TestStores.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
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 { SdkContextClass } from "../src/contexts/SDKContext";
|
||||
import { PosthogAnalytics } from "../src/PosthogAnalytics";
|
||||
import { SlidingSyncManager } from "../src/SlidingSyncManager";
|
||||
import { RoomNotificationStateStore } from "../src/stores/notifications/RoomNotificationStateStore";
|
||||
import RightPanelStore from "../src/stores/right-panel/RightPanelStore";
|
||||
import { RoomViewStore } from "../src/stores/RoomViewStore";
|
||||
import { SpaceStoreClass } from "../src/stores/spaces/SpaceStore";
|
||||
import { WidgetLayoutStore } from "../src/stores/widgets/WidgetLayoutStore";
|
||||
import WidgetStore from "../src/stores/WidgetStore";
|
||||
|
||||
/**
|
||||
* A class which provides the same API as Stores but adds additional unsafe setters which can
|
||||
* replace individual stores. This is useful for tests which need to mock out stores.
|
||||
*/
|
||||
export class TestStores extends SdkContextClass {
|
||||
public _RightPanelStore?: RightPanelStore;
|
||||
public _RoomNotificationStateStore?: RoomNotificationStateStore;
|
||||
public _RoomViewStore?: RoomViewStore;
|
||||
public _WidgetLayoutStore?: WidgetLayoutStore;
|
||||
public _WidgetStore?: WidgetStore;
|
||||
public _PosthogAnalytics?: PosthogAnalytics;
|
||||
public _SlidingSyncManager?: SlidingSyncManager;
|
||||
public _SpaceStore?: SpaceStoreClass;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue