{
return null;
}
- const state = getRoomNotifsState(this.props.room.roomId);
+ const state = this.roomProps.notificationVolume;
let contextMenu = null;
if (this.state.notificationsMenuPosition) {
diff --git a/src/components/views/settings/E2eAdvancedPanel.js b/src/components/views/settings/E2eAdvancedPanel.js
index 709465bcb0..2ba6190a9b 100644
--- a/src/components/views/settings/E2eAdvancedPanel.js
+++ b/src/components/views/settings/E2eAdvancedPanel.js
@@ -18,7 +18,7 @@ import React from 'react';
import * as sdk from '../../../index';
import {_t} from "../../../languageHandler";
-import {SettingLevel} from "../../../settings/SettingsStore";
+import {SettingLevel} from "../../../settings/SettingLevel";
const SETTING_MANUALLY_VERIFY_ALL_SESSIONS = "e2ee.manuallyVerifyAllSessions";
diff --git a/src/components/views/settings/EventIndexPanel.js b/src/components/views/settings/EventIndexPanel.js
index 6fd1247c4b..35c751d91e 100644
--- a/src/components/views/settings/EventIndexPanel.js
+++ b/src/components/views/settings/EventIndexPanel.js
@@ -20,10 +20,11 @@ import { _t } from '../../../languageHandler';
import SdkConfig from "../../../SdkConfig";
import * as sdk from '../../../index';
import Modal from '../../../Modal';
-import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
+import SettingsStore from "../../../settings/SettingsStore";
import AccessibleButton from "../elements/AccessibleButton";
import {formatBytes, formatCountLong} from "../../../utils/FormattingUtils";
import EventIndexPeg from "../../../indexing/EventIndexPeg";
+import {SettingLevel} from "../../../settings/SettingLevel";
export default class EventIndexPanel extends React.Component {
constructor() {
diff --git a/src/components/views/settings/Notifications.js b/src/components/views/settings/Notifications.js
index cbdf3ec1a7..5c16e1ed4f 100644
--- a/src/components/views/settings/Notifications.js
+++ b/src/components/views/settings/Notifications.js
@@ -20,7 +20,7 @@ import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
-import SettingsStore, {SettingLevel} from '../../../settings/SettingsStore';
+import SettingsStore from '../../../settings/SettingsStore';
import Modal from '../../../Modal';
import {
NotificationUtils,
@@ -31,6 +31,7 @@ import {
import SdkConfig from "../../../SdkConfig";
import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
import AccessibleButton from "../elements/AccessibleButton";
+import {SettingLevel} from "../../../settings/SettingLevel";
// TODO: this "view" component still has far too much application logic in it,
// which should be factored out to other files.
diff --git a/src/components/views/settings/SetIntegrationManager.js b/src/components/views/settings/SetIntegrationManager.js
index da2953482f..e6fb3f6e1c 100644
--- a/src/components/views/settings/SetIntegrationManager.js
+++ b/src/components/views/settings/SetIntegrationManager.js
@@ -18,7 +18,8 @@ import React from 'react';
import {_t} from "../../../languageHandler";
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
import * as sdk from '../../../index';
-import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
+import SettingsStore from "../../../settings/SettingsStore";
+import {SettingLevel} from "../../../settings/SettingLevel";
export default class SetIntegrationManager extends React.Component {
constructor() {
diff --git a/src/components/views/settings/tabs/room/NotificationSettingsTab.js b/src/components/views/settings/tabs/room/NotificationSettingsTab.js
index 257f4a5d23..dd88b5018f 100644
--- a/src/components/views/settings/tabs/room/NotificationSettingsTab.js
+++ b/src/components/views/settings/tabs/room/NotificationSettingsTab.js
@@ -21,7 +21,7 @@ import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
import AccessibleButton from "../../../elements/AccessibleButton";
import Notifier from "../../../../../Notifier";
import SettingsStore from '../../../../../settings/SettingsStore';
-import { SettingLevel } from '../../../../../settings/SettingsStore';
+import {SettingLevel} from "../../../../../settings/SettingLevel";
export default class NotificationsSettingsTab extends React.Component {
static propTypes = {
diff --git a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.js b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.js
index c67596a3a5..596344d7cd 100644
--- a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.js
+++ b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.js
@@ -20,9 +20,9 @@ import {_t} from "../../../../../languageHandler";
import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
import * as sdk from "../../../../..";
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
-import {SettingLevel} from "../../../../../settings/SettingsStore";
import Modal from "../../../../../Modal";
import QuestionDialog from "../../../dialogs/QuestionDialog";
+import {SettingLevel} from "../../../../../settings/SettingLevel";
export default class SecurityRoomSettingsTab extends React.Component {
static propTypes = {
diff --git a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx
index 9f3b8ba46c..c646025bbe 100644
--- a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx
+++ b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx
@@ -18,7 +18,7 @@ limitations under the License.
import React from 'react';
import {_t} from "../../../../../languageHandler";
import SdkConfig from "../../../../../SdkConfig";
-import SettingsStore, {SettingLevel} from "../../../../../settings/SettingsStore";
+import SettingsStore from "../../../../../settings/SettingsStore";
import { enumerateThemes } from "../../../../../theme";
import ThemeWatcher from "../../../../../settings/watchers/ThemeWatcher";
import Slider from "../../../elements/Slider";
@@ -35,6 +35,7 @@ import Field from '../../../elements/Field';
import EventTilePreview from '../../../elements/EventTilePreview';
import StyledRadioGroup from "../../../elements/StyledRadioGroup";
import classNames from 'classnames';
+import { SettingLevel } from "../../../../../settings/SettingLevel";
interface IProps {
}
diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js
index c7e7ce7c2c..1ebefae590 100644
--- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js
+++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js
@@ -20,7 +20,6 @@ import React from 'react';
import {_t} from "../../../../../languageHandler";
import ProfileSettings from "../../ProfileSettings";
import * as languageHandler from "../../../../../languageHandler";
-import {SettingLevel} from "../../../../../settings/SettingsStore";
import SettingsStore from "../../../../../settings/SettingsStore";
import LanguageDropdown from "../../../elements/LanguageDropdown";
import AccessibleButton from "../../../elements/AccessibleButton";
@@ -37,6 +36,7 @@ import IdentityAuthClient from "../../../../../IdentityAuthClient";
import {abbreviateUrl} from "../../../../../utils/UrlUtils";
import { getThreepidsWithBindStatus } from '../../../../../boundThreepids';
import Spinner from "../../../elements/Spinner";
+import {SettingLevel} from "../../../../../settings/SettingLevel";
export default class GeneralUserSettingsTab extends React.Component {
static propTypes = {
diff --git a/src/components/views/settings/tabs/user/LabsUserSettingsTab.js b/src/components/views/settings/tabs/user/LabsUserSettingsTab.js
index 2b8d7c5d3f..25dfd9e100 100644
--- a/src/components/views/settings/tabs/user/LabsUserSettingsTab.js
+++ b/src/components/views/settings/tabs/user/LabsUserSettingsTab.js
@@ -17,9 +17,10 @@ limitations under the License.
import React from 'react';
import {_t} from "../../../../../languageHandler";
import PropTypes from "prop-types";
-import SettingsStore, {SettingLevel} from "../../../../../settings/SettingsStore";
+import SettingsStore from "../../../../../settings/SettingsStore";
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
import * as sdk from "../../../../../index";
+import {SettingLevel} from "../../../../../settings/SettingLevel";
export class LabsSettingToggle extends React.Component {
static propTypes = {
diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js
index fe60a4a179..a77815a68c 100644
--- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js
+++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js
@@ -17,12 +17,12 @@ limitations under the License.
import React from 'react';
import {_t} from "../../../../../languageHandler";
-import {SettingLevel} from "../../../../../settings/SettingsStore";
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
import SettingsStore from "../../../../../settings/SettingsStore";
import Field from "../../../elements/Field";
import * as sdk from "../../../../..";
import PlatformPeg from "../../../../../PlatformPeg";
+import {SettingLevel} from "../../../../../settings/SettingLevel";
export default class PreferencesUserSettingsTab extends React.Component {
static ROOM_LIST_SETTINGS = [
diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js
index 591927411d..fda7ccb005 100644
--- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js
+++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js
@@ -19,7 +19,6 @@ import React from 'react';
import PropTypes from 'prop-types';
import {_t} from "../../../../../languageHandler";
import SdkConfig from "../../../../../SdkConfig";
-import {SettingLevel} from "../../../../../settings/SettingsStore";
import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
import * as FormattingUtils from "../../../../../utils/FormattingUtils";
import AccessibleButton from "../../../elements/AccessibleButton";
@@ -29,6 +28,7 @@ import * as sdk from "../../../../..";
import {sleep} from "../../../../../utils/promise";
import dis from "../../../../../dispatcher/dispatcher";
import {privateShouldBeEncrypted} from "../../../../../createRoom";
+import {SettingLevel} from "../../../../../settings/SettingLevel";
export class IgnoredUser extends React.Component {
static propTypes = {
diff --git a/src/components/views/settings/tabs/user/VoiceUserSettingsTab.js b/src/components/views/settings/tabs/user/VoiceUserSettingsTab.js
index efd184069e..4114f6bb22 100644
--- a/src/components/views/settings/tabs/user/VoiceUserSettingsTab.js
+++ b/src/components/views/settings/tabs/user/VoiceUserSettingsTab.js
@@ -21,10 +21,10 @@ import SdkConfig from "../../../../../SdkConfig";
import CallMediaHandler from "../../../../../CallMediaHandler";
import Field from "../../../elements/Field";
import AccessibleButton from "../../../elements/AccessibleButton";
-import {SettingLevel} from "../../../../../settings/SettingsStore";
import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
import * as sdk from "../../../../../index";
import Modal from "../../../../../Modal";
+import {SettingLevel} from "../../../../../settings/SettingLevel";
export default class VoiceUserSettingsTab extends React.Component {
constructor() {
diff --git a/src/components/views/toasts/NonUrgentEchoFailureToast.tsx b/src/components/views/toasts/NonUrgentEchoFailureToast.tsx
new file mode 100644
index 0000000000..76d0328e8b
--- /dev/null
+++ b/src/components/views/toasts/NonUrgentEchoFailureToast.tsx
@@ -0,0 +1,40 @@
+/*
+Copyright 2020 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from "react";
+import { _t } from "../../../languageHandler";
+import AccessibleButton from "../elements/AccessibleButton";
+import Modal from "../../../Modal";
+import ServerOfflineDialog from "../dialogs/ServerOfflineDialog";
+
+export default class NonUrgentEchoFailureToast extends React.PureComponent {
+ private openDialog = () => {
+ Modal.createTrackedDialog('Local Echo Server Error', '', ServerOfflineDialog, {});
+ };
+
+ public render() {
+ return (
+
+
+ {_t("Your server isn't responding to some
requests .", {}, {
+ 'a': (sub) => (
+
{sub}
+ ),
+ })}
+
+ )
+ }
+}
diff --git a/src/emojipicker/recent.ts b/src/emojipicker/recent.ts
index 1ba15d87b8..d86aad660d 100644
--- a/src/emojipicker/recent.ts
+++ b/src/emojipicker/recent.ts
@@ -15,8 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import SettingsStore, {SettingLevel} from "../settings/SettingsStore";
+import SettingsStore from "../settings/SettingsStore";
import {orderBy} from "lodash";
+import { SettingLevel } from "../settings/SettingLevel";
interface ILegacyFormat {
[emoji: string]: [number, number]; // [count, date]
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 46d9f4cdf3..579414c5e9 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -443,6 +443,7 @@
"%(senderName)s: %(message)s": "%(senderName)s: %(message)s",
"%(senderName)s: %(reaction)s": "%(senderName)s: %(reaction)s",
"%(senderName)s: %(stickerName)s": "%(senderName)s: %(stickerName)s",
+ "Change notification settings": "Change notification settings",
"New spinner design": "New spinner design",
"Message Pinning": "Message Pinning",
"Custom user status messages": "Custom user status messages",
@@ -613,6 +614,7 @@
"Headphones": "Headphones",
"Folder": "Folder",
"Pin": "Pin",
+ "Your server isn't responding to some requests .": "Your server isn't responding to some requests .",
"From %(deviceName)s (%(deviceId)s)": "From %(deviceName)s (%(deviceId)s)",
"Decline (%(counter)s)": "Decline (%(counter)s)",
"Accept to continue:": "Accept to continue:",
@@ -1745,6 +1747,19 @@
"Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.": "Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.",
"This usually only affects how the room is processed on the server. If you're having problems with your %(brand)s, please report a bug .": "This usually only affects how the room is processed on the server. If you're having problems with your %(brand)s, please report a bug .",
"You'll upgrade this room from to .": "You'll upgrade this room from to .",
+ "Resend": "Resend",
+ "You're all caught up.": "You're all caught up.",
+ "Server isn't responding": "Server isn't responding",
+ "Your server isn't responding to some of your requests. Below are some of the most likely reasons.": "Your server isn't responding to some of your requests. Below are some of the most likely reasons.",
+ "The server (%(serverName)s) took too long to respond.": "The server (%(serverName)s) took too long to respond.",
+ "Your firewall or anti-virus is blocking the request.": "Your firewall or anti-virus is blocking the request.",
+ "A browser extension is preventing the request.": "A browser extension is preventing the request.",
+ "The server is offline.": "The server is offline.",
+ "The server has denied your request.": "The server has denied your request.",
+ "Your area is experiencing difficulties connecting to the internet.": "Your area is experiencing difficulties connecting to the internet.",
+ "A connection error occurred while trying to contact the server.": "A connection error occurred while trying to contact the server.",
+ "The server is not configured to indicate what the problem is (CORS).": "The server is not configured to indicate what the problem is (CORS).",
+ "Recent changes that have not yet been received": "Recent changes that have not yet been received",
"Sign out and remove encryption keys?": "Sign out and remove encryption keys?",
"Clear Storage and Sign Out": "Clear Storage and Sign Out",
"Send Logs": "Send Logs",
@@ -1852,7 +1867,6 @@
"Reject invitation": "Reject invitation",
"Are you sure you want to reject the invitation?": "Are you sure you want to reject the invitation?",
"Unable to reject invite": "Unable to reject invite",
- "Resend": "Resend",
"Resend edit": "Resend edit",
"Resend %(unsentCount)s reaction(s)": "Resend %(unsentCount)s reaction(s)",
"Resend removal": "Resend removal",
@@ -1960,7 +1974,8 @@
"Couldn't load page": "Couldn't load page",
"You must register to use this functionality": "You must register to use this functionality",
"You must join the room to see its files": "You must join the room to see its files",
- "There are no visible files in this room": "There are no visible files in this room",
+ "No files visible in this room": "No files visible in this room",
+ "Attach files from chat or just drag and drop them anywhere in a room.": "Attach files from chat or just drag and drop them anywhere in a room.",
"HTML for your community's page \n\n Use the long description to introduce new members to the community, or distribute\n some important links \n
\n\n You can even use 'img' tags\n
\n": "HTML for your community's page \n\n Use the long description to introduce new members to the community, or distribute\n some important links \n
\n\n You can even use 'img' tags\n
\n",
"Add rooms to the community summary": "Add rooms to the community summary",
"Which rooms would you like to add to this summary?": "Which rooms would you like to add to this summary?",
@@ -2033,7 +2048,8 @@
"Communities": "Communities",
"Create a new community": "Create a new community",
"Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.": "Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.",
- "You have no visible notifications": "You have no visible notifications",
+ "You’re all caught up": "You’re all caught up",
+ "You have no visible notifications in this room.": "You have no visible notifications in this room.",
"%(brand)s failed to get the protocol list from the homeserver. The homeserver may be too old to support third party networks.": "%(brand)s failed to get the protocol list from the homeserver. The homeserver may be too old to support third party networks.",
"%(brand)s failed to get the public room list.": "%(brand)s failed to get the public room list.",
"The homeserver may be unavailable or overloaded.": "The homeserver may be unavailable or overloaded.",
diff --git a/src/i18n/strings/eo.json b/src/i18n/strings/eo.json
index cda198ea02..8d07db7eaf 100644
--- a/src/i18n/strings/eo.json
+++ b/src/i18n/strings/eo.json
@@ -1721,8 +1721,8 @@
"To be secure, do this in person or use a trusted way to communicate.": "Por plia sekureco, faru tion persone, aŭ uzu alian fidatan komunikilon.",
"Verify yourself & others to keep your chats safe": "Kontrolu vin mem kaj aliajn por sekurigi viajn babilojn",
"Channel: %(channelName)s": "Kanalo: %(channelName)s",
- "Show less": "Montri pli",
- "Show more": "Montri malpli",
+ "Show less": "Montri malpli",
+ "Show more": "Montri pli",
"Help": "Helpo",
"Session verified": "Salutaĵo kontroliĝis",
"Copy": "Kopii",
@@ -2376,7 +2376,7 @@
"Set a Security Phrase": "Agordi Sekurecan frazon",
"Confirm Security Phrase": "Konfirmi Sekurecan frazon",
"Save your Security Key": "Konservi vian Sekurecan ŝlosilon",
- "New spinner design": "",
+ "New spinner design": "Nova fasono de la turniĝilo",
"Show rooms with unread messages first": "Montri ĉambrojn kun nelegitaj mesaĝoj kiel unuajn",
"Show previews of messages": "Montri antaŭrigardojn al mesaĝoj",
"This room is public": "Ĉi tiu ĉambro estas publika",
@@ -2386,5 +2386,6 @@
"Are you sure you want to cancel entering passphrase?": "Ĉu vi certe volas nuligi enigon de pasfrazo?",
"Enable advanced debugging for the room list": "Ŝalti altnivelan erarserĉadon por la ĉambrobreto",
"* %(senderName)s %(emote)s": "* %(senderName)s %(emote)s",
- "Custom Tag": "Propra etikedo"
+ "Custom Tag": "Propra etikedo",
+ "Feedback": "Prikomenti"
}
diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json
index 17daca64bc..271893b862 100644
--- a/src/i18n/strings/et.json
+++ b/src/i18n/strings/et.json
@@ -1714,10 +1714,10 @@
"%(role)s in %(roomName)s": "%(role)s jututoas %(roomName)s",
"Failed to change power level": "Õiguste muutmine ei õnnestunud",
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "Sa ei saa seda muudatust hiljem tagasi pöörata, sest annad teisele kasutajale samad õigused, mis sinul on.",
- "Deactivate user?": "Kas blokeerime kasutaja?",
- "Deactivating this user will log them out and prevent them from logging back in. Additionally, they will leave all the rooms they are in. This action cannot be reversed. Are you sure you want to deactivate this user?": "Kasutaja blokeerimisel logitakse ta automaatselt välja ning ei lubata enam sisse logida. Lisaks lahkub ta kõikidest jututubadest, mille liige ta parasjagu on. Seda tegevust ei saa tagasi pöörata. Kas sa oled ikka kindel, et soovid selle kasutaja blokeerida?",
- "Deactivate user": "Blokeeri kasutaja",
- "Failed to deactivate user": "Kasutaja blokeerimine ei õnnestunud",
+ "Deactivate user?": "Kas deaktiveerime kasutajakonto?",
+ "Deactivating this user will log them out and prevent them from logging back in. Additionally, they will leave all the rooms they are in. This action cannot be reversed. Are you sure you want to deactivate this user?": "Kasutaja deaktiveerimisel logitakse ta automaatselt välja ning ei lubata enam sisse logida. Lisaks lahkub ta kõikidest jututubadest, mille liige ta parasjagu on. Seda tegevust ei saa tagasi pöörata. Kas sa oled ikka kindel, et soovid selle kasutaja kõijkalt eemaldada?",
+ "Deactivate user": "Deaktiveeri kasutaja",
+ "Failed to deactivate user": "Kasutaja deaktiveerimine ei õnnestunud",
"This client does not support end-to-end encryption.": "See klient ei toeta läbivat krüptimist.",
"Security": "Turvalisus",
"Using this widget may share data with %(widgetDomain)s & your Integration Manager.": "Selle vidina kasutamisel võidakse jagada andmeid saitidega %(widgetDomain)s ning sinu vidinahalduriga.",
@@ -2368,5 +2368,22 @@
"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.": "Sa oled selle sessiooni jaoks varem kasutanud %(brand)s'i uuemat versiooni. Selle versiooni kasutamiseks läbiva krüptimisega, pead sa esmalt logima välja ja siis uuesti logima tagasi sisse.",
"Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.": "Kui sa pole seadistanud krüptitud sõnumite taastamise meetodeid, siis väljalogimisel sa kaotad võimaluse neid krüptitud sõnumeid lugeda.",
"If you don't want to set this up now, you can later in Settings.": "Kui sa ei soovi seda teha kohe, siis vastava võimaluse leiad hiljem rakenduse seadistustest.",
- "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Kui sa ei ole ise uusi taastamise meetodeid lisanud, siis võib olla tegemist ründega sinu konto vastu. Palun vaheta koheselt oma kasutajakonto salasõna ning määra seadistustes uus taastemeetod."
+ "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Kui sa ei ole ise uusi taastamise meetodeid lisanud, siis võib olla tegemist ründega sinu konto vastu. Palun vaheta koheselt oma kasutajakonto salasõna ning määra seadistustes uus taastemeetod.",
+ "%(senderDisplayName)s enabled flair for %(groups)s in this room.": "%(senderDisplayName)s võttis selles jututoas kasutusele %(groups)s kogukonna rinnamärgi.",
+ "%(senderDisplayName)s disabled flair for %(groups)s in this room.": "%(senderDisplayName)s eemaldas selles jututoas kasutuselt %(groups)s kogukonna rinnamärgi.",
+ "%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.": "%(senderDisplayName)s võttis selles jututoas kasutusele %(newGroups)s kogukonna rinnamärgi ning eemaldas rinnamärgi %(oldGroups)s kogukonnalt.",
+ "Connect this session to key backup before signing out to avoid losing any keys that may only be on this session.": "Enne väljalogimist seo see sessioon krüptovõtmete varundusega. Kui sa seda ei tee, siis võid kaotada võtmed, mida kasutatakse vaid siin sessioonis.",
+ "Flair": "Kogukonna rinnasilt",
+ "Error updating flair": "Viga kogukonna rinnasildi uuendamisel",
+ "There was an error updating the flair for this room. The server may not allow it or a temporary error occurred.": "Kogukonna rinnasildi uuendamisel tekkis viga. See kas on serveri poolt keelatud või tekkis mingi ajutine viga.",
+ "Showing flair for these communities:": "Näidatakse nende kogukondade rinnasilte:",
+ "This room is not showing flair for any communities": "Sellele jututoale ei ole jagatud ühtegi kogukonna rinnasilti",
+ "Display your community flair in rooms configured to show it.": "Näita oma kogukonna rinnasilti nendes jututubades, kus selle kuvamine on seadistatud.",
+ "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use %(brand)s with an existing Matrix account on a different homeserver.": "Kohandatud serveriseadistusi saad kasutada selleks, et logida sisse sinu valitud koduserverisse. See võimaldab sinul kasutada %(brand)s'i mõnes teises koduserveri hallatava kasutajakontoga.",
+ "Did you know: you can use communities to filter your %(brand)s experience!": "Kas sa teadsid, et sa võid %(brand)s'i parema kasutuskogemuse nimel pruukida kogukondi!",
+ "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another session.": "Kui sa pole seadistanud krüptitud sõnumite taastamise meetodeid, siis väljalogimisel või muu sessiooni kasutamisel sa kaotad võimaluse oma krüptitud sõnumeid lugeda.",
+ "Set up Secure Message Recovery": "Võta kasutusele turvaline sõnumivõtmete varundus",
+ "Secure your backup with a recovery passphrase": "Krüpti oma varukoopia taastamiseks mõeldud paroolifraasiga",
+ "The person who invited you already left the room.": "See, kes sind jututoa liikmeks kutsus, on juba jututoast lahkunud.",
+ "The person who invited you already left the room, or their server is offline.": "See, kes sind jututoa liikmeks kutsus, kas juba on jututoast lahkunud või tema koduserver on võrgust väljas."
}
diff --git a/src/i18n/strings/eu.json b/src/i18n/strings/eu.json
index 9dcfe7ca22..01afa51836 100644
--- a/src/i18n/strings/eu.json
+++ b/src/i18n/strings/eu.json
@@ -326,7 +326,7 @@
"Upload an avatar:": "Igo abatarra:",
"This server does not support authentication with a phone number.": "Zerbitzari honek ez du telefono zenbakia erabiliz autentifikatzea onartzen.",
"An error occurred: %(error_string)s": "Errore bat gertatu da: %(error_string)s",
- "There are no visible files in this room": "Ez dago fitxategi ikusgairik gela honetan",
+ "There are no visible files in this room": "Ez dago fitxategirik ikusgai gela honetan",
"Sent messages will be stored until your connection has returned.": "Bidalitako mezuak zure konexioa berreskuratu arte gordeko dira.",
"(~%(count)s results)|one": "(~%(count)s emaitza)",
"(~%(count)s results)|other": "(~%(count)s emaitza)",
@@ -734,7 +734,7 @@
"All Rooms": "Gela guztiak",
"Wednesday": "Asteazkena",
"You cannot delete this message. (%(code)s)": "Ezin duzu mezu hau ezabatu. (%(code)s)",
- "Quote": "Aipua",
+ "Quote": "Aipatu",
"Send logs": "Bidali egunkariak",
"All messages": "Mezu guztiak",
"Call invitation": "Dei gonbidapena",
@@ -1539,7 +1539,7 @@
"View": "Ikusi",
"Find a room…": "Bilatu gela bat…",
"Find a room… (e.g. %(exampleRoom)s)": "Bilatu gela bat… (adib. %(exampleRoom)s)",
- "If you can't find the room you're looking for, ask for an invite or Create a new room .": "Ezin baduzu bila ari zaren gela aurkitu, eskatu gonbidapen bat edo Sortu gela berri bat .",
+ "If you can't find the room you're looking for, ask for an invite or Create a new room .": "Ez baduzu bilatzen duzuna aurkitzen, eskatu gonbidapen bat edo Sortu gela berri bat .",
"Explore rooms": "Arakatu gelak",
"Community Autocomplete": "Komunitate osatze automatikoa",
"Emoji Autocomplete": "Emoji osatze automatikoa",
@@ -1897,7 +1897,7 @@
"If your other sessions do not have the key for this message you will not be able to decrypt them.": "Zure beste saioek ez badute mezu honen gakoa ezin izango duzu deszifratu.",
"Re-request encryption keys from your other sessions.": "Eskatu berriro zifratze gakoak zure beste saioei.",
"Waiting for %(displayName)s to accept…": "%(displayName)s(e)k onartu bitartean zain…",
- "Your messages are secured and only you and the recipient have the unique keys to unlock them.": "Zuon mezuak babestuta daude eta soilik zuk eta hartzaileak dituzue hauek irekitzeko giltza.",
+ "Your messages are secured and only you and the recipient have the unique keys to unlock them.": "Zuen mezuak babestuta daude eta soilik zuk eta hartzaileak dituzue hauek desblokeatzeko gakoak.",
"One of the following may be compromised:": "Hauetakoren bat konprometituta egon daiteke:",
"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.": "Egiztatu gailu hau fidagarri gisa markatzeko. Gailu hau fidagarritzat jotzeak lasaitasuna ematen du muturretik-muturrera zifratutako mezuak erabiltzean.",
"Verifying this device will mark it as trusted, and users who have verified with you will trust this device.": "Gailu hau egiaztatzean fidagarri gisa markatuko da, eta egiaztatu zaituzten erabiltzaileek fidagarri gisa ikusiko dute.",
@@ -2248,5 +2248,23 @@
"Security & privacy": "Segurtasuna eta pribatutasuna",
"All settings": "Ezarpen guztiak",
"Feedback": "Iruzkinak",
- "Use a different passphrase?": "Erabili pasa-esaldi desberdin bat?"
+ "Use a different passphrase?": "Erabili pasa-esaldi desberdin bat?",
+ "We’re excited to announce Riot is now Element": "Pozik jakinarazten dizugu: Riot orain Element deitzen da",
+ "Riot is now Element!": "Riot orain Element da!",
+ "Learn More": "Ikasi gehiago",
+ "Light": "Argia",
+ "The person who invited you already left the room.": "Gonbidatu zaituen pertsonak dagoeneko gela utzi du.",
+ "The person who invited you already left the room, or their server is offline.": "Gonbidatu zaituen pertsonak dagoeneko gela utzi du edo bere zerbitzaria lineaz kanpo dago.",
+ "You joined the call": "Deira batu zara",
+ "%(senderName)s joined the call": "%(senderName)s deira batu da",
+ "You left the call": "Deitik atera zara",
+ "%(senderName)s left the call": "%(senderName)s(e) deitik atera da",
+ "Call ended": "Deia amaitu da",
+ "You started a call": "Dei bat hasi duzu",
+ "%(senderName)s started a call": "%(senderName)s(e)k dei bat hasi du",
+ "%(senderName)s is calling": "%(senderName)s deitzen ari da",
+ "%(brand)s Web": "%(brand)s web",
+ "%(brand)s Desktop": "%(brand)s Desktop",
+ "%(brand)s iOS": "%(brand)s iOS",
+ "%(brand)s X for Android": "%(brand)s X for Android"
}
diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json
index 4ca354c8ec..f06b1e22d9 100644
--- a/src/i18n/strings/gl.json
+++ b/src/i18n/strings/gl.json
@@ -2388,5 +2388,7 @@
"Click to view edits": "Preme para ver as edicións",
"Are you sure you want to cancel entering passphrase?": "¿Estás seguro de que non queres escribir a frase de paso?",
"Custom Tag": "Etiqueta personal",
- "* %(senderName)s %(emote)s": "* %(senderName)s %(emote)s"
+ "* %(senderName)s %(emote)s": "* %(senderName)s %(emote)s",
+ "The person who invited you already left the room.": "A persoa que te convidou xa deixou a sala.",
+ "The person who invited you already left the room, or their server is offline.": "A persoa que te convidou xa deixou a sala, ou o seu servidor non está a funcionar."
}
diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json
index e2f29db47d..209c237c0c 100644
--- a/src/i18n/strings/hu.json
+++ b/src/i18n/strings/hu.json
@@ -2387,5 +2387,22 @@
"Click to view edits": "A szerkesztések megtekintéséhez kattints",
"You’re already signed in and good to go here, but you can also grab the latest versions of the app on all platforms at element.io/get-started .": "Már be vagy jelentkezve és ez rendben van, de minden platformon az alkalmazás legfrissebb verziójának beszerzéséhez látogass el ide: element.io/get-started .",
"You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use %(brand)s with an existing Matrix account on a different homeserver.": "Használhatod a más szerver opciót, hogy egy másik matrix szerverre jelentkezz be amihez megadod a szerver url címét. Ezzel használhatod %(brand)s klienst egy már létező Matrix fiókkal egy másik matrix szerveren.",
- "Enter the location of your Element Matrix Services homeserver. It may use your own domain name or be a subdomain of element.io .": "Add meg az Element Matrix Services matrix szerveredet. Használhatod a saját domain-edet vagy az element.io al-domain-jét."
+ "Enter the location of your Element Matrix Services homeserver. It may use your own domain name or be a subdomain of element.io .": "Add meg az Element Matrix Services matrix szerveredet. Használhatod a saját domain-edet vagy az element.io al-domain-jét.",
+ "* %(senderName)s %(emote)s": "* %(senderName)s %(emote)s",
+ "The person who invited you already left the room.": "Aki meghívott a szobába már távozott.",
+ "The person who invited you already left the room, or their server is offline.": "Aki meghívott a szobába már távozott, vagy a szervere elérhetetlen.",
+ "Change notification settings": "Értesítési beállítások megváltoztatása",
+ "Your server isn't responding to some requests .": "A szervered nem válaszol néhány kérésre .",
+ "You're all caught up.": "Mindent elolvastál.",
+ "Server isn't responding": "A szerver nem válaszol",
+ "Your server isn't responding to some of your requests. Below are some of the most likely reasons.": "A szervered néhány kérésre nem válaszol. Alább felsorolunk pár lehetséges okot.",
+ "The server (%(serverName)s) took too long to respond.": "A szervernek (%(serverName)s) túl hosszú időbe telt válaszolni.",
+ "Your firewall or anti-virus is blocking the request.": "A tűzfalad vagy víruskeresőd blokkolja a kérést.",
+ "A browser extension is preventing the request.": "Egy böngésző kiterjesztés megakadályozza a kérést.",
+ "The server is offline.": "A szerver nem működik.",
+ "The server has denied your request.": "A szerver elutasította a kérést.",
+ "Your area is experiencing difficulties connecting to the internet.": "Probléma az Internet elérésben.",
+ "A connection error occurred while trying to contact the server.": "Kapcsolati hiba lépett fel miközben a szervert próbáltad elérni.",
+ "The server is not configured to indicate what the problem is (CORS).": "A szerver nincs beállítva, hogy megmutassa mi okozhatta a hibát (CORS).",
+ "Recent changes that have not yet been received": "Legutóbbi változások amik még nem érkeztek meg"
}
diff --git a/src/i18n/strings/is.json b/src/i18n/strings/is.json
index 43b286aa2a..6f561331f6 100644
--- a/src/i18n/strings/is.json
+++ b/src/i18n/strings/is.json
@@ -298,7 +298,7 @@
"Low Priority": "Lítill forgangur",
"Direct Chat": "Beint spjall",
"View Community": "Skoða samfélag",
- "I understand the risks and wish to continue": "Ég skil áhættuna og vil halda áfram",
+ "I understand the risks and wish to continue": "Ég skil áhættuna og óska að halda áfram",
"Name": "Nafn",
"Failed to upload image": "Gat ekki sent inn mynd",
"Add rooms to this community": "Bæta spjallrásum í þetta samfélag",
@@ -456,5 +456,7 @@
"Notify the whole room": "Tilkynna öllum á spjallrásinni",
"Room Notification": "Tilkynning á spjallrás",
"Passphrases must match": "Lykilfrasar verða að stemma",
- "Passphrase must not be empty": "Lykilfrasi má ekki vera auður"
+ "Passphrase must not be empty": "Lykilfrasi má ekki vera auður",
+ "Create Account": "Stofna Reikning",
+ "Please install Chrome , Firefox , or Safari for the best experience.": "vinsamlegast setja upp Chrome , Firefox , eða Safari fyrir besta reynsluna."
}
diff --git a/src/i18n/strings/ja.json b/src/i18n/strings/ja.json
index 5387c3a3e8..ee3871fc0f 100644
--- a/src/i18n/strings/ja.json
+++ b/src/i18n/strings/ja.json
@@ -1360,5 +1360,15 @@
"Leave Room": "部屋を退出",
"Failed to connect to integration manager": "インテグレーションマネージャへの接続に失敗しました",
"Start verification again from their profile.": "プロフィールから再度検証を開始してください。",
- "Integration Manager": "インテグレーションマネージャ"
+ "Integration Manager": "インテグレーションマネージャ",
+ "Do not use an identity server": "ID サーバーを使用しない",
+ "Composer": "入力欄",
+ "Sort by": "並び替え",
+ "List options": "一覧の設定",
+ "Use Single Sign On to continue": "シングルサインオンを使用して続行",
+ "Accept to continue:": " に同意して続行:",
+ "Always show the window menu bar": "常にウィンドウメニューバーを表示する",
+ "Create room": "部屋を作成",
+ "Show %(count)s more|other": "さらに %(count)s 件を表示",
+ "Show %(count)s more|one": "さらに %(count)s 件を表示"
}
diff --git a/src/i18n/strings/jbo.json b/src/i18n/strings/jbo.json
index 8937e10cf1..9ac42af1de 100644
--- a/src/i18n/strings/jbo.json
+++ b/src/i18n/strings/jbo.json
@@ -3,7 +3,7 @@
"This phone number is already in use": ".i xa'o pilno fa da le fonxa judri",
"Failed to verify email address: make sure you clicked the link in the email": ".i da nabmi fi lo nu facki le du'u do ponse le te samymri .i ko birti le du'u do samcu'a le judrysni pe le se samymri",
"The platform you're on": "jicmu vau je se pilno do",
- "Your language of choice": "se cuxna fi le ka bangu",
+ "Your language of choice": "se cuxna fo le ka bangu",
"Which officially provided instance you are using, if any": "samtcise'u vau je catni jai te selfu vau je se pilno do",
"Whether or not you're using the Richtext mode of the Rich Text Editor": "jei do pilno le se jadni ciska tadji pe le notci ciska tutci",
"Your homeserver's URL": "judri le samtcise'u",
@@ -30,7 +30,7 @@
"Unable to capture screen": ".i na ka'e facki le du'u vidvi fi le vidni",
"Existing Call": ".i xa'o ca'o fonjo'e",
"Server may be unavailable, overloaded, or you hit a bug.": ".i la'a cu'i gi ja le samtcise'u cu spofu vau ja mutce le ka gunka gi da samcfi",
- "Send": "benji",
+ "Send": "nu zilbe'i",
"Sun": "jy. dy. ze",
"Mon": "jy. dy. pa",
"Tue": "jy. dy. re",
@@ -100,7 +100,7 @@
"/ddg is not a command": "zoi ny. /ddg .ny. na nu minde",
"Changes your display nickname": "",
"Invites user with given id to current room": ".i vi'ecpe lo pilno poi se judri ti ku le kumfa pe'a",
- "Leave room": "nu cliva le ve zilbe'i",
+ "Leave room": "nu do zilvi'u le se zilbe'i",
"Kicks user with given id": ".i rinka lo nu lo pilno poi se judri ti cu cliva",
"Bans user with given id": ".i rinka lo nu lo pilno poi se judri ti cu vitno cliva",
"Ignores a user, hiding their messages from you": ".i rinka lo nu no'e jundi lo pilno gi'e mipri lo notci be fi py. do",
@@ -200,8 +200,8 @@
"Accept": "nu fonjo'e",
"Error": "nabmi",
"Incorrect verification code": ".i na'e drani ke lacri lerpoi",
- "Submit": "benji",
- "Phone": "lo fonxa",
+ "Submit": "nu zilbe'i",
+ "Phone": "fonxa",
"Add": "jmina",
"Failed to upload profile picture!": ".i da nabmi lo nu kibdu'a le pixra sinxa",
"No display name": ".i na da cmene",
diff --git a/src/i18n/strings/pt_BR.json b/src/i18n/strings/pt_BR.json
index c248189b23..60beb2b726 100644
--- a/src/i18n/strings/pt_BR.json
+++ b/src/i18n/strings/pt_BR.json
@@ -9,7 +9,7 @@
"Are you sure you want to reject the invitation?": "Você tem certeza que deseja rejeitar este convite?",
"Banned users": "Usuárias/os banidas/os",
"Bans user with given id": "Banir usuários com o identificador informado",
- "%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s mudou o tópico para \"%(topic)s\".",
+ "%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s mudou a descrição para \"%(topic)s\".",
"Changes your display nickname": "Troca o seu apelido",
"Click here to fix": "Clique aqui para resolver isso",
"Commands": "Comandos",
@@ -29,7 +29,7 @@
"Failed to leave room": "Falha ao tentar deixar a sala",
"Failed to reject invitation": "Falha ao tentar rejeitar convite",
"Failed to unban": "Não foi possível desfazer o banimento",
- "Favourite": "Favorito",
+ "Favourite": "Favoritar",
"Favourites": "Favoritos",
"Filter room members": "Filtrar integrantes da sala",
"Forget room": "Esquecer sala",
@@ -66,7 +66,7 @@
"Privileged Users": "Usuárias/os privilegiadas/os",
"Profile": "Perfil",
"Reject invitation": "Rejeitar convite",
- "Remove": "Remover",
+ "Remove": "Apagar",
"Return to login screen": "Retornar à tela de login",
"Room Colour": "Cores da sala",
"Rooms": "Salas",
@@ -275,7 +275,7 @@
"Failed to invite": "Falha ao enviar o convite",
"Failed to invite the following users to the %(roomName)s room:": "Falha ao convidar as(os) seguintes usuárias(os) para a sala %(roomName)s:",
"Confirm Removal": "Confirmar a remoção",
- "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "Você tem certeza que quer apagar este evento? Note que se você apaga o nome de uma sala ou uma mudança de tópico, esta ação não poderá ser desfeita.",
+ "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "Tem certeza de que deseja apagar este evento? Observe que, se você apagar o nome ou alterar a descrição de uma sala, pode desfazer a alteração.",
"Unknown error": "Erro desconhecido",
"Incorrect password": "Senha incorreta",
"Unable to restore session": "Não foi possível restaurar a sessão",
@@ -311,17 +311,17 @@
"Invited": "Convidada(o)",
"Results from DuckDuckGo": "Resultados de DuckDuckGo",
"Verified key": "Chave verificada",
- "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s removeu a imagem da sala.",
+ "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s removeu a foto da sala.",
"%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s alterou a imagem da sala %(roomName)s",
- "%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s alterou a imagem da sala para ",
+ "%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s alterou a foto da sala para ",
"No Microphones detected": "Não foi detectado nenhum microfone",
"No Webcams detected": "Não foi detectada nenhuma Webcam",
"No media permissions": "Não há permissões de uso de vídeo/áudio no seu navegador",
"You may need to manually permit %(brand)s to access your microphone/webcam": "Você talvez precise autorizar manualmente que o %(brand)s acesse seu microfone e webcam",
- "Default Device": "Dispositivo padrão",
+ "Default Device": "Aparelho padrão",
"Microphone": "Microfone",
"Camera": "Câmera de vídeo",
- "Add a topic": "Adicionar um tópico",
+ "Add a topic": "Adicionar uma descrição",
"Anyone": "Qualquer pessoa",
"Are you sure you want to leave the room '%(roomName)s'?": "Você tem certeza que deseja sair da sala '%(roomName)s'?",
"Custom level": "Nível personalizado",
@@ -400,7 +400,7 @@
"Edit": "Editar",
"Unpin Message": "Desafixar Mensagem",
"Add rooms to this community": "Adicionar salas na comunidade",
- "The version of %(brand)s": "A Versão do %(brand)s",
+ "The version of %(brand)s": "A versão do %(brand)s",
"The platform you're on": "A plataforma que você está usando",
"Your language of choice": "O idioma que você selecionou",
"Which officially provided instance you are using, if any": "Qual instância oficial você está usando, se for o caso",
@@ -746,7 +746,7 @@
"Off": "Desativado",
"Mentions only": "Apenas menções",
"Wednesday": "Quarta",
- "You can now return to your account after signing out, and sign in on other devices.": "Você pode retornar agora para a sua conta depois de fazer logout, e então fazer login em outros dispositivos.",
+ "You can now return to your account after signing out, and sign in on other devices.": "Agora você pode retornar à sua conta depois de sair, e fazer login em outros aparelhos.",
"Enable email notifications": "Ativar notificações por email",
"Event Type": "Tipo do Evento",
"Download this file": "Baixar este arquivo",
@@ -762,7 +762,7 @@
"Checking for an update...": "Verificando se há atualizações...",
"Every page you use in the app": "Toda a página que você usa no aplicativo",
"e.g. ": "ex. ",
- "Your device resolution": "Sua resolução de dispositivo",
+ "Your device resolution": "A resolução do seu aparelho",
"Call in Progress": "Chamada em andamento",
"A call is currently being placed!": "Uma chamada está sendo feita atualmente!",
"A call is already in progress!": "Uma chamada já está em andamento!",
@@ -825,7 +825,7 @@
"Unable to load key backup status": "Não é possível carregar o status da chave de backup",
"Backup version: ": "Versão do Backup: ",
"Algorithm: ": "Algoritmo: ",
- "This event could not be displayed": "O evento não pôde ser exibido",
+ "This event could not be displayed": "Este evento não pôde ser exibido",
"Use a longer keyboard pattern with more turns": "Use um padrão de teclas em diferentes direções e sentido",
"Share Link to User": "Compartilhar Link com Usuário",
"This room has been replaced and is no longer active.": "Esta sala foi substituída e não está mais ativa.",
@@ -865,9 +865,9 @@
"Failed to send logs: ": "Falha ao enviar registros: ",
"Submit debug logs": "Submeter registros de depuração",
"Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "Os registros de depuração contêm dados de uso do aplicativo, incluindo seu nome de usuário, os IDs ou aliases das salas ou grupos que você visitou e os nomes de usuários de outros usuários. Eles não contêm mensagens.",
- "Before submitting logs, you must create a GitHub issue to describe your problem.": "Antes de enviar os registros, você deve criar uma questão no GitHub para descrever seu problema.",
+ "Before submitting logs, you must create a GitHub issue to describe your problem.": "Antes de enviar os registros, você deve criar um bilhete de erro no GitHub para descrever seu problema.",
"Unable to load commit detail: %(msg)s": "Não é possível carregar os detalhes do commit: %(msg)s",
- "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": "Para evitar perder seu histórico de bate-papo, você deve exportar as chaves do seu quarto antes de fazer logout. Você precisará voltar para a versão mais recente do %(brand)s para fazer isso",
+ "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": "Para evitar perder seu histórico de bate-papo, você deve exportar as chaves da sua sala antes de se desconectar. Para fazer isso, você precisará retornar na versão mais atual do %(brand)s",
"Incompatible Database": "Banco de dados incompatível",
"Continue With Encryption Disabled": "Continuar com criptografia desativada",
"This will make your account permanently unusable. You will not be able to log in, and no one will be able to re-register the same user ID. This will cause your account to leave all rooms it is participating in, and it will remove your account details from your identity server. This action is irreversible. ": "Isso tornará sua conta permanentemente inutilizável. Você não poderá efetuar login e ninguém poderá registrar novamente o mesmo ID de usuário. Isso fará com que sua conta deixe todas as salas nas quais está participando e removerá os detalhes da sua conta do seu servidor de identidade. Esta ação é irreversível. b>",
@@ -933,7 +933,7 @@
"You can't send any messages until you review and agree to our terms and conditions .": "Você não pode enviar nenhuma mensagem até revisar e concordar com nossos termos e condições .",
"Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please contact your service administrator to continue using the service.": "Sua mensagem não foi enviada porque este homeserver atingiu seu Limite de usuário ativo mensal. Por favor, entre em contato com o seu administrador de serviços para continuar usando o serviço.",
"Your message wasn't sent because this homeserver has exceeded a resource limit. Please contact your service administrator to continue using the service.": "Sua mensagem não foi enviada porque este homeserver excedeu o limite de recursos. Por favor, entre em contato com o seu administrador de serviços para continuar usando o serviço.",
- "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "Se você enviou um bug por meio do GitHub, os logs de depuração podem nos ajudar a rastrear o problema. Os logs de depuração contêm dados de uso do aplicativo, incluindo seu nome de usuário, os IDs ou aliases das salas ou grupos que você visitou e os nomes de usuários de outros usuários. Eles não contêm mensagens.",
+ "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "Se você enviou um bug por meio do GitHub, os logs de depuração podem nos ajudar a rastrear o problema. Os logs de depuração contêm dados de uso do aplicativo, incluindo seu nome de usuário, os IDs ou apelidos das salas ou grupos que você visitou e os nomes de usuários de outros usuários. Eles não contêm mensagens.",
"Legal": "Legal",
"No Audio Outputs detected": "Nenhuma saída de áudio detectada",
"Audio Output": "Saída de áudio",
@@ -945,7 +945,7 @@
"Sign in with single sign-on": "Entre com o logon único",
"That matches!": "Isto corresponde!",
"That doesn't match.": "Isto não corresponde.",
- "Go back to set it again.": "Volte e configure novamente.",
+ "Go back to set it again.": "Voltar para configurar novamente.",
"Download": "Baixar",
"Print it and store it somewhere safe": "Imprima-o b> e armazene-o em algum lugar seguro",
"Save it on a USB key or backup drive": "Salve isto b> em uma chave USB ou unidade de backup",
@@ -968,8 +968,8 @@
"Invite anyway": "Convide mesmo assim",
"Whether or not you're logged in (we don't record your username)": "Se você está logado ou não (não gravamos seu nome de usuário)",
"Upgrades a room to a new version": "Atualiza uma sala para uma nova versão",
- "Gets or sets the room topic": "Obtém ou define o tópico da sala",
- "This room has no topic.": "Esta sala não tem assunto.",
+ "Gets or sets the room topic": "Consultar ou definir a descrição da sala",
+ "This room has no topic.": "Esta sala não tem descrição.",
"Sets the room name": "Define o nome da sala",
"Group & filter rooms by custom tags (refresh to apply changes)": "Agrupar e filtrar salas por tags personalizadas (atualize para aplicar as alterações)",
"Render simple counters in room header": "Renderizar contadores simples no cabeçalho da sala",
@@ -1127,19 +1127,19 @@
"Room version:": "Versão da sala:",
"Developer options": "Opções de desenvolvedor",
"Room Addresses": "Endereços da sala",
- "Change room avatar": "Alterar avatar da sala",
+ "Change room avatar": "Alterar a foto da sala",
"Change room name": "Alterar nome da sala",
"Change main address for the room": "Alterar o endereço principal da sala",
"Change history visibility": "Alterar a visibilidade do histórico",
"Change permissions": "Alterar permissões",
- "Change topic": "Alterar o tópico",
+ "Change topic": "Alterar a descrição",
"Modify widgets": "Modificar widgets",
"Default role": "Papel padrão",
"Send messages": "Enviar mensagens",
"Invite users": "Convidar usuários",
"Use Single Sign On to continue": "Use \"Single Sign On\" para continuar",
"Confirm adding this email address by using Single Sign On to prove your identity.": "Confirme a inclusão deste endereço de correio eletrônico usando o Single Sign On para comprovar sua identidade.",
- "Single Sign On": "Single Sign On",
+ "Single Sign On": "Autenticação Única",
"Confirm adding email": "Confirmar a inclusão de email",
"Click the button below to confirm adding this email address.": "Clique no botão abaixo para confirmar a adição deste endereço de email.",
"Confirm": "Confirmar",
@@ -1147,17 +1147,17 @@
"Confirm adding phone number": "Confirmar adição de número de telefone",
"Add Phone Number": "Adicionar número de telefone",
"Whether you're using %(brand)s on a device where touch is the primary input mechanism": "Se estiver usando %(brand)s em um aparelho onde touch é o mecanismo primário de entrada de dados",
- "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "Se você está usando a funcionalidade 'breadcrumbs' (imagens acima da lista de salas)",
+ "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "Se você está usando ou não a funcionalidade 'breadcrumbs' (fotos acima da lista de salas)",
"Whether you're using %(brand)s as an installed Progressive Web App": "Se estiver usando %(brand)s como uma Progressive Web App (PWA)",
"Your user agent": "Seu agente de usuária(o)",
- "Call failed due to misconfigured server": "A chamada caiu por conta de má configuração do servidor",
- "Please ask the administrator of your homeserver (%(homeserverDomain)s
) to configure a TURN server in order for calls to work reliably.": "Por favor, peça aos administradores do seu servidor (%(homeserverDomain)s
) para configurar um servidor TURN para que as chamadas funcionem de forma estável.",
- "Alternatively, you can try to use the public server at turn.matrix.org
, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.": "Alternativamente, pode tentar usar o servidor público em turn.matrix.org
, mas não será tão fiável e partilhará o seu IP com esse servidor. Também pode gerir isso nas definições.",
- "Try using turn.matrix.org": "Tentar utilizar turn.matrix.org",
+ "Call failed due to misconfigured server": "A chamada caiu por conta de má configuração no servidor",
+ "Please ask the administrator of your homeserver (%(homeserverDomain)s
) to configure a TURN server in order for calls to work reliably.": "Por favor, peça ao administrador do seu servidor (%(homeserverDomain)s
) para configurar um servidor TURN, de modo que as chamadas funcionem de maneira estável.",
+ "Alternatively, you can try to use the public server at turn.matrix.org
, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.": "Alternativamente, você pode tentar usar o servidor público em turn.matrix.org
. No entanto, ele não é tão confiável e compartilhará o seu IP com esse servidor. Você também pode configurar isso nas Configurações.",
+ "Try using turn.matrix.org": "Tente utilizar turn.matrix.org",
"Replying With Files": "Responder com arquivos",
- "At this time it is not possible to reply with a file. Would you like to upload this file without replying?": "Neste momento não é possível responder com um arquivo. Você quer fazer upload deste arquivo sem responder à mensagem?",
- "The file '%(fileName)s' failed to upload.": "O arquivo '%(fileName)s' não pôde ser enviado.",
- "The server does not support the room version specified.": "Este servidor não suporta a versão de sala especificada.",
+ "At this time it is not possible to reply with a file. Would you like to upload this file without replying?": "Momentaneamente, não é possível responder com um arquivo. Você quer fazer o envio deste arquivo sem responder a mensagem?",
+ "The file '%(fileName)s' failed to upload.": "O envio do arquivo '%(fileName)s' falhou.",
+ "The server does not support the room version specified.": "O servidor não suporta a versão da sala especificada.",
"Cancel entering passphrase?": "Cancelar a introdução da frase de senha?",
"Are you sure you want to cancel entering passphrase?": "Tem certeza que quer cancelar a introdução da frase de senha?",
"Go Back": "Voltar",
@@ -1189,7 +1189,7 @@
"Changes the avatar of the current room": "Altera a imagem da sala atual",
"Changes your avatar in this current room only": "Muda sua imagem de perfil apenas nesta sala",
"Changes your avatar in all rooms": "Muda sua imagem de perfil em todas as salas",
- "Failed to set topic": "Não foi possível definir o tópico",
+ "Failed to set topic": "Não foi possível definir a descrição",
"Use an identity server": "Usar um servidor de identidade",
"Use an identity server to invite by email. Manage in Settings.": "Use um servidor de identidade para convidar pessoas por email. Gerencie nas Configurações.",
"Joins room with given address": "Entra em uma sala com o endereço fornecido",
@@ -1215,7 +1215,7 @@
"Opens chat with the given user": "Abre um chat com determinada pessoa",
"Sends a message to the given user": "Envia uma mensagem com determinada pessoa",
"%(senderName)s made no change.": "%(senderName)s não fez nenhuma alteração.",
- "%(senderDisplayName)s changed the room name from %(oldRoomName)s to %(newRoomName)s.": "%(senderDisplayName)s alterou o nome da sala de %(oldRoomName)s para",
+ "%(senderDisplayName)s changed the room name from %(oldRoomName)s to %(newRoomName)s.": "%(senderDisplayName)s alterou o nome da sala de %(oldRoomName)s para %(newRoomName)s.",
"%(senderName)s added the alternative addresses %(addresses)s for this room.|other": "%(senderName)s adicionou os endereços alternativos %(addresses)s para esta sala.",
"%(senderName)s added the alternative addresses %(addresses)s for this room.|one": "%(senderName)s adicionou o endereço alternativo %(addresses)s para esta sala.",
"%(senderName)s removed the alternative addresses %(addresses)s for this room.|other": "%(senderName)s removeu os endereços alternativos %(addresses)s para esta sala.",
@@ -1228,10 +1228,10 @@
"%(senderName)s placed a video call.": "%(senderName)s iniciou uma chamada de vídeo.",
"%(senderName)s placed a video call. (not supported by this browser)": "%(senderName)s iniciou uma chamada de vídeo. (não suportada por este navegador)",
"%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.": "%(senderName)s cancelou o convite a %(targetDisplayName)s para entrar na sala.",
- "%(senderName)s removed the rule banning users matching %(glob)s": "%(senderName)s removeu a regra banindo usuárias(os) correspondendo a",
- "%(senderName)s removed the rule banning rooms matching %(glob)s": "%(senderName)s removeu uma regra banindo salas correspondendo a",
- "%(senderName)s removed the rule banning servers matching %(glob)s": "%(senderName)s removeu a regra banindo servidores correspondendo a",
- "%(senderName)s removed a ban rule matching %(glob)s": "%(senderName)s removeu uma regra de banimento correspondendo a",
+ "%(senderName)s removed the rule banning users matching %(glob)s": "%(senderName)s removeu a regra que bane usuárias(os) que correspondem a %(glob)s",
+ "%(senderName)s removed the rule banning rooms matching %(glob)s": "%(senderName)s removeu a regra que bane salas que correspondem a %(glob)s",
+ "%(senderName)s removed the rule banning servers matching %(glob)s": "%(senderName)s removeu a regra que bane servidores que correspondem a %(glob)s",
+ "%(senderName)s removed a ban rule matching %(glob)s": "%(senderName)s removeu uma regra de banimento correspondendo a %(glob)s",
"%(senderName)s updated an invalid ban rule": "%(senderName)s atualizou uma regra de banimento inválida",
"%(senderName)s updated the rule banning users matching %(glob)s for %(reason)s": "%(senderName)s atualizou a regra de banimento de usuárias(os) correspondendo a %(glob)s por %(reason)s",
"%(senderName)s updated the rule banning rooms matching %(glob)s for %(reason)s": "%(senderName)s atualizou a regra banindo salas correspondendo a %(glob)s por %(reason)s",
@@ -1283,7 +1283,7 @@
"%(num)s days from now": "dentro de %(num)s dias",
"%(name)s (%(userId)s)": "%(name)s (%(userId)s)",
"The user's homeserver does not support the version of the room.": "O servidor desta(e) usuária(o) não suporta a versão desta sala.",
- "Help us improve %(brand)s": "Ajude-nos a melhorar o",
+ "Help us improve %(brand)s": "Ajude-nos a melhorar %(brand)s",
"Send anonymous usage data which helps us improve %(brand)s. This will use a cookie .": "Envie dados anônimos de uso que nos ajudam a melhorar o %(brand)s. Isso necessitará do uso de um cookie .",
"I want to help": "Quero ajudar",
"Review where you’re logged in": "Revisar onde você está logada(o)",
@@ -1291,7 +1291,7 @@
"Review": "Revisar",
"Later": "Mais tarde",
"Your homeserver has exceeded its user limit.": "Seu servidor ultrapassou seu limite de usuárias(os).",
- "Your homeserver has exceeded one of its resource limits.": "Seu servidor excedeu um de seus limites de recursos",
+ "Your homeserver has exceeded one of its resource limits.": "Seu servidor excedeu um de seus limites de recursos.",
"Contact your server admin .": "Entre em contato com sua(seu) administrador(a) do servidor .",
"Ok": "Ok",
"Set password": "Definir senha",
@@ -1301,12 +1301,12 @@
"Verify this session": "Verificar esta sessão",
"Upgrade": "Atualizar",
"Verify": "Verificar",
- "Verify yourself & others to keep your chats safe": "Faça a sua auto-verificação e verifique seus contatos para manter suas conversas seguras!",
+ "Verify yourself & others to keep your chats safe": "Verifique a sua conta e as dos seus contatos, para manter suas conversas seguras",
"Other users may not trust it": "Outras(os) usuárias(os) podem não confiar nela",
"New login. Was this you?": "Novo login. Foi você?",
- "Verify the new login accessing your account: %(name)s": "Verifique o novo login acessando sua conta:",
+ "Verify the new login accessing your account: %(name)s": "Verifique o novo login acessando sua conta: %(name)s",
"Restart": "Reiniciar",
- "Upgrade your %(brand)s": "Atualize seu",
+ "Upgrade your %(brand)s": "Atualize o seu %(brand)s",
"A new version of %(brand)s is available!": "Uma nova versão do %(brand)s está disponível!",
"Guest": "Convidada(o)",
"You joined the call": "Você entrou na chamada",
@@ -1319,10 +1319,10 @@
"%(senderName)s started a call": "%(senderName)s iniciou uma chamada",
"Waiting for answer": "Esperando por uma resposta",
"%(senderName)s is calling": "%(senderName)s está chamando",
- "* %(senderName)s %(emote)s": "* %(senderName)s",
- "%(senderName)s: %(message)s": "%(senderName)s:",
- "%(senderName)s: %(reaction)s": "%(senderName)s:",
- "%(senderName)s: %(stickerName)s": "%(senderName)s:",
+ "* %(senderName)s %(emote)s": "* %(senderName)s %(emote)s",
+ "%(senderName)s: %(message)s": "%(senderName)s: %(message)s",
+ "%(senderName)s: %(reaction)s": "%(senderName)s: %(reaction)s",
+ "%(senderName)s: %(stickerName)s": "%(senderName)s: %(stickerName)s",
"New spinner design": "Novo design do spinner",
"Multiple integration managers": "Múltiplos gestores de integrações",
"Try out new ways to ignore people (experimental)": "Tente novas maneiras de ignorar pessoas (experimental)",
@@ -1343,7 +1343,7 @@
"Show shortcuts to recently viewed rooms above the room list": "Mostrar atalhos para salas recentemente visualizadas acima da lista de salas",
"Show hidden events in timeline": "Mostrar eventos ocultos na timeline",
"Low bandwidth mode": "Modo de baixo uso de internet",
- "Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)": "Permitir o servidor de respaldo de assistência de chamadas turn.matrix.org quando seu servidor não o ofereça (seu endereço IP será compartilhado numa chamada, neste caso)",
+ "Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)": "Permitir a assistência do servidor de chamadas reserva turn.matrix.org quando seu servidor não oferecer este serviço (seu endereço IP será transmitido quando você ligar)",
"Send read receipts for messages (requires compatible homeserver to disable)": "Enviar confirmação de leitura para mensagens (necessita um servidor compatível para desativar)",
"Show previews/thumbnails for images": "Mostrar miniaturas e resumos para imagens",
"Enable message search in encrypted rooms": "Ativar busca de mensagens em salas criptografadas",
@@ -1378,14 +1378,14 @@
"Decline (%(counter)s)": "Recusar (%(counter)s)",
"Accept to continue:": "Aceitar para continuar:",
"Upload": "Enviar",
- "This bridge was provisioned by .": "Esta ponte foi disponibilizada por",
+ "This bridge was provisioned by .": "Esta ponte foi disponibilizada por .",
"This bridge is managed by .": "Esta ponte é gerida por .",
- "Workspace: %(networkName)s": "Espaço de trabalho:",
- "Channel: %(channelName)s": "Canal:",
+ "Workspace: %(networkName)s": "Espaço de trabalho: %(networkName)s",
+ "Channel: %(channelName)s": "Canal: %(channelName)s",
"Show less": "Mostrar menos",
"Show more": "Mostrar mais",
"Changing password will currently reset any end-to-end encryption keys on all sessions, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Ao mudar a senha, você apagará quaisquer chaves de criptografia ponta-a-ponta existentes em todas as sessões, fazendo com que o histórico de conversas criptografadas fique ilegível, a não ser que você exporte as salas das chaves criptografadas antes de mudar a senha e então as importe novamente depois. No futuro, isso será melhorado.",
- "Your homeserver does not support cross-signing.": "Seu servidor não suporta assinatura cruzada",
+ "Your homeserver does not support cross-signing.": "Seu servidor não suporta assinatura cruzada.",
"Cross-signing and secret storage are enabled.": "Assinaturas cruzadas e armazenamento secreto estão habilitadas.",
"Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.": "Sua conta tem uma identidade de assinatura cruzada em um armazenamento secreto, mas ainda não é considerada confiável por esta sessão.",
"Cross-signing and secret storage are not yet set up.": "A assinatura cruzada e o armazenamento seguro ainda não foram configurados.",
@@ -1439,13 +1439,13 @@
"not stored": "não armazenado",
"Backup has a valid signature from this user": "A cópia de segurança (backup) tem uma assinatura válida deste(a) usuário(a)",
"Backup has a invalid signature from this user": "A cópia de segurança (backup) tem uma assinatura inválida deste(a) usuário(a)",
- "Backup has a signature from unknown user with ID %(deviceId)s": "Fazer cópia de segurança (backup) de usuária(o) desconhecida(o) com ID",
- "Backup has a signature from unknown session with ID %(deviceId)s": "Fazer cópia de segurança (backup) de uma sessão desconhecida com ID",
+ "Backup has a signature from unknown user with ID %(deviceId)s": "A cópia de segurança tem uma assinatura de um(a) usuário desconhecido com ID %(deviceId)s",
+ "Backup has a signature from unknown session with ID %(deviceId)s": "A cópia de segurança tem uma assinatura de uma sessão desconhecida com ID %(deviceId)s",
"Backup has a valid signature from this session": "A cópia de segurança (backup) tem uma assinatura válida desta sessão",
"Backup has an invalid signature from this session": "A cópia de segurança (backup) tem uma assinatura inválida desta sessão",
"Backup has a valid signature from verified session ": "A cópia de segurança (backup) tem uma assinatura válida da sessão verificada ",
- "Backup has a valid signature from unverified session ": "A cópia de segurança (backup) tem uma assinatura válida da sessão não verificada ",
- "Backup has an invalid signature from verified session ": "A cópia de segurança (backup) tem uma assinatura inválida de uma sessão verificada ",
+ "Backup has a valid signature from unverified session ": "A cópia de segurança tem uma assinatura válida de uma sessão não verificada ",
+ "Backup has an invalid signature from verified session ": "A cópia de segurança tem uma assinatura inválida de uma sessão verificada ",
"Backup has an invalid signature from unverified session ": "A cópia de segurança (backup) tem uma assinatura inválida de uma sessão não verificada ",
"Backup is not signed by any of your sessions": "A cópia de segurança (backup) não foi assinada por nenhuma de suas sessões",
"This backup is trusted because it has been restored on this session": "Esta cópia de segurança (backup) é confiável, pois foi restaurada nesta sessão",
@@ -1509,9 +1509,9 @@
"%(name)s wants to verify": "%(name)s deseja verificar",
"Smileys & People": "Emoticons e Pessoas",
"Widgets do not use message encryption.": "Widgets não usam criptografia de mensagens.",
- "Please create a new issue on GitHub so that we can investigate this bug.": "Por favor, crie uma nova issue no GitHub para que possamos investigar esta falha.",
+ "Please create a new issue on GitHub so that we can investigate this bug.": "Por favor, crie um novo bilhete de erro no GitHub para que possamos investigar esta falha.",
"Enter the name of a new server you want to explore.": "Entre com o nome do novo servidor que você quer explorar.",
- "Please tell us what went wrong or, better, create a GitHub issue that describes the problem.": "Por favor, diga-nos o que aconteceu de errado ou, ainda melhor, crie uma issue no GitHub que descreva o problema.",
+ "Please tell us what went wrong or, better, create a GitHub issue that describes the problem.": "Por favor, diga-nos o que aconteceu de errado ou, ainda melhor, crie um bilhete de erro no GitHub que descreva o problema.",
"Clearing all data from this session is permanent. Encrypted messages will be lost unless their keys have been backed up.": "Apagar todos os dados desta sessão é uma ação permanente. Mensagens criptografadas serão perdidas, a não ser que suas chaves tenham sido copiadas para o backup.",
"Set a room address to easily share your room with other people.": "Defina um endereço de sala para facilmente compartilhar sua sala com outras pessoas.",
"You can’t disable this later. Bridges & most bots won’t work yet.": "Você não poderá desabilitar depois. Pontes e a maioria dos bots não funcionarão no momento.",
@@ -1531,7 +1531,7 @@
"You'll lose access to your encrypted messages": "Você perderá acesso às suas mensagens criptografadas",
"Session key": "Chave da sessão",
"Verify session": "Verificar sessão",
- "We recommend you change your password and recovery key in Settings immediately": "Nós recomendamos que você altere imediatamente sua senha e chave de recuperação nas configurações.",
+ "We recommend you change your password and recovery key in Settings immediately": "Nós recomendamos que você altere imediatamente sua senha e chave de recuperação nas Configurações",
"Use this session to verify your new one, granting it access to encrypted messages:": "Use esta sessão para verificar a sua nova sessão, dando a ela acesso às mensagens criptografadas:",
"You’re already signed in and good to go here, but you can also grab the latest versions of the app on all platforms at element.io/get-started .": "Você já está logada(o) e pode começar a usar à vontade, mas você também pode buscar pelas últimas versões do app em todas as plataformas em element.io/get-started .",
"Go to Element": "Ir a Element",
@@ -1560,7 +1560,7 @@
"Missing captcha public key in homeserver configuration. Please report this to your homeserver administrator.": "Está faltando a chave pública do captcha no Servidor (homeserver). Por favor, reporte isso aos(às) administradores(as) do servidor.",
"Enter the location of your Element Matrix Services homeserver. It may use your own domain name or be a subdomain of element.io .": "Entre com a localização do seu Servidor Matrix. Pode ser seu próprio domínio ou ser um subdomínio de element.io .",
"Create your Matrix account on %(serverName)s": "Criar sua conta Matrix em %(serverName)s",
- "Create your Matrix account on ": "Criar sua conta Matrix em",
+ "Create your Matrix account on ": "Crie sua conta Matrix em ",
"Welcome to %(appName)s": "Desejamos boas vindas ao %(appName)s",
"Liberate your communication": "Liberte sua comunicação",
"Send a Direct Message": "Envie uma mensagem direta",
@@ -1700,5 +1700,454 @@
"Verification Requests": "Solicitações de verificação",
"Integrations are disabled": "As integrações estão desativadas",
"Integrations not allowed": "As integrações não estão permitidas",
- "End": "Fim"
+ "End": "Fim",
+ "List options": "Opções da Lista",
+ "Jump to first unread room.": "Ir para a primeira sala não lida.",
+ "Jump to first invite.": "Ir para o primeiro convite.",
+ "Add room": "Adicionar sala",
+ "Show %(count)s more|other": "Mostrar %(count)s a mais",
+ "Show %(count)s more|one": "Mostrar %(count)s a mais",
+ "Use default": "Usar o padrão",
+ "Room options": "Opções da Sala",
+ "%(count)s unread messages including mentions.|other": "%(count)s mensagens não lidas, incluindo menções.",
+ "%(count)s unread messages including mentions.|one": "1 menção não lida.",
+ "%(count)s unread messages.|other": "%(count)s mensagens não lidas.",
+ "%(count)s unread messages.|one": "1 mensagem não lida.",
+ "Unread messages.": "Mensagens não lidas.",
+ "This room is public": "Esta sala é pública",
+ "Away": "Ausente",
+ "This room has already been upgraded.": "Esta sala já foi atualizada.",
+ "This room is running room version , which this homeserver has marked as unstable .": "Esta sala está executando a versão , que este servidor marcou como instável .",
+ "Local address": "Endereço local",
+ "Published Addresses": "Endereços publicados",
+ "Published addresses can be used by anyone on any server to join your room. To publish an address, it needs to be set as a local address first.": "Os endereços publicados podem ser usados por qualquer pessoa em qualquer servidor para entrar na sala. Para publicar um endereço, primeiramente ele precisa ser definido como um endereço local.",
+ "Other published addresses:": "Outros endereços publicados:",
+ "New published address (e.g. #alias:server)": "Novo endereço publicado (por exemplo, #apelido:server)",
+ "Local Addresses": "Endereços locais",
+ "%(name)s cancelled verifying": "%(name)s cancelou a verificação",
+ "Your display name": "Seu nome de exibição",
+ "Your avatar URL": "A URL da sua foto de perfil",
+ "Your user ID": "Sua ID de usuário",
+ "%(brand)s URL": "URL de %(brand)s",
+ "Using this widget may share data with %(widgetDomain)s & your Integration Manager.": "Se você usar esse widget, os dados poderão ser compartilhados com %(widgetDomain)s & seu Gerenciador de Integrações.",
+ "Using this widget may share data with %(widgetDomain)s.": "Se você usar esse widget, os dados poderão ser compartilhados com %(widgetDomain)s.",
+ "%(severalUsers)smade no changes %(count)s times|other": "%(severalUsers)s não fizeram alterações %(count)s vezes",
+ "%(severalUsers)smade no changes %(count)s times|one": "%(severalUsers)s não fizeram alterações",
+ "%(oneUser)smade no changes %(count)s times|other": "%(oneUser)s não fez alteraçõe s%(count)s vezes",
+ "%(oneUser)smade no changes %(count)s times|one": "%(oneUser)s não fez alterações",
+ "Power level": "Nível de permissão",
+ "Please provide a room address": "Digite um endereço para a sala",
+ "Looks good": "Muito bem",
+ "Are you sure you want to remove %(serverName)s ": "Tem certeza de que deseja remover %(serverName)s ",
+ "%(networkName)s rooms": "Salas em %(networkName)s",
+ "Matrix rooms": "Salas em Matrix",
+ "Close dialog": "Fechar caixa de diálogo",
+ "GitHub issue": "Bilhete de erro no GitHub",
+ "If there is additional context that would help in analysing the issue, such as what you were doing at the time, room IDs, user IDs, etc., please include those things here.": "Se houver um contexto adicional que ajude a analisar o problema, tal como o que você estava fazendo no momento, IDs de salas, IDs de usuários etc, inclua essas coisas aqui.",
+ "Topic (optional)": "Descrição (opcional)",
+ "There was a problem communicating with the server. Please try again.": "Ocorreu um problema na comunicação com o servidor. Por favor, tente novamente.",
+ "Server did not require any authentication": "O servidor não exigiu autenticação",
+ "Verifying this user will mark their session as trusted, and also mark your session as trusted to them.": "Se você verificar esse usuário, a sessão será marcada como confiável para você e para ele.",
+ "Verifying this device will mark it as trusted, and users who have verified with you will trust this device.": "Verificar este aparelho o marcará como confiável, e os usuários que confirmaram com você também confiarão neste aparelho.",
+ "Keep going...": "Continue...",
+ "The username field must not be blank.": "O campo do nome de usuário não pode ficar em branco.",
+ "Username": "Nome de usuário",
+ "Use an email address to recover your account": "Use um endereço de e-mail para recuperar sua conta",
+ "Enter email address (required on this homeserver)": "Digite o endereço de e-mail (necessário neste servidor)",
+ "Doesn't look like a valid email address": "Este não parece ser um endereço de email válido",
+ "Passwords don't match": "As senhas não correspondem",
+ "Other users can invite you to rooms using your contact details": "Outros usuários podem convidá-lo para salas usando seus detalhes de contato",
+ "Enter phone number (required on this homeserver)": "Digite o número de celular (necessário neste servidor)",
+ "Doesn't look like a valid phone number": "Este não parece ser um número de telefone válido",
+ "Use lowercase letters, numbers, dashes and underscores only": "Use apenas letras minúsculas, números, traços e sublinhados",
+ "Enter username": "Digite o nome de usuário",
+ "Email (optional)": "E-mail (opcional)",
+ "Phone (optional)": "Número de celular (opcional)",
+ "Set an email for account recovery. Use email or phone to optionally be discoverable by existing contacts.": "Defina um e-mail para recuperação da conta. Opcionalmente, use e-mail ou número de celular para ser encontrado por seus contatos.",
+ "You cannot sign in to your account. Please contact your homeserver admin for more information.": "Você não pôde se conectar na sua conta. Entre em contato com o administrador do servidor para obter mais informações.",
+ "Confirm adding this phone number by using Single Sign On to prove your identity.": "Confirme a adição deste número de telefone usando o Login Único para provar sua identidade.",
+ "Use an identity server to invite by email. Click continue to use the default identity server (%(defaultIdentityServerName)s) or manage in Settings.": "Use um servidor de identidade para convidar por e-mail. Clique em continuar para usar o servidor de identidade padrão (%(defaultIdentityServerName)s) ou gerencie nas Configurações.",
+ "You might have configured them in a client other than %(brand)s. You cannot tune them in %(brand)s but they still apply.": "Você pode ter configurado estas opções em um cliente que não seja %(brand)s. Você não pode ajustar essas opções no %(brand)s, mas elas ainda se aplicam.",
+ "Enable audible notifications for this session": "Ativar notificações sonoras para esta sessão",
+ "Display Name": "Nome em exibição",
+ "Identity Server URL must be HTTPS": "O URL do Servidor de Identidade deve ser HTTPS",
+ "Not a valid Identity Server (status code %(code)s)": "Servidor de Identidade inválido (código de status %(code)s)",
+ "Could not connect to Identity Server": "Não foi possível conectar-se ao Servidor de Identidade",
+ "Checking server": "Verificando servidor",
+ "Change identity server": "Mudar o servidor de identidade",
+ "Disconnect from the identity server and connect to instead?": "Desconectar-se do servidor de identidade e conectar-se em em vez disso?",
+ "Terms of service not accepted or the identity server is invalid.": "Termos de serviço não aceitos ou o servidor de identidade é inválido.",
+ "The identity server you have chosen does not have any terms of service.": "O servidor de identidade que você escolheu não possui nenhum termo de serviço.",
+ "Disconnect identity server": "Desconectar servidor de identidade",
+ "Disconnect from the identity server ?": "Desconectar-se do servidor de identidade ?",
+ "Disconnect": "Desconectar",
+ "You should remove your personal data from identity server before disconnecting. Unfortunately, identity server is currently offline or cannot be reached.": "Você deve remover seus dados pessoais do servidor de identidade antes de desconectar. Infelizmente, o servidor de identidade está atualmente offline ou não pode ser acessado.",
+ "You should:": "Você deveria:",
+ "check your browser plugins for anything that might block the identity server (such as Privacy Badger)": "verifique se há extensões no seu navegador que possam bloquear o servidor de identidade (por exemplo, Privacy Badger)",
+ "contact the administrators of identity server ": "entre em contato com os administradores do servidor de identidade ",
+ "Disconnect anyway": "Desconectar de qualquer maneira",
+ "You are still sharing your personal data on the identity server .": "Você ainda está compartilhando seus dados pessoais no servidor de identidade .",
+ "We recommend that you remove your email addresses and phone numbers from the identity server before disconnecting.": "Recomendamos que você remova seus endereços de e-mail e números de telefone do servidor de identidade antes de desconectar.",
+ "Go back": "Voltar",
+ "Identity Server (%(server)s)": "Servidor de identidade (%(server)s)",
+ "You are currently using to discover and be discoverable by existing contacts you know. You can change your identity server below.": "No momento, você está usando para descobrir e ser descoberto pelos contatos existentes que você conhece. Você pode alterar seu servidor de identidade abaixo.",
+ "If you don't want to use to discover and be discoverable by existing contacts you know, enter another identity server below.": "Se você não quiser usar para descobrir e ser detectável pelos contatos existentes, digite outro servidor de identidade abaixo.",
+ "Identity Server": "Servidor de identidade",
+ "You are not currently using an identity server. To discover and be discoverable by existing contacts you know, add one below.": "No momento, você não está usando um servidor de identidade. Para descobrir e ser descoberto pelos contatos existentes, adicione um abaixo.",
+ "Disconnecting from your identity server will mean you won't be discoverable by other users and you won't be able to invite others by email or phone.": "Desconectar-se do servidor de identidade significa que você não poderá ser descoberto por outros usuários e não poderá convidar outras pessoas por e-mail ou número de celular.",
+ "Using an identity server is optional. If you choose not to use an identity server, you won't be discoverable by other users and you won't be able to invite others by email or phone.": "Usar um servidor de identidade é opcional. Se você optar por não usar um servidor de identidade, não poderá ser descoberto por outros usuários e não poderá convidar outras pessoas por e-mail ou por número de celular.",
+ "Do not use an identity server": "Não usar um servidor de identidade",
+ "Enter a new identity server": "Digitar um novo servidor de identidade",
+ "Change": "Alterar",
+ "Manage integrations": "Gerenciar integrações",
+ "New version available. Update now. ": "Nova versão disponível. Atualize agora. ",
+ "Hey you. You're the best!": "Ei, você aí. Você é incrível!",
+ "Size must be a number": "O tamanho deve ser um número",
+ "Custom font size can only be between %(min)s pt and %(max)s pt": "O tamanho da fonte personalizada só pode estar entre %(min)s pt e %(max)s pt",
+ "Use between %(min)s pt and %(max)s pt": "Use entre %(min)s pt e %(max)s pt",
+ "Invalid theme schema.": "Esquema inválido de tema.",
+ "Error downloading theme information.": "Erro ao baixar as informações do tema.",
+ "Theme added!": "Tema adicionado!",
+ "Custom theme URL": "URL do tema personalizado",
+ "Add theme": "Adicionar tema",
+ "Message layout": "Aparência da mensagem",
+ "Compact": "Compacto",
+ "Modern": "Moderno",
+ "Set the name of a font installed on your system & %(brand)s will attempt to use it.": "Defina o nome de uma fonte instalada no seu sistema e %(brand)s tentará usá-la.",
+ "Customise your appearance": "Personalize sua aparência",
+ "Appearance Settings only affect this %(brand)s session.": "As Configurações de aparência afetam apenas esta sessão do %(brand)s.",
+ "Your password was successfully changed. You will not receive push notifications on other sessions until you log back in to them": "Sua senha foi alterada com sucesso. Você não receberá notificações por push em outras sessões até fazer login novamente nelas",
+ "Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.": "Concordar com os Termos de Serviço do servidor de identidade (%(serverName)s), para permitir aos seus contatos encontrarem-na(no) por endereço de e-mail ou por número de celular.",
+ "Discovery": "Contatos",
+ "Deactivate account": "Desativar conta",
+ "Clear cache and reload": "Limpar cache e recarregar",
+ "To report a Matrix-related security issue, please read the Matrix.org Security Disclosure Policy .": "Para relatar um problema de segurança relacionado à tecnologia Matrix, leia a Política de Divulgação de Segurança da Matrix.org.",
+ "Always show the window menu bar": "Sempre mostrar a barra de menu na janela",
+ "Show tray icon and minimize window to it on close": "Mostrar ícone na barra de tarefas, que permanece visível ao fechar a janela",
+ "Read Marker lifetime (ms)": "Duração do marcador de leitura (ms)",
+ "Read Marker off-screen lifetime (ms)": "Vida útil do marcador de leitura fora da tela (ms)",
+ "Change settings": "Alterar configurações",
+ "Send %(eventType)s events": "Enviar eventos de %(eventType)s",
+ "Roles & Permissions": "Papeis & Permissões",
+ "Select the roles required to change various parts of the room": "Selecione as permissões necessárias para alterar várias partes da sala",
+ "Emoji picker": "Seletor de emoji",
+ "Room %(name)s": "Sala %(name)s",
+ "No recently visited rooms": "Não há salas visitadas recentemente",
+ "Custom Tag": "Etiqueta personalizada",
+ "Joining room …": "Entrando na sala…",
+ "Loading …": "Carregando…",
+ "Rejecting invite …": "Rejeitando convite…",
+ "Join the conversation with an account": "Participar da conversa com uma conta",
+ "Sign Up": "Inscrever-se",
+ "Loading room preview": "Carregando visualização da sala",
+ "You were kicked from %(roomName)s by %(memberName)s": "Você foi removida(o) de %(roomName)s por %(memberName)s",
+ "Reason: %(reason)s": "Razão: %(reason)s",
+ "Forget this room": "Esquecer esta sala",
+ "Re-join": "Entrar novamente",
+ "You were banned from %(roomName)s by %(memberName)s": "Você foi banida(o) de %(roomName)s por %(memberName)s",
+ "Something went wrong with your invite to %(roomName)s": "Ocorreu um erro no seu convite para %(roomName)s",
+ "An error (%(errcode)s) was returned while trying to validate your invite. You could try to pass this information on to a room admin.": "Ocorreu um erro (%(errcode)s) ao validar seu convite. Você pode passar essas informações para um administrador da sala.",
+ "You can only join it with a working invite.": "Você só pode participar com um convite válido.",
+ "Try to join anyway": "Tentar entrar mesmo assim",
+ "You can still join it because this is a public room.": "Você ainda pode entrar, porque esta é uma sala pública.",
+ "Join the discussion": "Participar da discussão",
+ "This invite to %(roomName)s was sent to %(email)s which is not associated with your account": "Este convite para %(roomName)s foi enviado para %(email)s, que não está associado à sua conta",
+ "Link this email with your account in Settings to receive invites directly in %(brand)s.": "Vincule esse e-mail à sua conta em Configurações, para receber convites diretamente em %(brand)s.",
+ "This invite to %(roomName)s was sent to %(email)s": "Este convite para %(roomName)s foi enviado para %(email)s",
+ "Use an identity server in Settings to receive invites directly in %(brand)s.": "Use um servidor de identidade em Configurações para receber convites diretamente em %(brand)s.",
+ "Share this email in Settings to receive invites directly in %(brand)s.": "Compartilhe este e-mail em Configurações para receber convites diretamente em %(brand)s.",
+ "Do you want to chat with %(user)s?": "Deseja conversar com %(user)s?",
+ " wants to chat": " quer conversar",
+ "Do you want to join %(roomName)s?": "Deseja se juntar a %(roomName)s?",
+ " invited you": " convidou você",
+ "Reject & Ignore user": "Rejeitar e ignorar usuário",
+ "You're previewing %(roomName)s. Want to join it?": "Você está visualizando %(roomName)s. Deseja participar?",
+ "%(roomName)s can't be previewed. Do you want to join it?": "%(roomName)s não pode ser visualizado. Deseja participar?",
+ "This room doesn't exist. Are you sure you're at the right place?": "Esta sala não existe. Tem certeza de que você está no lugar certo?",
+ "Securely back up your keys to avoid losing them. Learn more. ": "Faça backup de suas chaves com segurança para evitar perdê-las. Saiba mais. ",
+ "Not now": "Agora não",
+ "Don't ask me again": "Não me pergunte novamente",
+ "Appearance": "Aparência",
+ "Show rooms with unread messages first": "Mostrar salas com mensagens não lidas primeiro",
+ "Show previews of messages": "Mostrar pré-visualizações de mensagens",
+ "Sort by": "Ordenar por",
+ "Activity": "Atividade",
+ "A-Z": "A-Z",
+ "Unknown Command": "Comando desconhecido",
+ "Unrecognised command: %(commandText)s": "Comando não reconhecido: %(commandText)s",
+ "You can use /help
to list available commands. Did you mean to send this as a message?": "Você pode usar /help
para listar os comandos disponíveis. Você quis enviar isso como uma mensagem?",
+ "Send as message": "Enviar como mensagem",
+ "Room Topic": "Descrição da sala",
+ "React": "Reagir",
+ "If you run into any bugs or have feedback you'd like to share, please let us know on GitHub.": "Se você encontrar algum erro ou tiver um comentário que gostaria de compartilhar, informe-nos no GitHub.",
+ "Resend %(unsentCount)s reaction(s)": "Reenviar %(unsentCount)s reações",
+ "Notification settings": "Configurar notificações",
+ "Want more than a community? Get your own server ": "Quer mais do que uma comunidade? Obtenha seu próprio servidor ",
+ "Switch to light mode": "Alternar para o modo claro",
+ "Switch to dark mode": "Alternar para o modo escuro",
+ "Security & privacy": "Segurança & privacidade",
+ "All settings": "Todas as configurações",
+ "You're signed out": "Você foi desconectada(o)",
+ "Clear personal data": "Limpar dados pessoais",
+ "Command Autocomplete": "Preenchimento automático de comandos",
+ "Community Autocomplete": "Preenchimento automático da comunidade",
+ "DuckDuckGo Results": "Resultados no DuckDuckGo",
+ "If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Se você não excluiu o método de recuperação, um invasor pode estar tentando acessar sua conta. Altere a senha da sua conta e defina imediatamente um novo método de recuperação nas Configurações.",
+ "Room List": "Lista de salas",
+ "Autocomplete": "Autocompletar",
+ "Alt": "Alt",
+ "Alt Gr": "Alt Gr",
+ "Shift": "Shift",
+ "Super": "Super",
+ "Ctrl": "Ctrl",
+ "Toggle Bold": "Negrito",
+ "Toggle Italics": "Itálico",
+ "Toggle Quote": "Citar",
+ "New line": "Nova linha",
+ "Navigate recent messages to edit": "Navegue pelas mensagens recentes para editar",
+ "Cancel replying to a message": "Cancelar resposta à mensagem",
+ "Toggle microphone mute": "Ativar/desativar som do microfone",
+ "Toggle video on/off": "Ativar/desativar o vídeo",
+ "Scroll up/down in the timeline": "Rolar para cima/baixo na linha do tempo",
+ "Dismiss read marker and jump to bottom": "Ignorar o marcador de leitura e ir para o final",
+ "Jump to oldest unread message": "Ir para a mensagem não lida mais antiga",
+ "Upload a file": "Enviar um arquivo",
+ "Jump to room search": "Ir para a pesquisa de salas",
+ "Navigate up/down in the room list": "Navegue para cima/baixo na lista de salas",
+ "Select room from the room list": "Selecionar sala da lista de salas",
+ "Collapse room list section": "Esconder seção da lista de salas",
+ "Expand room list section": "Mostrar seção da lista de salas",
+ "The person who invited you already left the room.": "A pessoa que convidou você já saiu da sala.",
+ "The person who invited you already left the room, or their server is offline.": "A pessoa que convidou você já saiu da sala, ou o servidor dela está offline.",
+ "Use an Integration Manager (%(serverName)s) to manage bots, widgets, and sticker packs.": "Use o Gerenciador de Integrações em (%(serverName)s) para gerenciar bots, widgets e pacotes de adesivos.",
+ "Use an Integration Manager to manage bots, widgets, and sticker packs.": "Use o Gerenciador de Integrações para gerenciar bots, widgets e pacotes de adesivos.",
+ "Integration Managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "O Gerenciador de Integrações recebe dados de configuração e pode modificar widgets, enviar convites para salas e definir níveis de permissão em seu nome.",
+ "Keyboard Shortcuts": "Atalhos do teclado",
+ "Customise your experience with experimental labs features. Learn more .": "Personalize sua experiência com os recursos experimentais. Saiba mais .",
+ "Ignored/Blocked": "Ignorado/Bloqueado",
+ "Error adding ignored user/server": "Erro ao adicionar usuário/servidor ignorado",
+ "Something went wrong. Please try again or view your console for hints.": "Algo deu errado. Por favor, tente novamente ou veja seu console para obter dicas.",
+ "Error subscribing to list": "Erro ao inscrever-se na lista",
+ "Error removing ignored user/server": "Erro ao remover usuário/servidor ignorado",
+ "Error unsubscribing from list": "Erro ao cancelar a inscrição da lista",
+ "Please try again or view your console for hints.": "Por favor, tente novamente ou veja seu console para obter dicas.",
+ "None": "Nenhum",
+ "Server rules": "Regras do servidor",
+ "User rules": "Regras do usuário",
+ "You have not ignored anyone.": "Você não ignorou ninguém.",
+ "You are currently ignoring:": "Você está atualmente ignorando:",
+ "You are not subscribed to any lists": "Você não está inscrito em nenhuma lista",
+ "Unsubscribe": "Desinscrever-se",
+ "View rules": "Ver regras",
+ "You are currently subscribed to:": "No momento, você está inscrito em:",
+ "⚠ These settings are meant for advanced users.": "⚠ Essas configurações são destinadas a usuários avançados.",
+ "Add users and servers you want to ignore here. Use asterisks to have %(brand)s match any characters. For example, @bot:*
would ignore all users that have the name 'bot' on any server.": "Adicione aqui os usuários e servidores que você deseja ignorar. Use asteriscos para fazer com que o %(brand)s corresponda a qualquer caractere. Por exemplo, @bot:*
ignorará todos os usuários em qualquer servidor que tenham 'bot' no nome.",
+ "Server or user ID to ignore": "Servidor ou ID de usuário para ignorar",
+ "Subscribe": "Inscrever-se",
+ "Session ID:": "ID da sessão:",
+ "Message search": "Pesquisa de mensagens",
+ "Where you’re logged in": "Onde você está conectado",
+ "Set a new custom sound": "Definir um novo som personalizado",
+ "Browse": "Buscar",
+ "Upgrade the room": "Atualizar a sala",
+ "Kick users": "Remover usuários",
+ "Ban users": "Banir usuários",
+ "Remove messages": "Remover mensagens",
+ "Notify everyone": "Notificar todo mundo",
+ "Your email address hasn't been verified yet": "Seu endereço de e-mail ainda não foi verificado",
+ "Revoke": "Revogar",
+ "Share": "Compartilhar",
+ "Unable to revoke sharing for phone number": "Não foi possível revogar o compartilhamento do número de celular",
+ "Unable to share phone number": "Não foi possível compartilhar o número de celular",
+ "Please enter verification code sent via text.": "Digite o código de verificação enviado por mensagem de texto.",
+ "Remove %(email)s?": "Remover %(email)s?",
+ "Remove %(phone)s?": "Remover %(phone)s?",
+ "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.": "Digite o código de verificação enviado por mensagem de texto para +%(msisdn)s.",
+ "This user has not verified all of their sessions.": "Este usuário não verificou todas suas próprias sessões.",
+ "You have not verified this user.": "Você não verificou este usuário.",
+ "You have verified this user. This user has verified all of their sessions.": "Você confirmou este usuário. Este usuário verificou todas as próprias sessões.",
+ "Someone is using an unknown session": "Alguém está usando uma sessão desconhecida",
+ "Everyone in this room is verified": "Todo mundo nesta sala está verificado",
+ "Edit message": "Editar mensagem",
+ "Mod": "Moderador",
+ "Scroll to most recent messages": "Ir para as mensagens mais recentes",
+ "Close preview": "Fechar a visualização",
+ "Send a reply…": "Enviar uma resposta…",
+ "Send a message…": "Enviar uma mensagem…",
+ "Bold": "Negrito",
+ "Italics": "Itálico",
+ "Strikethrough": "Riscado",
+ "Code block": "Bloco de código",
+ "Failed to connect to integration manager": "Falha ao conectar-se ao gerenciador de integrações",
+ "Failed to revoke invite": "Falha ao revogar o convite",
+ "Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.": "Não foi possível revogar o convite. O servidor pode estar com um problema temporário ou você não tem permissões suficientes para revogar o convite.",
+ "Revoke invite": "Revogar o convite",
+ "Invited by %(sender)s": "Convidado por %(sender)s",
+ "Mark all as read": "Marcar tudo como lido",
+ "Error updating main address": "Erro ao atualizar o endereço principal",
+ "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.": "Ocorreu um erro ao atualizar o endereço principal da sala. Isso pode não ser permitido pelo servidor ou houve um problema temporário.",
+ "There was an error updating the room's alternative addresses. It may not be allowed by the server or a temporary failure occurred.": "Ocorreu um erro ao atualizar o endereço alternativo da sala. Isso pode não ser permitido pelo servidor ou houve um problema temporário.",
+ "Error creating address": "Erro ao criar o endereço",
+ "There was an error creating that address. It may not be allowed by the server or a temporary failure occurred.": "Ocorreu um erro ao criar esse endereço. Isso pode não ser permitido pelo servidor ou houve um problema temporário.",
+ "You don't have permission to delete the address.": "Você não tem permissão para excluir este endereço.",
+ "There was an error removing that address. It may no longer exist or a temporary error occurred.": "Ocorreu um erro ao remover esse endereço. Ele pode não mais existir ou houve um problema temporário.",
+ "Error removing address": "Erro ao remover o endereço",
+ "Main address": "Endereço principal",
+ "Room Name": "Nome da sala",
+ "Room avatar": "Foto da sala",
+ "Waiting for you to accept on your other session…": "Aguardando sua confirmação na sua outra sessão…",
+ "Waiting for %(displayName)s to accept…": "Aguardando %(displayName)s aceitar…",
+ "Accepting…": "Aceitando…",
+ "Your messages are secured and only you and the recipient have the unique keys to unlock them.": "Suas mensagens são protegidas e somente você e o destinatário têm as chaves exclusivas para desbloqueá-las.",
+ "Your messages are not secure": "Suas mensagens não estão seguras",
+ "Your homeserver": "Seu servidor local",
+ "Trusted": "Confiável",
+ "Not trusted": "Não confiável",
+ "%(count)s verified sessions|other": "%(count)s sessões verificadas",
+ "%(count)s verified sessions|one": "1 sessão verificada",
+ "Hide verified sessions": "Esconder sessões verificadas",
+ "%(count)s sessions|other": "%(count)s sessões",
+ "%(count)s sessions|one": "%(count)s sessão",
+ "Hide sessions": "Esconder sessões",
+ "No recent messages by %(user)s found": "Nenhuma mensagem recente de %(user)s foi encontrada",
+ "Remove recent messages by %(user)s": "Remover mensagens recentes de %(user)s",
+ "Remove %(count)s messages|other": "Remover %(count)s mensagens",
+ "Remove %(count)s messages|one": "Remover 1 mensagem",
+ "Remove recent messages": "Remover mensagens recentes",
+ "%(role)s in %(roomName)s": "%(role)s em %(roomName)s",
+ "Deactivate user?": "Desativar usuário?",
+ "Deactivate user": "Desativar usuário",
+ "Failed to deactivate user": "Falha ao desativar o usuário",
+ "Security": "Segurança",
+ "You've successfully verified your device!": "Você verificou o seu aparelho com êxito!",
+ "Verification timed out.": "O tempo de verificação se esgotou.",
+ "You cancelled verification on your other session.": "Você cancelou a verificação em sua outra sessão.",
+ "%(displayName)s cancelled verification.": "%(displayName)s cancelou a verificação.",
+ "You cancelled verification.": "Você cancelou a verificação.",
+ "Verification cancelled": "Verificação cancelada",
+ "Compare emoji": "Comparar emojis",
+ "Show image": "Mostrar imagem",
+ "You have ignored this user, so their message is hidden. Show anyways. ": "Você ignorou este usuário, portanto, a mensagem dele está oculta. Mostrar mesmo assim. ",
+ "You verified %(name)s": "Você verificou %(name)s",
+ "Use an identity server to invite by email. Use the default (%(defaultIdentityServerName)s) or manage in Settings .": "Use um servidor de identidade para convidar por e-mail. Use o padrão (%(defaultIdentityServerName)s) ou um servidor personalizado em Configurações .",
+ "Use an identity server to invite by email. Manage in Settings .": "Use um servidor de identidade para convidar por e-mail. Gerencie o servidor em Configurações .",
+ "Destroy cross-signing keys?": "Destruir chaves de assinatura cruzada?",
+ "Waiting for partner to confirm...": "Esperando a outra pessoa confirmar...",
+ "Enable 'Manage Integrations' in Settings to do this.": "Para fazer isso, ative 'Gerenciar Integrações' nas Configurações.",
+ "Your %(brand)s doesn't allow you to use an Integration Manager to do this. Please contact an admin.": "Seu %(brand)s não permite que você use o Gerenciador de Integrações para fazer isso. Entre em contato com um administrador.",
+ "Confirm to continue": "Confirme para continuar",
+ "Click the button below to confirm your identity.": "Clique no botão abaixo para confirmar sua identidade.",
+ "Failed to invite the following users to chat: %(csvUsers)s": "Falha ao convidar os seguintes usuários para a conversa: %(csvUsers)s",
+ "Something went wrong trying to invite the users.": "Ocorreu um erro ao tentar convidar os usuários.",
+ "We couldn't invite those users. Please check the users you want to invite and try again.": "Não foi possível convidar esses usuários. Por favor, tente novamente.",
+ "Failed to find the following users": "Falha ao encontrar os seguintes usuários",
+ "The following users might not exist or are invalid, and cannot be invited: %(csvNames)s": "Os seguintes usuários não puderam ser convidados porque não existem ou são inválidos: %(csvNames)s",
+ "Recent Conversations": "Conversas recentes",
+ "Suggestions": "Sugestões",
+ "Invite someone using their name, username (like ), email address or share this room .": "Convide alguém com seu nome, nome de usuário (por exemplo: ), endereço de e-mail ou compartilhe esta sala .",
+ "Use your account to sign in to the latest version of the app at ": "Use sua conta para fazer login na versão mais recente do aplicativo em ",
+ "Report bugs & give feedback": "Relatar erros & enviar comentários",
+ "Room Settings - %(roomName)s": "Configurações da sala - %(roomName)s",
+ "Automatically invite users": "Convidar usuários automaticamente",
+ "Upgrade private room": "Atualizar a sala privada",
+ "Upgrade public room": "Atualizar a sala pública",
+ "Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.": "Atualizar uma sala é uma ação avançada e geralmente é recomendada quando uma sala está instável devido a erros, recursos ausentes ou vulnerabilidades de segurança.",
+ "This usually only affects how the room is processed on the server. If you're having problems with your %(brand)s, please report a bug .": "Isso geralmente afeta apenas como a sala é processada no servidor. Se você tiver problemas com o %(brand)s, informe um erro .",
+ "You'll upgrade this room from to .": "Você atualizará esta sala de para .",
+ "A username can only contain lower case letters, numbers and '=_-./'": "Um nome de usuário só pode ter letras minúsculas, números e '=_-./'",
+ "Command Help": "Ajuda com Comandos",
+ "To help us prevent this in future, please send us logs .": "Para nos ajudar a evitar isso no futuro, envie-nos os registros .",
+ "Your browser likely removed this data when running low on disk space.": "O seu navegador provavelmente removeu esses dados quando o espaço de armazenamento ficou insuficiente.",
+ "Integration Manager": "Gerenciador de Integrações",
+ "Find others by phone or email": "Encontre outras pessoas por telefone ou e-mail",
+ "Use bots, bridges, widgets and sticker packs": "Use bots, pontes, widgets e pacotes de adesivos",
+ "Terms of Service": "Termos de serviço",
+ "To continue you need to accept the terms of this service.": "Para continuar, você precisa aceitar os termos deste serviço.",
+ "Service": "Serviço",
+ "Summary": "Resumo",
+ "Document": "Documento",
+ "Upload files (%(current)s of %(total)s)": "Enviar arquivos (%(current)s de %(total)s)",
+ "Upload files": "Enviar arquivos",
+ "Upload all": "Enviar tudo",
+ "This file is too large to upload. The file size limit is %(limit)s but this file is %(sizeOfThisFile)s.": "Este arquivo é muito grande para ser enviado. O limite do tamanho de arquivos é %(limit)s, enquanto que o tamanho desse arquivo é %(sizeOfThisFile)s.",
+ "These files are too large to upload. The file size limit is %(limit)s.": "Esses arquivos são muito grandes para serem enviados. O limite do tamanho de arquivos é %(limit)s.",
+ "Some files are too large to be uploaded. The file size limit is %(limit)s.": "Alguns arquivos são muito grandes para serem enviados. O limite do tamanho de arquivos é %(limit)s.",
+ "Upload %(count)s other files|other": "Enviar %(count)s outros arquivos",
+ "Upload %(count)s other files|one": "Enviar %(count)s outros arquivos",
+ "Cancel All": "Cancelar tudo",
+ "Upload Error": "Erro no envio",
+ "Verification Request": "Solicitação de verificação",
+ "Remember my selection for this widget": "Lembrar minha escolha para este widget",
+ "Deny": "Rejeitar",
+ "Wrong file type": "Tipo errado de arquivo",
+ "Address (optional)": "Endereço (opcional)",
+ "Report Content": "Reportar conteúdo",
+ "Update status": "Atualizar status",
+ "Set status": "Definir status",
+ "Hide": "Esconder",
+ "Help": "Ajuda",
+ "Remove for everyone": "Remover para todo mundo",
+ "Remove for me": "Remover para mim",
+ "User Status": "Status do usuário",
+ "This homeserver would like to make sure you are not a robot.": "Este servidor local gostaria se certificar de que você não é um robô.",
+ "Confirm your identity by entering your account password below.": "Confirme sua identidade digitando sua senha abaixo.",
+ "Unable to validate homeserver/identity server": "Não foi possível validar seu servidor local/servidor de identidade",
+ "Server Name": "Nome do servidor",
+ "Enter password": "Digite a senha",
+ "Nice, strong password!": "Muito bem, uma senha forte!",
+ "Password is allowed, but unsafe": "Esta senha é permitida, mas não é segura",
+ "Not sure of your password? Set a new one ": "Esqueceu sua senha? Defina uma nova ",
+ "Set an email for account recovery. Use email to optionally be discoverable by existing contacts.": "Defina um e-mail para poder recuperar a conta. Este e-mail também pode ser usado para encontrar seus contatos.",
+ "Enter your custom homeserver URL What does this mean? ": "Digite o URL de um servidor local O que isso significa? ",
+ "Homeserver URL": "URL do servidor local",
+ "Identity Server URL": "URL do servidor de identidade",
+ "Other servers": "Outros servidores",
+ "Free": "Gratuito",
+ "Find other public servers or use a custom server": "Encontre outros servidores públicos ou use um servidor personalizado",
+ "Sign in to your Matrix account on %(serverName)s": "Faça login com sua conta Matrix em %(serverName)s",
+ "Sign in to your Matrix account on ": "Faça login com sua conta Matrix em ",
+ "Sign in with SSO": "Faça login com SSO (Login Único)",
+ "Please install Chrome , Firefox , or Safari for the best experience.": "Por favor, instale o Chrome , o Firefox ou o Safari para obter a melhor experiência de uso.",
+ "Couldn't load page": "Não foi possível carregar a página",
+ "This homeserver does not support communities": "Este servidor local não suporta o recurso de comunidades",
+ "%(brand)s failed to get the protocol list from the homeserver. The homeserver may be too old to support third party networks.": "%(brand)s não conseguiu obter a lista de protocolos do servidor local. O servidor local pode ser muito antigo para suportar redes de terceiros.",
+ "%(brand)s failed to get the public room list.": "%(brand)s não conseguiu obter a lista de salas públicas.",
+ "The homeserver may be unavailable or overloaded.": "O servidor local pode estar indisponível ou sobrecarregado.",
+ "Delete the room address %(alias)s and remove %(name)s from the directory?": "Excluir o endereço da sala %(alias)s e remover %(name)s da lista de salas?",
+ "delete the address.": "exclui o endereço.",
+ "%(brand)s does not know how to join a room on this network": "%(brand)s não sabe como entrar em uma sala desta rede",
+ "View": "Ver",
+ "Find a room…": "Encontrar uma sala…",
+ "Find a room… (e.g. %(exampleRoom)s)": "Encontrar uma sala… (por exemplo: %(exampleRoom)s)",
+ "Search rooms": "Buscar salas",
+ "You have %(count)s unread notifications in a prior version of this room.|other": "Você tem %(count)s notificações não lidas em uma versão anterior desta sala.",
+ "You have %(count)s unread notifications in a prior version of this room.|one": "Você tem %(count)s notificações não lidas em uma versão anterior desta sala.",
+ "Feedback": "Feedback",
+ "User menu": "Menu do usuário",
+ "Could not load user profile": "Não foi possível carregar o perfil do usuário",
+ "Session verified": "Sessão verificada",
+ "Your Matrix account on %(serverName)s": "Sua conta Matrix em %(serverName)s",
+ "Your Matrix account on ": "Sua conta Matrix em ",
+ "No identity server is configured: add one in server settings to reset your password.": "Nenhum servidor de identidade está configurado: adicione um nas configurações do servidor para redefinir sua senha.",
+ "A verification email will be sent to your inbox to confirm setting your new password.": "Um e-mail de verificação será enviado para sua caixa de entrada para confirmar sua nova senha.",
+ "Your password has been reset.": "Sua senha foi alterada.",
+ "You have been logged out of all sessions and will no longer receive push notifications. To re-enable notifications, sign in again on each device.": "Você foi desconectado de todas as sessões e não receberá mais notificações. Para reativar as notificações, faça login novamente em cada aparelho.",
+ "Set a new password": "Digite uma nova senha",
+ "Invalid base_url for m.homeserver": "base_url inválido para m.homeserver",
+ "Invalid base_url for m.identity_server": "base_url inválido para m.identity_server",
+ "This account has been deactivated.": "Esta conta foi desativada.",
+ "Syncing...": "Sincronizando...",
+ "%(brand)s Web": "%(brand)s Web",
+ "Your new session is now verified. Other users will see it as trusted.": "Sua nova sessão agora está confirmada. Para outros usuários ela será vista como confiável.",
+ "Forgotten your password?": "Esqueceu sua senha?",
+ "Restore": "Restaurar",
+ "Copy": "Copiar",
+ "For maximum security, this should be different from your account password.": "Para segurança máxima, essa deve ser diferente da senha da sua conta.",
+ "Success!": "Pronto!",
+ "Disable": "Desativar",
+ "Space used:": "Espaço usado:",
+ "Navigation": "Navegação",
+ "Calls": "Chamadas",
+ "Esc": "Esc",
+ "Enter": "Enter"
}
diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json
index bc3ed92ec3..5e29bfed09 100644
--- a/src/i18n/strings/ru.json
+++ b/src/i18n/strings/ru.json
@@ -1547,8 +1547,8 @@
"%(creator)s created and configured the room.": "%(creator)s создал и настроил комнату.",
"Preview": "Заглянуть",
"View": "Просмотр",
- "Find a room…": "Найди комнату…",
- "Find a room… (e.g. %(exampleRoom)s)": "Найди комнату... (напр. %(exampleRoom)s)",
+ "Find a room…": "Поиск комнат…",
+ "Find a room… (e.g. %(exampleRoom)s)": "Поиск комнат... (напр. %(exampleRoom)s)",
"Explore rooms": "Список комнат",
"No identity server is configured: add one in server settings to reset your password.": "Идентификационный сервер не настроен: добавьте его в настройки сервера, чтобы сбросить пароль.",
"Command Autocomplete": "Автозаполнение команды",
@@ -1609,8 +1609,8 @@
"Verifies a user, session, and pubkey tuple": "Проверяет пользователя, сессию и публичные ключи",
"Unknown (user, session) pair:": "Неизвестная (пользователь:сессия) пара:",
"Session already verified!": "Сессия уже подтверждена!",
- "Never send encrypted messages to unverified sessions from this session": "Никогда не отправляйте зашифрованные сообщения в непроверенные сессий из этой сессии",
- "Never send encrypted messages to unverified sessions in this room from this session": "Никогда не отправляйте зашифрованные сообщения в непроверенные сессии в эту комнату из этой сессии",
+ "Never send encrypted messages to unverified sessions from this session": "Никогда не отправлять зашифрованные сообщения непроверенным сессиям в этой сессии",
+ "Never send encrypted messages to unverified sessions in this room from this session": "Никогда не отправлять зашифрованные сообщения непроверенным сессиям в этой комнате и в этой сессии",
"Your keys are not being backed up from this session .": "Ваши ключи не резервируются с этой сессии .",
"Server or user ID to ignore": "Сервер или ID пользователя для игнорирования",
"Subscribed lists": "Подписанные списки",
@@ -2172,5 +2172,8 @@
"This address is already in use": "Этот адрес уже используется",
"Are you sure you want to remove %(serverName)s ": "Вы уверены, что хотите удалить %(serverName)s ",
"Enter the name of a new server you want to explore.": "Введите имя нового сервера для просмотра.",
- "Reminder: Your browser is unsupported, so your experience may be unpredictable.": "Напоминание: ваш браузер не поддерживается, возможны непредвиденные проблемы."
+ "Reminder: Your browser is unsupported, so your experience may be unpredictable.": "Напоминание: ваш браузер не поддерживается, возможны непредвиденные проблемы.",
+ "Notification settings": "Настройки уведомлений",
+ "Switch to light mode": "Переключить в светлый режим",
+ "Switch to dark mode": "Переключить в тёмный режим"
}
diff --git a/src/i18n/strings/si.json b/src/i18n/strings/si.json
new file mode 100644
index 0000000000..11da7b7e4c
--- /dev/null
+++ b/src/i18n/strings/si.json
@@ -0,0 +1,8 @@
+{
+ "This email address is already in use": "මෙම විද්යුත් තැපැල් ලිපිනය දැනටමත් භාවිතයේ පවතී",
+ "This phone number is already in use": "මෙම දුරකථන අංකය දැනටමත් භාවිතයේ පවතී",
+ "Use Single Sign On to continue": "ඉදිරියට යාමට තනි පුරනය වීම භාවිතා කරන්න",
+ "Confirm adding this email address by using Single Sign On to prove your identity.": "ඔබගේ අනන්යතාවය සනාථ කිරීම සඳහා තනි පුරනය භාවිතා කිරීමෙන් මෙම විද්යුත් තැපැල් ලිපිනය එක් කිරීම තහවුරු කරන්න.",
+ "Confirm": "තහවුරු කරන්න",
+ "Add Email Address": "විද්යුත් තැපැල් ලිපිනය එක් කරන්න"
+}
diff --git a/src/i18n/strings/sq.json b/src/i18n/strings/sq.json
index 08deec1269..3750d4d768 100644
--- a/src/i18n/strings/sq.json
+++ b/src/i18n/strings/sq.json
@@ -2387,5 +2387,7 @@
"%(brand)s iOS": "%(brand)s iOS",
"%(brand)s X for Android": "%(brand)s X për Android",
"* %(senderName)s %(emote)s": "* %(senderName)s %(emote)s",
- "Custom Tag": "Etiketë Vetjake"
+ "Custom Tag": "Etiketë Vetjake",
+ "The person who invited you already left the room.": "Personi që ju ftoi ka dalë nga dhoma tashmë.",
+ "The person who invited you already left the room, or their server is offline.": "Personi që ju ftoi, ka dalë nga dhoma tashmë, ose shërbyesi i tij është jashtë funksionimi."
}
diff --git a/src/i18n/strings/uk.json b/src/i18n/strings/uk.json
index 7ccb338785..2272ed49eb 100644
--- a/src/i18n/strings/uk.json
+++ b/src/i18n/strings/uk.json
@@ -6,7 +6,7 @@
"Dismiss": "Відхилити",
"Error": "Помилка",
"Failed to forget room %(errCode)s": "Не вдалось видалити кімнату %(errCode)s",
- "Favourite": "Вибране",
+ "Favourite": "Улюблені",
"Mute": "Стишити",
"Notifications": "Сповіщення",
"Operation failed": "Не вдалося виконати дію",
@@ -78,7 +78,7 @@
"Unpin Message": "Відкріпити повідомлення",
"Register": "Зареєструватися",
"Rooms": "Кімнати",
- "Add rooms to this community": "Добавити кімнати в це суспільство",
+ "Add rooms to this community": "Додати кімнати в цю спільноту",
"This email address is already in use": "Ця е-пошта вже використовується",
"This phone number is already in use": "Цей телефонний номер вже використовується",
"Fetching third party location failed": "Не вдалось отримати стороннє місцеперебування",
@@ -240,33 +240,33 @@
"You cannot place a call with yourself.": "Ви не можете подзвонити самим собі.",
"Warning!": "Увага!",
"Upload Failed": "Помилка відвантаження",
- "Sun": "Нд",
- "Mon": "Пн",
- "Tue": "Вт",
- "Wed": "Ср",
- "Thu": "Чт",
- "Fri": "Пт",
- "Sat": "Сб",
- "Jan": "Січ",
- "Feb": "Лют",
- "Mar": "Бер",
- "Apr": "Квіт",
- "May": "Трав",
- "Jun": "Чер",
- "Jul": "Лип",
- "Aug": "Сер",
- "Sep": "Вер",
- "Oct": "Жов",
- "Nov": "Лис",
- "Dec": "Гру",
- "PM": "PM",
- "AM": "AM",
+ "Sun": "нд",
+ "Mon": "пн",
+ "Tue": "вт",
+ "Wed": "ср",
+ "Thu": "чт",
+ "Fri": "пт",
+ "Sat": "сб",
+ "Jan": "січ.",
+ "Feb": "лют.",
+ "Mar": "бер.",
+ "Apr": "квіт.",
+ "May": "трав.",
+ "Jun": "черв.",
+ "Jul": "лип.",
+ "Aug": "серп.",
+ "Sep": "вер.",
+ "Oct": "жовт.",
+ "Nov": "лист.",
+ "Dec": "груд.",
+ "PM": "пп",
+ "AM": "дп",
"%(weekDayName)s %(time)s": "%(weekDayName)s %(time)s",
- "%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(monthName)s, %(day)s, %(time)s",
- "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(monthName)s, %(day)s, %(fullYear)s",
- "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s",
+ "%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(day)s %(monthName)s %(time)s",
+ "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(day)s %(monthName)s %(fullYear)s",
+ "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(day)s %(monthName)s %(fullYear)s %(time)s",
"Who would you like to add to this community?": "Кого ви хочете додати до цієї спільноти?",
- "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Якщо дана сторінка містить особисту інформацію, як то назва кімнати, користувача чи групи, ці дані будуть вилучені перед надсиланням на сервер.",
+ "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Там, де ця сторінка містить ототожненну інформацію, як-от назва кімнати, користувача чи групи, ці дані будуть вилучені перед надсиланням на сервер.",
"Call in Progress": "Іде виклик",
"A call is currently being placed!": "Зараз іде виклик!",
"A call is already in progress!": "Вже здійснюється дзвінок!",
@@ -311,9 +311,9 @@
"Changes your display nickname": "Змінює ваш нік",
"Invites user with given id to current room": "Запрошує користувача з вказаним ідентифікатором до кімнати",
"Leave room": "Покинути кімнату",
- "Kicks user with given id": "Вилучити з кімнати користувача з вказаним ідентифікатором",
- "Ignores a user, hiding their messages from you": "Ігнорує користувача, приховуючи повідомлення від них",
- "Ignored user": "Користувача ігноровано",
+ "Kicks user with given id": "Викидає з кімнати користувача з вказаним ідентифікатором",
+ "Ignores a user, hiding their messages from you": "Ігнорує користувача, приховуючи його повідомлення від вас",
+ "Ignored user": "Зігнорований користувач",
"You are now ignoring %(userId)s": "Ви ігноруєте %(userId)s",
"Stops ignoring a user, showing their messages going forward": "Припиняє ігнорувати користувача, від цього моменту показуючи їхні повідомлення",
"Unignored user": "Припинено ігнорування користувача",
@@ -321,8 +321,8 @@
"Define the power level of a user": "Вказати рівень повноважень користувача",
"Deops user with given id": "Знімає права оператора з користувача з вказаним ідентифікатором",
"Opens the Developer Tools dialog": "Відкриває вікно інструментів розробника",
- "Verified key": "Перевірений ключ",
- "Displays action": "Показує дію",
+ "Verified key": "Звірений ключ",
+ "Displays action": "Відбиває дію",
"Reason": "Причина",
"%(senderName)s requested a VoIP conference.": "%(senderName)s бажає розпочати дзвінок-конференцію.",
"%(senderName)s invited %(targetName)s.": "%(senderName)s запросив/ла %(targetName)s.",
@@ -339,9 +339,9 @@
"%(senderName)s unbanned %(targetName)s.": "%(senderName)s розблокував/ла %(targetName)s.",
"%(senderName)s kicked %(targetName)s.": "%(senderName)s викинув/ла %(targetName)s.",
"%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s відкликав/ла запрошення %(targetName)s.",
- "%(senderDisplayName)s sent an image.": "%(senderDisplayName)s надіслав/ла зображення.",
- "%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s призначив/ла основну адресу цієї кімнати: %(address)s.",
- "%(senderName)s removed the main address for this room.": "%(senderName)s вилучив/ла основу адресу цієї кімнати.",
+ "%(senderDisplayName)s sent an image.": "%(senderDisplayName)s надіслав(-ла) зображення.",
+ "%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s призначив(-ла) основну адресу цієї кімнати: %(address)s.",
+ "%(senderName)s removed the main address for this room.": "%(senderName)s прибрав(-ла) основу адресу цієї кімнати.",
"Someone": "Хтось",
"(not supported by this browser)": "(не підтримується цією веб-переглядачкою)",
"(could not connect media)": "(не можливо під'єднати медіа)",
@@ -403,7 +403,7 @@
"Password": "Пароль",
"New Password": "Новий пароль",
"Confirm password": "Підтвердження пароля",
- "Last seen": "Востаннє з'являвся",
+ "Last seen": "Востаннє в мережі",
"Failed to set display name": "Не вдалося встановити ім'я для показу",
"The maximum permitted number of widgets have already been added to this room.": "Максимально дозволену кількість віджетів уже додано до цієї кімнати.",
"Drop File Here": "Киньте файл сюди",
@@ -460,7 +460,7 @@
"Sends a message as plain text, without interpreting it as markdown": "Надсилає повідомлення як чистий текст, не використовуючи markdown",
"Upgrades a room to a new version": "Покращує кімнату до нової версії",
"You do not have the required permissions to use this command.": "Вам бракує дозволу на використання цієї команди.",
- "Warning : Upgrading a room will not automatically migrate room members to the new version of the room. We'll post a link to the new room in the old version of the room - room members will have to click this link to join the new room.": "Увага! : Покращення кімнати не перенесе автоматично усіх учасників до нової версії кімнати. Ми опублікуємо посилання на нову кімнату у старій версії кімнати, а учасники мають власноруч клацнути це посилання, щоб приєднатися до нової кімнати.",
+ "Warning : Upgrading a room will not automatically migrate room members to the new version of the room. We'll post a link to the new room in the old version of the room - room members will have to click this link to join the new room.": "Увага! : Поліпшення кімнати не перенесе автоматично усіх учасників до нової версії кімнати. Ми опублікуємо посилання на нову кімнату у старій версії кімнати, а учасники мають власноруч клацнути це посилання, щоб приєднатися до нової кімнати.",
"Changes your display nickname in the current room only": "Змінює ваше псевдо тільки для поточної кімнати",
"Changes the avatar of the current room": "Змінює аватар поточної кімнати",
"Changes your avatar in this current room only": "Змінює ваш аватар для поточної кімнати",
@@ -469,13 +469,13 @@
"This room has no topic.": "Ця кімната не має теми.",
"Sets the room name": "Встановлює назву кімнати",
"Use an identity server": "Використовувати сервер ідентифікації",
- "Use an identity server to invite by email. Manage in Settings.": "Використовувати сервер ідентифікації для запрошень через е-пошту. Керується у налаштуваннях.",
+ "Use an identity server to invite by email. Manage in Settings.": "Використовувати сервер ідентифікації для запрошень через е-пошту. Керуйте у налаштуваннях.",
"Unbans user with given ID": "Розблоковує користувача з вказаним ідентифікатором",
"Adds a custom widget by URL to the room": "Додає власний віджет до кімнати за посиланням",
"Please supply a https:// or http:// widget URL": "Вкажіть посилання на віджет — https:// або http://",
"You cannot modify widgets in this room.": "Ви не можете змінювати віджети у цій кімнаті.",
- "Forces the current outbound group session in an encrypted room to be discarded": "Примусово відкидає поточний вихідний груповий сеанс у шифрованій кімнаті",
- "Sends the given message coloured as a rainbow": "Надсилає вказане повідомлення розфарбоване веселкою",
+ "Forces the current outbound group session in an encrypted room to be discarded": "Примусово відкидає поточний вихідний груповий сеанс у зашифрованій кімнаті",
+ "Sends the given message coloured as a rainbow": "Надсилає вказане повідомлення, розфарбоване веселкою",
"Your %(brand)s is misconfigured": "Ваш %(brand)s налаштовано неправильно",
"Join the discussion": "Приєднатися до обговорення",
"Upload": "Обрати",
@@ -639,47 +639,47 @@
"Room Settings - %(roomName)s": "Налаштування кімнати - %(roomName)s",
"A verification email will be sent to your inbox to confirm setting your new password.": "Ми відправимо перевіряльний електронний лист до вас для підтвердження зміни пароля.",
"To return to your account in future you need to set a password": "Щоб повернутися до своєї обліківки в майбутньому, вам потрібно встановити пароль",
- "Use an identity server to invite by email. Click continue to use the default identity server (%(defaultIdentityServerName)s) or manage in Settings.": "Використайте сервер ідентифікації, щоб запросити е-поштою. Нажміть продовжити, щоб використовувати звичайний сервер ідентифікації(%(defaultIdentityServerName)s) або змініть у Налаштуваннях.",
+ "Use an identity server to invite by email. Click continue to use the default identity server (%(defaultIdentityServerName)s) or manage in Settings.": "Використовувати сервер ідентифікації, щоб запрошувати через е-пошту. Натисніть \"Продовжити\", щоб використовувати типовий сервер ідентифікації (%(defaultIdentityServerName)s) або змініть його у налаштуваннях.",
"Joins room with given address": "Приєднатися до кімнати зі вказаною адресою",
- "Unrecognised room address:": "Не вдалося знайти адресу кімнати:",
+ "Unrecognised room address:": "Невпізнана адреса кімнати:",
"Command failed": "Не вдалося виконати команду",
"Could not find user in room": "Не вдалося знайти користувача в кімнаті",
"Please supply a widget URL or embed code": "Вкажіть URL або код вставки віджету",
- "Verifies a user, session, and pubkey tuple": "Перевіряє користувача, сесію та публічні ключі",
- "Unknown (user, session) pair:": "Невідома (користувач:сесія) пара:",
- "Session already verified!": "Сесія вже підтверджена!",
- "WARNING: Session already verified, but keys do NOT MATCH!": "УВАГА: Сесія вже підтверджена, проте ключі НЕ ЗБІГАЮТЬСЯ!",
- "WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and session %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "УВАГА: НЕ ВДАЛОСЯ ЗДІЙСНИТИ ПЕРЕВІРКУ КЛЮЧА! Ключом для %(userId)s та сесії %(devicerId)s являється \"%(fprint)s\", що не відповідає вказаному ключу \"%(fingerprint)s\". Це може означати, що ваші повідомлення перехоплюються!",
- "The signing key you provided matches the signing key you received from %(userId)s's session %(deviceId)s. Session marked as verified.": "Ключ підпису, який ви надали, відповідає ключу підпису, який ви отримали від %(userId)s's сесії %(deviceId)s. Сесія відзначена як підтверджена.",
- "Sends the given emote coloured as a rainbow": "Надсилає заданий смайлик, пофарбований у вигляді веселки",
- "Displays list of commands with usages and descriptions": "Відображає список команд із описом та користуванням",
- "Displays information about a user": "Показати інформацію про користувача",
- "Send a bug report with logs": "Відправити звіт про помилку з логами",
- "Opens chat with the given user": "Відкрити чат з даним користувачем",
- "Sends a message to the given user": "Відправити повідомлення даному користувачу",
- "%(senderName)s made no change.": "%(senderName)s не вніс змін.",
+ "Verifies a user, session, and pubkey tuple": "Звіряє користувача, сеанс та кортеж відкритого ключа",
+ "Unknown (user, session) pair:": "Невідома пара (користувача, сеансу):",
+ "Session already verified!": "Сеанс вже підтверджений!",
+ "WARNING: Session already verified, but keys do NOT MATCH!": "УВАГА: Сеанс вже підтверджений, проте ключі НЕ ЗБІГАЮТЬСЯ!",
+ "WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and session %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "УВАГА: НЕ ВДАЛОСЯ ЗДІЙСНИТИ ЗВІРЯННЯ КЛЮЧА! Ключем для %(userId)s та сеансу %(deviceId)s є \"%(fprint)s\", що не відповідає наданому ключу \"%(fingerprint)s\". Це може означати, що ваші повідомлення перехоплюють!",
+ "The signing key you provided matches the signing key you received from %(userId)s's session %(deviceId)s. Session marked as verified.": "Наданий вами ключ підпису збігається з ключем підпису, що ви отримали від сеансу %(deviceId)s %(userId)s. Сеанс позначено як звірений.",
+ "Sends the given emote coloured as a rainbow": "Надсилає вказаний смайлик, розфарбований веселкою",
+ "Displays list of commands with usages and descriptions": "Відбиває перелік команд із прикладами вжитку та описом",
+ "Displays information about a user": "Відбиває інформацію про користувача",
+ "Send a bug report with logs": "Надіслати звіт про ваду разом з журналами",
+ "Opens chat with the given user": "Відкриває балачку з вказаним користувачем",
+ "Sends a message to the given user": "Надсилає повідомлення вказаному користувачеві",
+ "%(senderName)s made no change.": "%(senderName)s не запровадив(-ла) жодних змін.",
"%(senderDisplayName)s changed the room name from %(oldRoomName)s to %(newRoomName)s.": "%(senderDisplayName)s змінив(ла) назву кімнати з %(oldRoomName)s на %(newRoomName)s.",
- "%(senderDisplayName)s upgraded this room.": "%(senderDisplayName)s модернізував цю кімнату.",
- "%(senderDisplayName)s made the room public to whoever knows the link.": "%(senderDisplayName)s робить кімнату публічною для всіх, хто знає посилання.",
- "%(senderDisplayName)s made the room invite only.": "%(senderDisplayName)s робить кімнату доступною лише по запрошенню.",
- "%(senderDisplayName)s changed the join rule to %(rule)s": "%(senderDisplayName)s змінює правило входу на \"%(rule)s\"",
- "%(senderDisplayName)s has allowed guests to join the room.": "%(senderDisplayName)s дозволяє гостям приєднуватися до кімнати.",
- "%(senderDisplayName)s has prevented guests from joining the room.": "%(senderDisplayName)s забороняє гостям приєднуватися до кімнати.",
- "%(senderDisplayName)s changed guest access to %(rule)s": "%(senderDisplayName)s змінює гостьовий доступ на \"%(rule)s\"",
- "%(senderDisplayName)s enabled flair for %(groups)s in this room.": "%(senderDisplayName)s увімкнуто для %(groups)s у цій кімнаті.",
- "%(senderDisplayName)s disabled flair for %(groups)s in this room.": "%(senderDisplayName)s вимкнуто для %(groups)s в цій кімнаті.",
- "%(senderName)s added the alternative addresses %(addresses)s for this room.|other": "%(senderName)s додав(ла) альтернативні адреси %(addresses)s для цієї кімнати.",
- "%(senderName)s added the alternative addresses %(addresses)s for this room.|one": "%(senderName)s добавив(ла)альтернативні адреси %(addresses)s для цієї кімнати.",
- "%(senderName)s removed the alternative addresses %(addresses)s for this room.|other": "%(senderName)s видалив(ла) альтернативні адреси %(addresses)s для цієї кімнати.",
- "%(senderName)s removed the alternative addresses %(addresses)s for this room.|one": "%(senderName)s видалив(ла) альтернативні адреси %(addresses)s для цієї кімнати.",
- "%(senderName)s changed the alternative addresses for this room.": "%(senderName)s змінив(ла) альтернативні адреси для цієї кімнати.",
- "%(senderName)s changed the main and alternative addresses for this room.": "%(senderName)s змінив(ла) головні та альтернативні адреси для цієї кімнати.",
- "%(senderName)s changed the addresses for this room.": "%(senderName)s змінив(ла) адреси для цієї кімнати.",
- "%(senderName)s placed a voice call.": "%(senderName)s зробив голосовий виклик.",
- "%(senderName)s placed a voice call. (not supported by this browser)": "%(senderName)s зробив голосовий виклик. (не підтримується вашим браузером)",
- "%(senderName)s placed a video call.": "%(senderName)s здійснив відео-виклик.",
- "%(senderName)s placed a video call. (not supported by this browser)": "%(senderName)s здійснив відео-виклик. (не підтримується вашим браузером)",
- "%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.": "%(senderName)s відкликав(ла) запрошення %(targetDisplayName)s приєднання до кімнати.",
+ "%(senderDisplayName)s upgraded this room.": "%(senderDisplayName)s поліпшив(-ла) цю кімнату.",
+ "%(senderDisplayName)s made the room public to whoever knows the link.": "%(senderDisplayName)s зробив(-ла) кімнату відкритою для всіх, хто знає посилання.",
+ "%(senderDisplayName)s made the room invite only.": "%(senderDisplayName)s зробив(-ла) кімнату доступною лише за запрошеннями.",
+ "%(senderDisplayName)s changed the join rule to %(rule)s": "%(senderDisplayName)s змінив(-ла) правило приєднування на \"%(rule)s\"",
+ "%(senderDisplayName)s has allowed guests to join the room.": "%(senderDisplayName)s дозволив(-ла) гостям приєднуватися до кімнати.",
+ "%(senderDisplayName)s has prevented guests from joining the room.": "%(senderDisplayName)s заборонив(-ла) гостям приєднуватися до кімнати.",
+ "%(senderDisplayName)s changed guest access to %(rule)s": "%(senderDisplayName)s змінив(-ла) гостьовий доступ на \"%(rule)s\"",
+ "%(senderDisplayName)s enabled flair for %(groups)s in this room.": "%(senderDisplayName)s увімкнув(-ла) значок для %(groups)s у цій кімнаті.",
+ "%(senderDisplayName)s disabled flair for %(groups)s in this room.": "%(senderDisplayName)s вимкнув(-ла) значок для %(groups)s в цій кімнаті.",
+ "%(senderName)s added the alternative addresses %(addresses)s for this room.|other": "%(senderName)s додав(-ла) альтернативні адреси %(addresses)s для цієї кімнати.",
+ "%(senderName)s added the alternative addresses %(addresses)s for this room.|one": "%(senderName)s додав(-ла) альтернативні адреси %(addresses)s для цієї кімнати.",
+ "%(senderName)s removed the alternative addresses %(addresses)s for this room.|other": "%(senderName)s прибрав(-ла) альтернативні адреси %(addresses)s для цієї кімнати.",
+ "%(senderName)s removed the alternative addresses %(addresses)s for this room.|one": "%(senderName)s прибрав(-ла) альтернативні адреси %(addresses)s для цієї кімнати.",
+ "%(senderName)s changed the alternative addresses for this room.": "%(senderName)s змінив(-ла) альтернативні адреси для цієї кімнати.",
+ "%(senderName)s changed the main and alternative addresses for this room.": "%(senderName)s змінив(-ла) головні та альтернативні адреси для цієї кімнати.",
+ "%(senderName)s changed the addresses for this room.": "%(senderName)s змінив(-ла) адреси для цієї кімнати.",
+ "%(senderName)s placed a voice call.": "%(senderName)s розпочав(-ла) голосовий виклик.",
+ "%(senderName)s placed a voice call. (not supported by this browser)": "%(senderName)s розпочав(-ла) голосовий виклик. (не підтримується цим переглядачем)",
+ "%(senderName)s placed a video call.": "%(senderName)s розпочав(-ла) відеовиклик.",
+ "%(senderName)s placed a video call. (not supported by this browser)": "%(senderName)s розпочав(-ла) відеовиклик. (не підтримується цим переглядачем)",
+ "%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.": "%(senderName)s відкликав(-ла) запрошення %(targetDisplayName)s приєднання до кімнати.",
"%(senderName)s removed the rule banning users matching %(glob)s": "%(senderName)s видалив(ла) правило блокування користувачів по шаблону %(glob)s",
"%(senderName)s removed the rule banning rooms matching %(glob)s": "%(senderName)s видалив(ла) правило блокування кімнат по шаблону %(glob)s",
"%(senderName)s removed the rule banning servers matching %(glob)s": "%(senderName)s видалив(ла) правило блокування серверів по шаблону %(glob)s",
@@ -765,13 +765,13 @@
"Review where you’re logged in": "Перевірте, де ви ввійшли",
"Your homeserver has exceeded its user limit.": "Ваш домашній сервер перевищив свій ліміт користувачів.",
"Your homeserver has exceeded one of its resource limits.": "Ваш домашній сервер перевищив один із своїх ресурсних лімітів.",
- "Contact your server admin .": "Зверніться до адміністратора серверу .",
+ "Contact your server admin .": "Зверніться до адміністратора серверу .",
"Ok": "Гаразд",
"Set password": "Встановити пароль",
"Set up encryption": "Налаштування шифрування",
- "Encryption upgrade available": "Доступне оновлення шифрування",
+ "Encryption upgrade available": "Доступне поліпшене шифрування",
"Set up": "Налаштувати",
- "Upgrade": "Оновлення",
+ "Upgrade": "Поліпшити",
"Other users may not trust it": "Інші користувачі можуть не довіряти цьому",
"New login. Was this you?": "Новий вхід у вашу обліківку. Це були Ви?",
"Restart": "Перезапустити",
@@ -855,7 +855,7 @@
"Preferences": "Параметри",
"Room list": "Перелік кімнат",
"Composer": "Редактор",
- "Security & Privacy": "Безпека та конфіденціальність",
+ "Security & Privacy": "Безпека та конфіденційність",
"Where you’re logged in": "Де ви ввійшли",
"Skip": "Пропустити",
"Notification settings": "Налаштування сповіщень",
@@ -882,7 +882,7 @@
"Enable 'Manage Integrations' in Settings to do this.": "Щоб зробити це увімкніть \"Керувати інтеграціями\" у налаштуваннях.",
"Confirm by comparing the following with the User Settings in your other session:": "Підтвердьте шляхом порівняння наступного рядка з рядком у користувацьких налаштуваннях вашого іншого сеансу:",
"Confirm this user's session by comparing the following with their User Settings:": "Підтвердьте сеанс цього користувача шляхом порівняння наступного рядка з рядком з їхніх користувацьких налаштувань:",
- "We recommend you change your password and recovery key in Settings immediately": "Ми радимо невідкладно змінити ваші пароль та ключ відновлення у налаштуваннях",
+ "We recommend you change your password and recovery key in Settings immediately": "Ми радимо невідкладно змінити ваші пароль та відновлювальний ключ у налаштуваннях",
"Share Message": "Поширити повідомлення",
"Community Settings": "Налаштування спільноти",
"All settings": "Усі налаштування",
@@ -968,7 +968,7 @@
"Cross-signing and secret storage are enabled.": "Кросс-підпис та секретне сховище дозволені.",
"well formed": "добре сформований",
"unexpected type": "несподіваний тип",
- "Cross-signing public keys:": "Публічні ключі для кросс-підпису:",
+ "Cross-signing public keys:": "Перехресно-підписувальні відкриті ключі:",
"in memory": "у пам'яті",
"not found": "не знайдено",
"Cross-signing private keys:": "Приватні ключі для кросс-підпису:",
@@ -1004,5 +1004,101 @@
"Enter a new identity server": "Введіть новий сервер ідентифікації",
"Change": "Змінити",
"Manage integrations": "Керування інтеграціями",
- "Size must be a number": "Розмір повинен бути числом"
+ "Size must be a number": "Розмір повинен бути числом",
+ "Incoming voice call": "Входовий голосовий виклик",
+ "Incoming video call": "Входовий відеовиклик",
+ "Upgrade to your own domain": "Поліпшити до свого власного домену",
+ "No Audio Outputs detected": "Звуковий вивід не виявлено",
+ "Audio Output": "Звуковий вивід",
+ "Voice & Video": "Голос та відео",
+ "Upgrade this room to the recommended room version": "Поліпшити цю кімнату до рекомендованої версії",
+ "this room": "ця кімната",
+ "Upgrade the room": "Поліпшити кімнату",
+ "Unable to revoke sharing for email address": "Не вдалось відкликати оприлюднювання адреси е-пошти",
+ "Revoke": "Відкликати",
+ "Unable to revoke sharing for phone number": "Не вдалось відкликати оприлюднювання телефонного номеру",
+ "Filter room members": "Відфільтрувати учасників кімнати",
+ "Voice call": "Голосовий виклик",
+ "Video call": "Відеовиклик",
+ "Not now": "Не зараз",
+ "Don't ask me again": "Не запитувати мене знову",
+ "Appearance": "Вигляд",
+ "Show rooms with unread messages first": "Показувати вгорі кімнати з непрочитаними повідомленнями",
+ "Show previews of messages": "Показувати попередній перегляд повідомлень",
+ "Sort by": "Упорядкувати за",
+ "Activity": "Активністю",
+ "A-Z": "А-Я",
+ "List options": "Параметри переліку",
+ "Use default": "Типово",
+ "Mentions & Keywords": "Згадки та ключові слова",
+ "Notification options": "Параметри сповіщень",
+ "Leave Room": "Вийти з кімнати",
+ "Forget Room": "Забути кімнату",
+ "Favourited": "Улюблено",
+ "%(count)s unread messages including mentions.|other": "%(count)s непрочитаних повідомлень включно зі згадками.",
+ "%(count)s unread messages including mentions.|one": "1 непрочитана згадка.",
+ "%(count)s unread messages.|other": "%(count)s непрочитаних повідомлень.",
+ "%(count)s unread messages.|one": "1 непрочитане повідомлення.",
+ "Unread messages.": "Непрочитані повідомлення.",
+ "This room is public": "Ця кімната є прилюдною",
+ "Show Stickers": "Показати наліпки",
+ "Failed to revoke invite": "Не вдалось відкликати запрошення",
+ "Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.": "Не вдалось відкликати запрошення. Сервер може мати тимчасові збої або у вас немає достатніх дозволів щоб відкликати запрошення.",
+ "Revoke invite": "Відкликати запрошення",
+ "Security": "Безпека",
+ "Report bugs & give feedback": "Відзвітувати про вади та залишити відгук",
+ "Report Content to Your Homeserver Administrator": "Поскаржитись на зміст адміністратору вашого домашнього сервера",
+ "Failed to upgrade room": "Не вдалось поліпшити кімнату",
+ "The room upgrade could not be completed": "Поліпшення кімнати не може бути завершене",
+ "Upgrade this room to version %(version)s": "Поліпшити цю кімнату до версії %(version)s",
+ "Upgrade Room Version": "Поліпшити версію кімнати",
+ "Upgrade private room": "Поліпшити закриту кімнату",
+ "You'll upgrade this room from to .": "Ви поліпшите цю кімнату з до версії.",
+ "Share Room Message": "Поширити повідомлення кімнати",
+ "Report Content": "Поскаржитись на зміст",
+ "Feedback": "Зворотній зв'язок",
+ "General failure": "Загальний збій",
+ "Enter your account password to confirm the upgrade:": "Введіть пароль вашого облікового запису щоб підтвердити поліпшення:",
+ "Security & privacy": "Безпека та конфіденційність",
+ "Secret storage public key:": "Таємне сховище відкритого ключа:",
+ "Key backup": "Резервне копіювання ключів",
+ "Message search": "Пошук повідомлень",
+ "Cross-signing": "Перехресне підписування",
+ "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "Адміністратором вашого сервера було вимкнено початкове наскрізне шифрування у закритих кімнатах та особистих повідомленнях.",
+ "Something went wrong!": "Щось пішло не так!",
+ "expand": "розгорнути",
+ "Wrong Recovery Key": "Неправильний відновлювальний ключ",
+ "Invalid Recovery Key": "Нечинний відновлювальний ключ",
+ "Recovery key mismatch": "Незбіг відновлювального ключа",
+ "Backup could not be decrypted with this recovery key: please verify that you entered the correct recovery key.": "Резервна копія не може бути дешифрована з цим відновлювальним ключем: переконайтесь, будь ласка, що ви ввели правильний відновлювальний ключ.",
+ "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options ": "Якщо ви забули відновлювальну парольну фразу, ви можете скористатись вашим відновлювальним ключем або налаштувати нові параметри відновлювання ",
+ "Enter recovery key": "Введіть відновлювальний ключ",
+ "This looks like a valid recovery key!": "Це скидається на чинний відновлювальний ключ!",
+ "Not a valid recovery key": "Нечинний відновлювальний ключ",
+ "Access your secure message history and set up secure messaging by entering your recovery key.": "Доступіться до вашої захищеної історії повідомлень та налаштуйте захищене листування шляхом вводження вашого відновлювального ключа.",
+ "If you've forgotten your recovery key you can set up new recovery options ": "Якщо ви забули ваш відновлювальний ключ, ви можете наново налаштувати параметри відновлювання ",
+ "Switch to light mode": "Світла тема",
+ "Switch to dark mode": "Темна тема",
+ "Use Recovery Key or Passphrase": "Скористуйтесь відновлювальними ключем або парольною фразою",
+ "Use Recovery Key": "Скористуйтесь відновлювальним ключем",
+ "Set up with a recovery key": "Налаштувати з відновлювальним ключем",
+ "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.": "Ваш відновлювальний ключ — це убезпека. Ви можете використовувати його щоб доступитись до ваших зашифрованих повідомлень у разі втрати вашої відновлювальної парольної фрази.",
+ "Your recovery key": "Ваш відновлювальний ключ",
+ "Your recovery key has been copied to your clipboard , paste it to:": "Ваш відновлювальний ключ було скопійовано до буферу обміну , вставте його у:",
+ "Your recovery key is in your Downloads folder.": "Ваш відновлювальний ключ у вашій теці Завантаження .",
+ "Make a copy of your recovery key": "Зробити копію вашого відновлювального ключа",
+ "Don't ask again": "Не запитувати знову",
+ "New Recovery Method": "Новий відновлювальний засіб",
+ "A new recovery passphrase and key for Secure Messages have been detected.": "Було виявлено нові відновлювальні парольну фразу та ключ від захищених повідомлень.",
+ "This session is encrypting history using the new recovery method.": "Цей сеанс зашифровує історію новим відновлювальним засобом.",
+ "Set up Secure Messages": "Налаштувати захищені повідомлення",
+ "Recovery Method Removed": "Відновлювальний засіб було видалено",
+ "This session has detected that your recovery passphrase and key for Secure Messages have been removed.": "Цей сеанс виявив, що ваші відновлювальні парольна фраза та ключ від захищених повідомлень були видалені.",
+ "%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.": "%(senderDisplayName)s увімкнув(-ла) значок для %(newGroups)s та вимкнув(-ла) значок для %(oldGroups)s у цій кімнаті.",
+ "New version available. Update now. ": "Доступна нова версія. Оновити зараз ",
+ "Upgrade public room": "Поліпшити відкриту кімнату",
+ "Restore your key backup to upgrade your encryption": "Відновіть резервну копію вашого ключа щоб поліпшити шифрування",
+ "You'll need to authenticate with the server to confirm the upgrade.": "Ви матимете пройти розпізнання на сервері щоб підтвердити поліпшування.",
+ "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.": "Поліпште цей сеанс щоб уможливити звіряння інших сеансів, надаючи їм доступ до зашифрованих повідомлень та позначуючи їх довіреними для інших користувачів.",
+ "Upgrade your encryption": "Поліпшити ваше шифрування"
}
diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js
index 1dc31869c9..d5abb3f2d2 100644
--- a/src/indexing/EventIndex.js
+++ b/src/indexing/EventIndex.js
@@ -18,8 +18,9 @@ import PlatformPeg from "../PlatformPeg";
import {MatrixClientPeg} from "../MatrixClientPeg";
import {EventTimeline, RoomMember} from 'matrix-js-sdk';
import {sleep} from "../utils/promise";
-import SettingsStore, {SettingLevel} from "../settings/SettingsStore";
+import SettingsStore from "../settings/SettingsStore";
import {EventEmitter} from "events";
+import {SettingLevel} from "../settings/SettingLevel";
/*
* Event indexing class that wraps the platform specific event indexing.
diff --git a/src/indexing/EventIndexPeg.js b/src/indexing/EventIndexPeg.js
index 20e05f985d..58e8430825 100644
--- a/src/indexing/EventIndexPeg.js
+++ b/src/indexing/EventIndexPeg.js
@@ -21,7 +21,8 @@ limitations under the License.
import PlatformPeg from "../PlatformPeg";
import EventIndex from "../indexing/EventIndex";
-import SettingsStore, {SettingLevel} from '../settings/SettingsStore';
+import SettingsStore from '../settings/SettingsStore';
+import {SettingLevel} from "../settings/SettingLevel";
const INDEX_VERSION = 1;
diff --git a/src/languageHandler.tsx b/src/languageHandler.tsx
index 91d90d4e6c..9d28b7d07d 100644
--- a/src/languageHandler.tsx
+++ b/src/languageHandler.tsx
@@ -21,11 +21,12 @@ import request from 'browser-request';
import counterpart from 'counterpart';
import React from 'react';
-import SettingsStore, {SettingLevel} from "./settings/SettingsStore";
+import SettingsStore from "./settings/SettingsStore";
import PlatformPeg from "./PlatformPeg";
// @ts-ignore - $webapp is a webpack resolve alias pointing to the output directory, see webpack config
import webpackLangJsonUrl from "$webapp/i18n/languages.json";
+import { SettingLevel } from "./settings/SettingLevel";
const i18nFolder = 'i18n/';
diff --git a/src/mjolnir/Mjolnir.js b/src/mjolnir/Mjolnir.js
index 9876cb1f7f..891438bbb9 100644
--- a/src/mjolnir/Mjolnir.js
+++ b/src/mjolnir/Mjolnir.js
@@ -16,9 +16,10 @@ limitations under the License.
import {MatrixClientPeg} from "../MatrixClientPeg";
import {ALL_RULE_TYPES, BanList} from "./BanList";
-import SettingsStore, {SettingLevel} from "../settings/SettingsStore";
+import SettingsStore from "../settings/SettingsStore";
import {_t} from "../languageHandler";
import dis from "../dispatcher/dispatcher";
+import {SettingLevel} from "../settings/SettingLevel";
// TODO: Move this and related files to the js-sdk or something once finalized.
diff --git a/src/rageshake/submit-rageshake.ts b/src/rageshake/submit-rageshake.ts
index 64a1ea0c33..350602aa5d 100644
--- a/src/rageshake/submit-rageshake.ts
+++ b/src/rageshake/submit-rageshake.ts
@@ -141,7 +141,7 @@ export default async function sendBugReport(bugReportEndpoint: string, opts: IOp
}
// add labs options
- const enabledLabs = SettingsStore.getLabsFeatures().filter(SettingsStore.isFeatureEnabled);
+ const enabledLabs = SettingsStore.getLabsFeatures().filter(f => SettingsStore.isFeatureEnabled(f));
if (enabledLabs.length) {
body.append('enabled_labs', enabledLabs.join(', '));
}
diff --git a/src/settings/SettingLevel.ts b/src/settings/SettingLevel.ts
new file mode 100644
index 0000000000..e4703be1a9
--- /dev/null
+++ b/src/settings/SettingLevel.ts
@@ -0,0 +1,29 @@
+/*
+Copyright 2020 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.
+*/
+
+/**
+ * Represents the various setting levels supported by the SettingsStore.
+ */
+export enum SettingLevel {
+ // TODO: [TS] Follow naming convention
+ DEVICE = "device",
+ ROOM_DEVICE = "room-device",
+ ROOM_ACCOUNT = "room-account",
+ ACCOUNT = "account",
+ ROOM = "room",
+ CONFIG = "config",
+ DEFAULT = "default",
+}
diff --git a/src/settings/Settings.js b/src/settings/Settings.ts
similarity index 81%
rename from src/settings/Settings.js
rename to src/settings/Settings.ts
index d5866c9064..1989bd7a34 100644
--- a/src/settings/Settings.js
+++ b/src/settings/Settings.ts
@@ -1,7 +1,6 @@
/*
Copyright 2017 Travis Ralston
-Copyright 2018, 2019 New Vector Ltd.
-Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
+Copyright 2018, 2019, 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -16,9 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import {MatrixClient} from 'matrix-js-sdk';
+import { MatrixClient } from 'matrix-js-sdk/src/client';
-import {_td} from '../languageHandler';
+import { _td } from '../languageHandler';
import {
AudioNotificationsEnabledController,
NotificationBodyEnabledController,
@@ -28,75 +27,89 @@ import CustomStatusController from "./controllers/CustomStatusController";
import ThemeController from './controllers/ThemeController';
import PushToMatrixClientController from './controllers/PushToMatrixClientController';
import ReloadOnChangeController from "./controllers/ReloadOnChangeController";
-import {RightPanelPhases} from "../stores/RightPanelStorePhases";
import FontSizeController from './controllers/FontSizeController';
import SystemFontController from './controllers/SystemFontController';
import UseSystemFontController from './controllers/UseSystemFontController';
+import { SettingLevel } from "./SettingLevel";
+import SettingController from "./controllers/SettingController";
+import { RightPanelPhases } from "../stores/RightPanelStorePhases";
// These are just a bunch of helper arrays to avoid copy/pasting a bunch of times
-const LEVELS_ROOM_SETTINGS = ['device', 'room-device', 'room-account', 'account', 'config'];
-const LEVELS_ROOM_OR_ACCOUNT = ['room-account', 'account'];
-const LEVELS_ROOM_SETTINGS_WITH_ROOM = ['device', 'room-device', 'room-account', 'account', 'config', 'room'];
-const LEVELS_ACCOUNT_SETTINGS = ['device', 'account', 'config'];
-const LEVELS_FEATURE = ['device', 'config'];
-const LEVELS_DEVICE_ONLY_SETTINGS = ['device'];
-const LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG = ['device', 'config'];
+const LEVELS_ROOM_SETTINGS = [
+ SettingLevel.DEVICE,
+ SettingLevel.ROOM_DEVICE,
+ SettingLevel.ROOM_ACCOUNT,
+ SettingLevel.ACCOUNT,
+ SettingLevel.CONFIG,
+];
+const LEVELS_ROOM_OR_ACCOUNT = [
+ SettingLevel.ROOM_ACCOUNT,
+ SettingLevel.ACCOUNT,
+];
+const LEVELS_ROOM_SETTINGS_WITH_ROOM = [
+ SettingLevel.DEVICE,
+ SettingLevel.ROOM_DEVICE,
+ SettingLevel.ROOM_ACCOUNT,
+ SettingLevel.ACCOUNT,
+ SettingLevel.CONFIG,
+ SettingLevel.ROOM,
+];
+const LEVELS_ACCOUNT_SETTINGS = [
+ SettingLevel.DEVICE,
+ SettingLevel.ACCOUNT,
+ SettingLevel.CONFIG,
+];
+const LEVELS_FEATURE = [
+ SettingLevel.DEVICE,
+ SettingLevel.CONFIG,
+];
+const LEVELS_DEVICE_ONLY_SETTINGS = [
+ SettingLevel.DEVICE,
+];
+const LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG = [
+ SettingLevel.DEVICE,
+ SettingLevel.CONFIG,
+];
-export const SETTINGS = {
- // EXAMPLE SETTING:
- // "my-setting": {
- // // Must be set to true for features. Default is 'false'.
- // isFeature: false,
- //
- // // Display names are strongly recommended for clarity.
- // displayName: _td("Cool Name"),
- //
- // // Display name can also be an object for different levels.
- // //displayName: {
- // // "device": _td("Name for when the setting is used at 'device'"),
- // // "room": _td("Name for when the setting is used at 'room'"),
- // // "default": _td("The name for all other levels"),
- // //}
- //
- // // The supported levels are required. Preferably, use the preset arrays
- // // at the top of this file to define this rather than a custom array.
- // supportedLevels: [
- // // The order does not matter.
- //
- // "device", // Affects the current device only
- // "room-device", // Affects the current room on the current device
- // "room-account", // Affects the current room for the current account
- // "account", // Affects the current account
- // "room", // Affects the current room (controlled by room admins)
- // "config", // Affects the current application
- //
- // // "default" is always supported and does not get listed here.
- // ],
- //
- // // Required. Can be any data type. The value specified here should match
- // // the data being stored (ie: if a boolean is used, the setting should
- // // represent a boolean).
- // default: {
- // your: "value",
- // },
- //
- // // Optional settings controller. See SettingsController for more information.
- // controller: new MySettingController(),
- //
- // // Optional flag to make supportedLevels be respected as the order to handle
- // // settings. The first element is treated as "most preferred". The "default"
- // // level is always appended to the end.
- // supportedLevelsAreOrdered: false,
- //
- // // Optional value to invert a boolean setting's value. The string given will
- // // be read as the setting's ID instead of the one provided as the key for the
- // // setting definition. By setting this, the returned value will automatically
- // // be inverted, except for when the default value is returned. Inversion will
- // // occur after the controller is asked for an override. This should be used by
- // // historical settings which we don't want existing user's values be wiped. Do
- // // not use this for new settings.
- // invertedSettingName: "my-negative-setting",
- // },
+export interface ISetting {
+ // Must be set to true for features. Default is 'false'.
+ isFeature?: boolean;
+
+ // Display names are strongly recommended for clarity.
+ // Display name can also be an object for different levels.
+ displayName?: string | {
+ // @ts-ignore - TS wants the key to be a string, but we know better
+ [level: SettingLevel]: string;
+ };
+
+ // The supported levels are required. Preferably, use the preset arrays
+ // at the top of this file to define this rather than a custom array.
+ supportedLevels?: SettingLevel[];
+
+ // Required. Can be any data type. The value specified here should match
+ // the data being stored (ie: if a boolean is used, the setting should
+ // represent a boolean).
+ default: any;
+
+ // Optional settings controller. See SettingsController for more information.
+ controller?: SettingController;
+
+ // Optional flag to make supportedLevels be respected as the order to handle
+ // settings. The first element is treated as "most preferred". The "default"
+ // level is always appended to the end.
+ supportedLevelsAreOrdered?: boolean;
+
+ // Optional value to invert a boolean setting's value. The string given will
+ // be read as the setting's ID instead of the one provided as the key for the
+ // setting definition. By setting this, the returned value will automatically
+ // be inverted, except for when the default value is returned. Inversion will
+ // occur after the controller is asked for an override. This should be used by
+ // historical settings which we don't want existing user's values be wiped. Do
+ // not use this for new settings.
+ invertedSettingName?: string;
+}
+
+export const SETTINGS: {[setting: string]: ISetting} = {
"feature_new_spinner": {
isFeature: true,
displayName: _td("New spinner design"),
@@ -153,11 +166,11 @@ export const SETTINGS = {
default: false,
},
"mjolnirRooms": {
- supportedLevels: ['account'],
+ supportedLevels: [SettingLevel.ACCOUNT],
default: [],
},
"mjolnirPersonalRoom": {
- supportedLevels: ['account'],
+ supportedLevels: [SettingLevel.ACCOUNT],
default: null,
},
"feature_bridge_state": {
@@ -354,24 +367,24 @@ export const SETTINGS = {
},
"breadcrumb_rooms": {
// not really a setting
- supportedLevels: ['account'],
+ supportedLevels: [SettingLevel.ACCOUNT],
default: [],
},
"recent_emoji": {
// not really a setting
- supportedLevels: ['account'],
+ supportedLevels: [SettingLevel.ACCOUNT],
default: [],
},
"room_directory_servers": {
- supportedLevels: ['account'],
+ supportedLevels: [SettingLevel.ACCOUNT],
default: [],
},
"integrationProvisioning": {
- supportedLevels: ['account'],
+ supportedLevels: [SettingLevel.ACCOUNT],
default: true,
},
"allowedWidgets": {
- supportedLevels: ['room-account'],
+ supportedLevels: [SettingLevel.ROOM_ACCOUNT],
default: {}, // none allowed
},
"analyticsOptIn": {
@@ -398,7 +411,7 @@ export const SETTINGS = {
"blacklistUnverifiedDevices": {
// We specifically want to have room-device > device so that users may set a device default
// with a per-room override.
- supportedLevels: ['room-device', 'device'],
+ supportedLevels: [SettingLevel.ROOM_DEVICE, SettingLevel.DEVICE],
supportedLevelsAreOrdered: true,
displayName: {
"default": _td('Never send encrypted messages to unverified sessions from this session'),
@@ -416,7 +429,7 @@ export const SETTINGS = {
default: true,
},
"urlPreviewsEnabled_e2ee": {
- supportedLevels: ['room-device', 'room-account'],
+ supportedLevels: [SettingLevel.ROOM_DEVICE, SettingLevel.ROOM_ACCOUNT],
displayName: {
"room-account": _td("Enable URL previews for this room (only affects you)"),
},
@@ -455,7 +468,7 @@ export const SETTINGS = {
default: false,
},
"PinnedEvents.isOpen": {
- supportedLevels: ['room-device'],
+ supportedLevels: [SettingLevel.ROOM_DEVICE],
default: false,
},
"promptBeforeInviteUnknownUsers": {
@@ -565,7 +578,8 @@ export const SETTINGS = {
"ircDisplayNameWidth": {
// We specifically want to have room-device > device so that users may set a device default
// with a per-room override.
- supportedLevels: ['room-device', 'device'],
+ supportedLevels: [SettingLevel.ROOM_DEVICE, SettingLevel.DEVICE],
+ supportedLevelsAreOrdered: true,
displayName: _td("IRC display name width"),
default: 80,
},
diff --git a/src/settings/SettingsStore.js b/src/settings/SettingsStore.ts
similarity index 78%
rename from src/settings/SettingsStore.js
rename to src/settings/SettingsStore.ts
index 488a15d003..e64de8af16 100644
--- a/src/settings/SettingsStore.js
+++ b/src/settings/SettingsStore.ts
@@ -1,6 +1,6 @@
/*
Copyright 2017 Travis Ralston
-Copyright 2019 New Vector Ltd.
+Copyright 2019, 2020 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.
@@ -22,27 +22,14 @@ import RoomAccountSettingsHandler from "./handlers/RoomAccountSettingsHandler";
import AccountSettingsHandler from "./handlers/AccountSettingsHandler";
import RoomSettingsHandler from "./handlers/RoomSettingsHandler";
import ConfigSettingsHandler from "./handlers/ConfigSettingsHandler";
-import {_t} from '../languageHandler';
+import { _t } from '../languageHandler';
import SdkConfig from "../SdkConfig";
import dis from '../dispatcher/dispatcher';
-import {SETTINGS} from "./Settings";
+import { ISetting, SETTINGS } from "./Settings";
import LocalEchoWrapper from "./handlers/LocalEchoWrapper";
-import {WatchManager} from "./WatchManager";
-
-/**
- * Represents the various setting levels supported by the SettingsStore.
- */
-export const SettingLevel = {
- // Note: This enum is not used in this class or in the Settings file
- // This should always be used elsewhere in the project.
- DEVICE: "device",
- ROOM_DEVICE: "room-device",
- ROOM_ACCOUNT: "room-account",
- ACCOUNT: "account",
- ROOM: "room",
- CONFIG: "config",
- DEFAULT: "default",
-};
+import { WatchManager } from "./WatchManager";
+import { SettingLevel } from "./SettingLevel";
+import SettingsHandler from "./handlers/SettingsHandler";
const defaultWatchManager = new WatchManager();
@@ -61,13 +48,13 @@ for (const key of Object.keys(SETTINGS)) {
}
const LEVEL_HANDLERS = {
- "device": new DeviceSettingsHandler(featureNames, defaultWatchManager),
- "room-device": new RoomDeviceSettingsHandler(defaultWatchManager),
- "room-account": new RoomAccountSettingsHandler(defaultWatchManager),
- "account": new AccountSettingsHandler(defaultWatchManager),
- "room": new RoomSettingsHandler(defaultWatchManager),
- "config": new ConfigSettingsHandler(),
- "default": new DefaultSettingsHandler(defaultSettings, invertedDefaultSettings),
+ [SettingLevel.DEVICE]: new DeviceSettingsHandler(featureNames, defaultWatchManager),
+ [SettingLevel.ROOM_DEVICE]: new RoomDeviceSettingsHandler(defaultWatchManager),
+ [SettingLevel.ROOM_ACCOUNT]: new RoomAccountSettingsHandler(defaultWatchManager),
+ [SettingLevel.ACCOUNT]: new AccountSettingsHandler(defaultWatchManager),
+ [SettingLevel.ROOM]: new RoomSettingsHandler(defaultWatchManager),
+ [SettingLevel.CONFIG]: new ConfigSettingsHandler(),
+ [SettingLevel.DEFAULT]: new DefaultSettingsHandler(defaultSettings, invertedDefaultSettings),
};
// Wrap all the handlers with local echo
@@ -76,20 +63,41 @@ for (const key of Object.keys(LEVEL_HANDLERS)) {
}
const LEVEL_ORDER = [
- 'device', 'room-device', 'room-account', 'account', 'room', 'config', 'default',
+ SettingLevel.DEVICE,
+ SettingLevel.ROOM_DEVICE,
+ SettingLevel.ROOM_ACCOUNT,
+ SettingLevel.ACCOUNT,
+ SettingLevel.ROOM,
+ SettingLevel.CONFIG,
+ SettingLevel.DEFAULT,
];
+export type CallbackFn = (
+ settingName: string,
+ roomId: string,
+ atLevel: SettingLevel,
+ newValAtLevel: any,
+ newVal: any,
+) => void;
+
+interface IHandlerMap {
+ // @ts-ignore - TS wants this to be a string key but we know better
+ [level: SettingLevel]: SettingsHandler;
+}
+
+export type LabsFeatureState = "labs" | "disable" | "enable" | string;
+
/**
* Controls and manages application settings by providing varying levels at which the
* setting value may be specified. The levels are then used to determine what the setting
* value should be given a set of circumstances. The levels, in priority order, are:
- * - "device" - Values are determined by the current device
- * - "room-device" - Values are determined by the current device for a particular room
- * - "room-account" - Values are determined by the current account for a particular room
- * - "account" - Values are determined by the current account
- * - "room" - Values are determined by a particular room (by the room admins)
- * - "config" - Values are determined by the config.json
- * - "default" - Values are determined by the hardcoded defaults
+ * - SettingLevel.DEVICE - Values are determined by the current device
+ * - SettingLevel.ROOM_DEVICE - Values are determined by the current device for a particular room
+ * - SettingLevel.ROOM_ACCOUNT - Values are determined by the current account for a particular room
+ * - SettingLevel.ACCOUNT - Values are determined by the current account
+ * - SettingLevel.ROOM - Values are determined by a particular room (by the room admins)
+ * - SettingLevel.CONFIG - Values are determined by the config.json
+ * - SettingLevel.DEFAULT - Values are determined by the hardcoded defaults
*
* Each level has a different method to storing the setting value. For implementation
* specific details, please see the handlers. The "config" and "default" levels are
@@ -110,11 +118,11 @@ export default class SettingsStore {
// We also maintain a list of monitors which are special watchers: they cause dispatches
// when the setting changes. We track which rooms we're monitoring though to ensure we
// don't duplicate updates on the bus.
- static _watchers = {}; // { callbackRef => { callbackFn } }
- static _monitors = {}; // { settingName => { roomId => callbackRef } }
+ private static watchers = {}; // { callbackRef => { callbackFn } }
+ private static monitors = {}; // { settingName => { roomId => callbackRef } }
// Counter used for generation of watcher IDs
- static _watcherCount = 1;
+ private static watcherCount = 1;
/**
* Watches for changes in a particular setting. This is done without any local echo
@@ -132,7 +140,7 @@ export default class SettingsStore {
* if the change in value is worthwhile enough to react upon.
* @returns {string} A reference to the watcher that was employed.
*/
- static watchSetting(settingName, roomId, callbackFn) {
+ public static watchSetting(settingName: string, roomId: string, callbackFn: CallbackFn): string {
const setting = SETTINGS[settingName];
const originalSettingName = settingName;
if (!setting) throw new Error(`${settingName} is not a setting`);
@@ -141,14 +149,14 @@ export default class SettingsStore {
settingName = setting.invertedSettingName;
}
- const watcherId = `${new Date().getTime()}_${SettingsStore._watcherCount++}_${settingName}_${roomId}`;
+ const watcherId = `${new Date().getTime()}_${SettingsStore.watcherCount++}_${settingName}_${roomId}`;
const localizedCallback = (changedInRoomId, atLevel, newValAtLevel) => {
const newValue = SettingsStore.getValue(originalSettingName);
callbackFn(originalSettingName, changedInRoomId, atLevel, newValAtLevel, newValue);
};
- SettingsStore._watchers[watcherId] = localizedCallback;
+ SettingsStore.watchers[watcherId] = localizedCallback;
defaultWatchManager.watchSetting(settingName, roomId, localizedCallback);
return watcherId;
@@ -160,14 +168,14 @@ export default class SettingsStore {
* @param {string} watcherReference The watcher reference (received from #watchSetting)
* to cancel.
*/
- static unwatchSetting(watcherReference) {
- if (!SettingsStore._watchers[watcherReference]) {
+ public static unwatchSetting(watcherReference: string) {
+ if (!SettingsStore.watchers[watcherReference]) {
console.warn(`Ending non-existent watcher ID ${watcherReference}`);
return;
}
- defaultWatchManager.unwatchSetting(SettingsStore._watchers[watcherReference]);
- delete SettingsStore._watchers[watcherReference];
+ defaultWatchManager.unwatchSetting(SettingsStore.watchers[watcherReference]);
+ delete SettingsStore.watchers[watcherReference];
}
/**
@@ -178,13 +186,13 @@ export default class SettingsStore {
* @param {string} settingName The setting name to monitor.
* @param {String} roomId The room ID to monitor for changes in. Use null for all rooms.
*/
- static monitorSetting(settingName, roomId) {
+ public static monitorSetting(settingName: string, roomId: string) {
roomId = roomId || null; // the thing wants null specifically to work, so appease it.
- if (!this._monitors[settingName]) this._monitors[settingName] = {};
+ if (!this.monitors[settingName]) this.monitors[settingName] = {};
const registerWatcher = () => {
- this._monitors[settingName][roomId] = SettingsStore.watchSetting(
+ this.monitors[settingName][roomId] = SettingsStore.watchSetting(
settingName, roomId, (settingName, inRoomId, level, newValueAtLevel, newValue) => {
dis.dispatch({
action: 'setting_updated',
@@ -198,16 +206,16 @@ export default class SettingsStore {
);
};
- const hasRoom = Object.keys(this._monitors[settingName]).find((r) => r === roomId || r === null);
+ const hasRoom = Object.keys(this.monitors[settingName]).find((r) => r === roomId || r === null);
if (!hasRoom) {
registerWatcher();
} else {
if (roomId === null) {
// Unregister all existing watchers and register the new one
- for (const roomId of Object.keys(this._monitors[settingName])) {
- SettingsStore.unwatchSetting(this._monitors[settingName][roomId]);
+ for (const roomId of Object.keys(this.monitors[settingName])) {
+ SettingsStore.unwatchSetting(this.monitors[settingName][roomId]);
}
- this._monitors[settingName] = {};
+ this.monitors[settingName] = {};
registerWatcher();
} // else a watcher is already registered for the room, so don't bother registering it again
}
@@ -216,11 +224,11 @@ export default class SettingsStore {
/**
* Gets the translated display name for a given setting
* @param {string} settingName The setting to look up.
- * @param {"device"|"room-device"|"room-account"|"account"|"room"|"config"|"default"} atLevel
+ * @param {SettingLevel} atLevel
* The level to get the display name for; Defaults to 'default'.
* @return {String} The display name for the setting, or null if not found.
*/
- static getDisplayName(settingName, atLevel = "default") {
+ public static getDisplayName(settingName: string, atLevel = SettingLevel.DEFAULT) {
if (!SETTINGS[settingName] || !SETTINGS[settingName].displayName) return null;
let displayName = SETTINGS[settingName].displayName;
@@ -229,20 +237,20 @@ export default class SettingsStore {
else displayName = displayName["default"];
}
- return _t(displayName);
+ return _t(displayName as string);
}
/**
* Returns a list of all available labs feature names
* @returns {string[]} The list of available feature names
*/
- static getLabsFeatures() {
+ public static getLabsFeatures(): string[] {
const possibleFeatures = Object.keys(SETTINGS).filter((s) => SettingsStore.isFeature(s));
const enableLabs = SdkConfig.get()["enableLabs"];
if (enableLabs) return possibleFeatures;
- return possibleFeatures.filter((s) => SettingsStore._getFeatureState(s) === "labs");
+ return possibleFeatures.filter((s) => SettingsStore.getFeatureState(s) === "labs");
}
/**
@@ -250,7 +258,7 @@ export default class SettingsStore {
* @param {string} settingName The setting to look up.
* @return {boolean} True if the setting is a feature.
*/
- static isFeature(settingName) {
+ public static isFeature(settingName: string) {
if (!SETTINGS[settingName]) return false;
return SETTINGS[settingName].isFeature;
}
@@ -262,7 +270,7 @@ export default class SettingsStore {
* @param {String} roomId The optional room ID to validate in, may be null.
* @return {boolean} True if the feature is enabled, false otherwise
*/
- static isFeatureEnabled(settingName, roomId = null) {
+ public static isFeatureEnabled(settingName: string, roomId: string = null) {
if (!SettingsStore.isFeature(settingName)) {
throw new Error("Setting " + settingName + " is not a feature");
}
@@ -276,7 +284,7 @@ export default class SettingsStore {
* @param {boolean} value True to enable the feature, false otherwise.
* @returns {Promise} Resolves when the setting has been set.
*/
- static setFeatureEnabled(settingName, value) {
+ public static setFeatureEnabled(settingName: string, value: any): Promise {
// Verify that the setting is actually a setting
if (!SETTINGS[settingName]) {
throw new Error("Setting '" + settingName + "' does not appear to be a setting.");
@@ -285,7 +293,7 @@ export default class SettingsStore {
throw new Error("Setting " + settingName + " is not a feature");
}
- return SettingsStore.setValue(settingName, null, "device", value);
+ return SettingsStore.setValue(settingName, null, SettingLevel.DEVICE, value);
}
/**
@@ -296,7 +304,7 @@ export default class SettingsStore {
* @param {boolean} excludeDefault True to disable using the default value.
* @return {*} The value, or null if not found
*/
- static getValue(settingName, roomId = null, excludeDefault = false) {
+ public static getValue(settingName: string, roomId: string = null, excludeDefault = false): any {
// Verify that the setting is actually a setting
if (!SETTINGS[settingName]) {
throw new Error("Setting '" + settingName + "' does not appear to be a setting.");
@@ -310,7 +318,7 @@ export default class SettingsStore {
/**
* Gets a setting's value at a particular level, ignoring all levels that are more specific.
- * @param {"device"|"room-device"|"room-account"|"account"|"room"|"config"|"default"} level The
+ * @param {SettingLevel|"config"|"default"} level The
* level to look at.
* @param {string} settingName The name of the setting to read.
* @param {String} roomId The room ID to read the setting value in, may be null.
@@ -319,7 +327,13 @@ export default class SettingsStore {
* @param {boolean} excludeDefault True to disable using the default value.
* @return {*} The value, or null if not found.
*/
- static getValueAt(level, settingName, roomId = null, explicit = false, excludeDefault = false) {
+ public static getValueAt(
+ level: SettingLevel,
+ settingName: string,
+ roomId: string = null,
+ explicit = false,
+ excludeDefault = false,
+ ): any {
// Verify that the setting is actually a setting
const setting = SETTINGS[settingName];
if (!setting) {
@@ -327,19 +341,19 @@ export default class SettingsStore {
}
const levelOrder = (setting.supportedLevelsAreOrdered ? setting.supportedLevels : LEVEL_ORDER);
- if (!levelOrder.includes("default")) levelOrder.push("default"); // always include default
+ if (!levelOrder.includes(SettingLevel.DEFAULT)) levelOrder.push(SettingLevel.DEFAULT); // always include default
const minIndex = levelOrder.indexOf(level);
if (minIndex === -1) throw new Error("Level " + level + " is not prioritized");
if (SettingsStore.isFeature(settingName)) {
- const configValue = SettingsStore._getFeatureState(settingName);
+ const configValue = SettingsStore.getFeatureState(settingName);
if (configValue === "enable") return true;
if (configValue === "disable") return false;
// else let it fall through the default process
}
- const handlers = SettingsStore._getHandlers(settingName);
+ const handlers = SettingsStore.getHandlers(settingName);
// Check if we need to invert the setting at all. Do this after we get the setting
// handlers though, otherwise we'll fail to read the value.
@@ -351,10 +365,10 @@ export default class SettingsStore {
if (explicit) {
const handler = handlers[level];
if (!handler) {
- return SettingsStore._getFinalValue(setting, level, roomId, null, null);
+ return SettingsStore.getFinalValue(setting, level, roomId, null, null);
}
const value = handler.getValue(settingName, roomId);
- return SettingsStore._getFinalValue(setting, level, roomId, value, level);
+ return SettingsStore.getFinalValue(setting, level, roomId, value, level);
}
for (let i = minIndex; i < levelOrder.length; i++) {
@@ -364,10 +378,10 @@ export default class SettingsStore {
const value = handler.getValue(settingName, roomId);
if (value === null || value === undefined) continue;
- return SettingsStore._getFinalValue(setting, level, roomId, value, levelOrder[i]);
+ return SettingsStore.getFinalValue(setting, level, roomId, value, levelOrder[i]);
}
- return SettingsStore._getFinalValue(setting, level, roomId, null, null);
+ return SettingsStore.getFinalValue(setting, level, roomId, null, null);
}
/**
@@ -376,7 +390,7 @@ export default class SettingsStore {
* @param {String} roomId The room ID to read the setting value in, may be null.
* @return {*} The default value
*/
- static getDefaultValue(settingName) {
+ public static getDefaultValue(settingName: string): any {
// Verify that the setting is actually a setting
if (!SETTINGS[settingName]) {
throw new Error("Setting '" + settingName + "' does not appear to be a setting.");
@@ -385,7 +399,13 @@ export default class SettingsStore {
return SETTINGS[settingName].default;
}
- static _getFinalValue(setting, level, roomId, calculatedValue, calculatedAtLevel) {
+ private static getFinalValue(
+ setting: ISetting,
+ level: SettingLevel,
+ roomId: string,
+ calculatedValue: any,
+ calculatedAtLevel: SettingLevel,
+ ): any {
let resultingValue = calculatedValue;
if (setting.controller) {
@@ -404,20 +424,21 @@ export default class SettingsStore {
* to indicate that the level should no longer have an override.
* @param {string} settingName The name of the setting to change.
* @param {String} roomId The room ID to change the value in, may be null.
- * @param {"device"|"room-device"|"room-account"|"account"|"room"} level The level
+ * @param {SettingLevel} level The level
* to change the value at.
* @param {*} value The new value of the setting, may be null.
* @return {Promise} Resolves when the setting has been changed.
*/
+
/* eslint-enable valid-jsdoc */
- static async setValue(settingName, roomId, level, value) {
+ public static async setValue(settingName: string, roomId: string, level: SettingLevel, value: any): Promise {
// Verify that the setting is actually a setting
const setting = SETTINGS[settingName];
if (!setting) {
throw new Error("Setting '" + settingName + "' does not appear to be a setting.");
}
- const handler = SettingsStore._getHandler(settingName, level);
+ const handler = SettingsStore.getHandler(settingName, level);
if (!handler) {
throw new Error("Setting " + settingName + " does not have a handler for " + level);
}
@@ -449,28 +470,28 @@ export default class SettingsStore {
* set for a particular room, otherwise it should be supplied.
* @param {string} settingName The name of the setting to check.
* @param {String} roomId The room ID to check in, may be null.
- * @param {"device"|"room-device"|"room-account"|"account"|"room"} level The level to
+ * @param {SettingLevel} level The level to
* check at.
* @return {boolean} True if the user may set the setting, false otherwise.
*/
- static canSetValue(settingName, roomId, level) {
+ public static canSetValue(settingName: string, roomId: string, level: SettingLevel): boolean {
// Verify that the setting is actually a setting
if (!SETTINGS[settingName]) {
throw new Error("Setting '" + settingName + "' does not appear to be a setting.");
}
- const handler = SettingsStore._getHandler(settingName, level);
+ const handler = SettingsStore.getHandler(settingName, level);
if (!handler) return false;
return handler.canSetValue(settingName, roomId);
}
/**
* Determines if the given level is supported on this device.
- * @param {"device"|"room-device"|"room-account"|"account"|"room"} level The level
+ * @param {SettingLevel} level The level
* to check the feasibility of.
* @return {boolean} True if the level is supported, false otherwise.
*/
- static isLevelSupported(level) {
+ public static isLevelSupported(level: SettingLevel): boolean {
if (!LEVEL_HANDLERS[level]) return false;
return LEVEL_HANDLERS[level].isSupported();
}
@@ -482,7 +503,7 @@ export default class SettingsStore {
* @param {string} realSettingName The setting name to try and read.
* @param {string} roomId Optional room ID to test the setting in.
*/
- static debugSetting(realSettingName, roomId) {
+ public static debugSetting(realSettingName: string, roomId: string) {
console.log(`--- DEBUG ${realSettingName}`);
// Note: we intentionally use JSON.stringify here to avoid the console masking the
@@ -570,13 +591,13 @@ export default class SettingsStore {
console.log(`--- END DEBUG`);
}
- static _getHandler(settingName, level) {
- const handlers = SettingsStore._getHandlers(settingName);
+ private static getHandler(settingName: string, level: SettingLevel): SettingsHandler {
+ const handlers = SettingsStore.getHandlers(settingName);
if (!handlers[level]) return null;
return handlers[level];
}
- static _getHandlers(settingName) {
+ private static getHandlers(settingName: string): IHandlerMap {
if (!SETTINGS[settingName]) return {};
const handlers = {};
@@ -591,7 +612,7 @@ export default class SettingsStore {
return handlers;
}
- static _getFeatureState(settingName) {
+ private static getFeatureState(settingName: string): LabsFeatureState {
const featuresConfig = SdkConfig.get()['features'];
const enableLabs = SdkConfig.get()['enableLabs']; // we'll honour the old flag
@@ -611,4 +632,4 @@ export default class SettingsStore {
}
// For debugging purposes
-global.mxSettingsStore = SettingsStore;
+window.mxSettingsStore = SettingsStore;
diff --git a/src/settings/WatchManager.js b/src/settings/WatchManager.ts
similarity index 56%
rename from src/settings/WatchManager.js
rename to src/settings/WatchManager.ts
index 3f54ca929e..d51439459c 100644
--- a/src/settings/WatchManager.js
+++ b/src/settings/WatchManager.ts
@@ -14,41 +14,52 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+import { SettingLevel } from "./SettingLevel";
+
+export type CallbackFn = (changedInRoomId: string, atLevel: SettingLevel, newValAtLevel: any) => void;
+
+const IRRELEVANT_ROOM = Symbol("any room");
+
+interface RoomWatcherMap {
+ // @ts-ignore - TS wants string-only keys but we know better - https://github.com/Microsoft/TypeScript/issues/1863
+ [roomId: string | symbol]: CallbackFn[];
+}
+
/**
* Generalized management class for dealing with watchers on a per-handler (per-level)
* basis without duplicating code. Handlers are expected to push updates through this
* class, which are then proxied outwards to any applicable watchers.
*/
export class WatchManager {
- _watchers = {}; // { settingName: { roomId: callbackFns[] } }
+ private watchers: {[settingName: string]: RoomWatcherMap} = {};
// Proxy for handlers to delegate changes to this manager
- watchSetting(settingName, roomId, cb) {
- if (!this._watchers[settingName]) this._watchers[settingName] = {};
- if (!this._watchers[settingName][roomId]) this._watchers[settingName][roomId] = [];
- this._watchers[settingName][roomId].push(cb);
+ public watchSetting(settingName: string, roomId: string | null, cb: CallbackFn) {
+ if (!this.watchers[settingName]) this.watchers[settingName] = {};
+ if (!this.watchers[settingName][roomId]) this.watchers[settingName][roomId] = [];
+ this.watchers[settingName][roomId].push(cb);
}
// Proxy for handlers to delegate changes to this manager
- unwatchSetting(cb) {
- for (const settingName of Object.keys(this._watchers)) {
- for (const roomId of Object.keys(this._watchers[settingName])) {
+ public unwatchSetting(cb: CallbackFn) {
+ for (const settingName of Object.keys(this.watchers)) {
+ for (const roomId of Object.keys(this.watchers[settingName])) {
let idx;
- while ((idx = this._watchers[settingName][roomId].indexOf(cb)) !== -1) {
- this._watchers[settingName][roomId].splice(idx, 1);
+ while ((idx = this.watchers[settingName][roomId].indexOf(cb)) !== -1) {
+ this.watchers[settingName][roomId].splice(idx, 1);
}
}
}
}
- notifyUpdate(settingName, inRoomId, atLevel, newValueAtLevel) {
+ public notifyUpdate(settingName: string, inRoomId: string | null, atLevel: SettingLevel, newValueAtLevel: any) {
// Dev note: We could avoid raising changes for ultimately inconsequential changes, but
// we also don't have a reliable way to get the old value of a setting. Instead, we'll just
// let it fall through regardless and let the receiver dedupe if they want to.
- if (!this._watchers[settingName]) return;
+ if (!this.watchers[settingName]) return;
- const roomWatchers = this._watchers[settingName];
+ const roomWatchers = this.watchers[settingName];
const callbacks = [];
if (inRoomId !== null && roomWatchers[inRoomId]) {
@@ -59,8 +70,8 @@ export class WatchManager {
// Fire updates to all the individual room watchers too, as they probably
// care about the change higher up.
callbacks.push(...Object.values(roomWatchers).reduce((r, a) => [...r, ...a], []));
- } else if (roomWatchers[null]) {
- callbacks.push(...roomWatchers[null]);
+ } else if (roomWatchers[IRRELEVANT_ROOM]) {
+ callbacks.push(...roomWatchers[IRRELEVANT_ROOM]);
}
for (const callback of callbacks) {
diff --git a/src/settings/controllers/CustomStatusController.js b/src/settings/controllers/CustomStatusController.ts
similarity index 84%
rename from src/settings/controllers/CustomStatusController.js
rename to src/settings/controllers/CustomStatusController.ts
index 031387bb6a..c7dfad0b3b 100644
--- a/src/settings/controllers/CustomStatusController.js
+++ b/src/settings/controllers/CustomStatusController.ts
@@ -1,5 +1,5 @@
/*
-Copyright 2019 New Vector Ltd
+Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -16,9 +16,10 @@ limitations under the License.
import SettingController from "./SettingController";
import dis from "../../dispatcher/dispatcher";
+import { SettingLevel } from "../SettingLevel";
export default class CustomStatusController extends SettingController {
- onChange(level, roomId, newValue) {
+ public onChange(level: SettingLevel, roomId: string, newValue: any) {
// Dispatch setting change so that some components that are still visible when the
// Settings page is open (such as RoomTiles) can reflect the change.
dis.dispatch({
diff --git a/src/settings/controllers/FontSizeController.ts b/src/settings/controllers/FontSizeController.ts
index 6440fd32fe..b86d596040 100644
--- a/src/settings/controllers/FontSizeController.ts
+++ b/src/settings/controllers/FontSizeController.ts
@@ -18,13 +18,14 @@ import SettingController from "./SettingController";
import dis from "../../dispatcher/dispatcher";
import { UpdateFontSizePayload } from "../../dispatcher/payloads/UpdateFontSizePayload";
import { Action } from "../../dispatcher/actions";
+import { SettingLevel } from "../SettingLevel";
export default class FontSizeController extends SettingController {
constructor() {
super();
}
- onChange(level, roomId, newValue) {
+ public onChange(level: SettingLevel, roomId: string, newValue: any) {
// Dispatch font size change so that everything open responds to the change.
dis.dispatch({
action: Action.UpdateFontSize,
diff --git a/src/settings/controllers/NotificationControllers.js b/src/settings/controllers/NotificationControllers.ts
similarity index 77%
rename from src/settings/controllers/NotificationControllers.js
rename to src/settings/controllers/NotificationControllers.ts
index e38a5bded1..fc50af6096 100644
--- a/src/settings/controllers/NotificationControllers.js
+++ b/src/settings/controllers/NotificationControllers.ts
@@ -1,5 +1,6 @@
/*
Copyright 2017 Travis Ralston
+Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -16,13 +17,14 @@ limitations under the License.
import SettingController from "./SettingController";
import {MatrixClientPeg} from '../../MatrixClientPeg';
+import { SettingLevel } from "../SettingLevel";
// XXX: This feels wrong.
import {PushProcessor} from "matrix-js-sdk/src/pushprocessor";
// .m.rule.master being enabled means all events match that push rule
// default action on this rule is dont_notify, but it could be something else
-function isPushNotifyDisabled() {
+function isPushNotifyDisabled(): boolean {
// Return the value of the master push rule as a default
const processor = new PushProcessor(MatrixClientPeg.get());
const masterRule = processor.getPushRuleById(".m.rule.master");
@@ -36,14 +38,20 @@ function isPushNotifyDisabled() {
return masterRule.enabled && !masterRule.actions.includes("notify");
}
-function getNotifier() {
+function getNotifier(): any { // TODO: [TS] Formal type that doesn't cause a cyclical reference.
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
let Notifier = require('../../Notifier'); // avoids cyclical references
if (Notifier.default) Notifier = Notifier.default; // correct for webpack require() weirdness
return Notifier;
}
export class NotificationsEnabledController extends SettingController {
- getValueOverride(level, roomId, calculatedValue, calculatedAtLevel) {
+ public getValueOverride(
+ level: SettingLevel,
+ roomId: string,
+ calculatedValue: any,
+ calculatedAtLevel: SettingLevel,
+ ): any {
if (!getNotifier().isPossible()) return false;
if (calculatedValue === null || calculatedAtLevel === "default") {
@@ -53,7 +61,7 @@ export class NotificationsEnabledController extends SettingController {
return calculatedValue;
}
- onChange(level, roomId, newValue) {
+ public onChange(level: SettingLevel, roomId: string, newValue: any) {
if (getNotifier().supportsDesktopNotifications()) {
getNotifier().setEnabled(newValue);
}
@@ -61,7 +69,7 @@ export class NotificationsEnabledController extends SettingController {
}
export class NotificationBodyEnabledController extends SettingController {
- getValueOverride(level, roomId, calculatedValue) {
+ public getValueOverride(level: SettingLevel, roomId: string, calculatedValue: any): any {
if (!getNotifier().isPossible()) return false;
if (calculatedValue === null) {
@@ -73,7 +81,7 @@ export class NotificationBodyEnabledController extends SettingController {
}
export class AudioNotificationsEnabledController extends SettingController {
- getValueOverride(level, roomId, calculatedValue) {
+ public getValueOverride(level: SettingLevel, roomId: string, calculatedValue: any): any {
if (!getNotifier().isPossible()) return false;
// Note: Audio notifications are *not* enabled by default.
diff --git a/src/settings/controllers/PushToMatrixClientController.js b/src/settings/controllers/PushToMatrixClientController.ts
similarity index 67%
rename from src/settings/controllers/PushToMatrixClientController.js
rename to src/settings/controllers/PushToMatrixClientController.ts
index b7c285227f..8fbf7eb34c 100644
--- a/src/settings/controllers/PushToMatrixClientController.js
+++ b/src/settings/controllers/PushToMatrixClientController.ts
@@ -15,23 +15,20 @@ limitations under the License.
*/
import { MatrixClientPeg } from '../../MatrixClientPeg';
+import { SettingLevel } from "../SettingLevel";
+import SettingController from "./SettingController";
/**
* When the value changes, call a setter function on the matrix client with the new value
*/
-export default class PushToMatrixClientController {
- constructor(setter, inverse) {
- this._setter = setter;
- this._inverse = inverse;
+export default class PushToMatrixClientController extends SettingController {
+ constructor(private setter: Function, private inverse: boolean) {
+ super();
}
- getValueOverride(level, roomId, calculatedValue, calculatedAtLevel) {
- return null; // no override
- }
-
- onChange(level, roomId, newValue) {
+ public onChange(level: SettingLevel, roomId: string, newValue: any) {
// XXX does this work? This surely isn't necessarily the effective value,
// but it's what NotificationsEnabledController does...
- this._setter.call(MatrixClientPeg.get(), this._inverse ? !newValue : newValue);
+ this.setter.call(MatrixClientPeg.get(), this.inverse ? !newValue : newValue);
}
}
diff --git a/src/settings/controllers/ReloadOnChangeController.js b/src/settings/controllers/ReloadOnChangeController.ts
similarity index 80%
rename from src/settings/controllers/ReloadOnChangeController.js
rename to src/settings/controllers/ReloadOnChangeController.ts
index eadaee89ca..12bc23ef6c 100644
--- a/src/settings/controllers/ReloadOnChangeController.js
+++ b/src/settings/controllers/ReloadOnChangeController.ts
@@ -1,5 +1,5 @@
/*
-Copyright 2019 The Matrix.org Foundation C.I.C.
+Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -16,9 +16,10 @@ limitations under the License.
import SettingController from "./SettingController";
import PlatformPeg from "../../PlatformPeg";
+import { SettingLevel } from "../SettingLevel";
export default class ReloadOnChangeController extends SettingController {
- onChange(level, roomId, newValue) {
+ public onChange(level: SettingLevel, roomId: string, newValue: any) {
PlatformPeg.get().reload();
}
}
diff --git a/src/settings/controllers/SettingController.js b/src/settings/controllers/SettingController.ts
similarity index 79%
rename from src/settings/controllers/SettingController.js
rename to src/settings/controllers/SettingController.ts
index a7d0ccf21a..d90eba1e9e 100644
--- a/src/settings/controllers/SettingController.js
+++ b/src/settings/controllers/SettingController.ts
@@ -1,5 +1,6 @@
/*
Copyright 2017 Travis Ralston
+Copyright 2020 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.
@@ -14,6 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+import { SettingLevel } from "../SettingLevel";
+
/**
* Represents a controller for individual settings to alter the reading behaviour
* based upon environmental conditions, or to react to changes and therefore update
@@ -22,7 +25,7 @@ limitations under the License.
* This is not intended to replace the functionality of a SettingsHandler, it is only
* intended to handle environmental factors for specific settings.
*/
-export default class SettingController {
+export default abstract class SettingController {
/**
* Gets the overridden value for the setting, if any. This must return null if the
* value is not to be overridden, otherwise it must return the new value.
@@ -30,11 +33,16 @@ export default class SettingController {
* @param {String} roomId The room ID, may be null.
* @param {*} calculatedValue The value that the handlers think the setting should be,
* may be null.
- * @param {string} calculatedAtLevel The level for which the calculated value was
+ * @param {SettingLevel} calculatedAtLevel The level for which the calculated value was
* calculated at. May be null.
* @return {*} The value that should be used, or null if no override is applicable.
*/
- getValueOverride(level, roomId, calculatedValue, calculatedAtLevel) {
+ public getValueOverride(
+ level: SettingLevel,
+ roomId: string,
+ calculatedValue: any,
+ calculatedAtLevel: SettingLevel,
+ ): any {
return null; // no override
}
@@ -44,7 +52,7 @@ export default class SettingController {
* @param {String} roomId The room ID, may be null.
* @param {*} newValue The new value for the setting, may be null.
*/
- onChange(level, roomId, newValue) {
+ public onChange(level: SettingLevel, roomId: string, newValue: any) {
// do nothing by default
}
}
diff --git a/src/settings/controllers/SystemFontController.ts b/src/settings/controllers/SystemFontController.ts
index 4f591efc17..f38dbdbbf6 100644
--- a/src/settings/controllers/SystemFontController.ts
+++ b/src/settings/controllers/SystemFontController.ts
@@ -19,13 +19,14 @@ import SettingsStore from "../SettingsStore";
import dis from "../../dispatcher/dispatcher";
import { UpdateSystemFontPayload } from "../../dispatcher/payloads/UpdateSystemFontPayload";
import { Action } from "../../dispatcher/actions";
+import { SettingLevel } from "../SettingLevel";
export default class SystemFontController extends SettingController {
constructor() {
super();
}
- onChange(level, roomId, newValue) {
+ public onChange(level: SettingLevel, roomId: string, newValue: any) {
// Dispatch font size change so that everything open responds to the change.
dis.dispatch({
action: Action.UpdateSystemFont,
diff --git a/src/settings/controllers/ThemeController.js b/src/settings/controllers/ThemeController.ts
similarity index 79%
rename from src/settings/controllers/ThemeController.js
rename to src/settings/controllers/ThemeController.ts
index 4098a5ca3e..15dd64c901 100644
--- a/src/settings/controllers/ThemeController.js
+++ b/src/settings/controllers/ThemeController.ts
@@ -1,6 +1,6 @@
/*
-Copyright 2019 New Vector Ltd
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
+Copyright 2019, 2020 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.
@@ -17,11 +17,17 @@ limitations under the License.
import SettingController from "./SettingController";
import {DEFAULT_THEME, enumerateThemes} from "../../theme";
+import { SettingLevel } from "../SettingLevel";
export default class ThemeController extends SettingController {
- static isLogin = false;
+ public static isLogin = false;
- getValueOverride(level, roomId, calculatedValue, calculatedAtLevel) {
+ public getValueOverride(
+ level: SettingLevel,
+ roomId: string,
+ calculatedValue: any,
+ calculatedAtLevel: SettingLevel,
+ ): any {
if (!calculatedValue) return null; // Don't override null themes
if (ThemeController.isLogin) return 'light';
diff --git a/src/settings/controllers/UseSystemFontController.ts b/src/settings/controllers/UseSystemFontController.ts
index d598b25962..0b6acc590a 100644
--- a/src/settings/controllers/UseSystemFontController.ts
+++ b/src/settings/controllers/UseSystemFontController.ts
@@ -19,13 +19,14 @@ import SettingsStore from "../SettingsStore";
import dis from "../../dispatcher/dispatcher";
import { UpdateSystemFontPayload } from "../../dispatcher/payloads/UpdateSystemFontPayload";
import { Action } from "../../dispatcher/actions";
+import { SettingLevel } from "../SettingLevel";
export default class UseSystemFontController extends SettingController {
constructor() {
super();
}
- onChange(level, roomId, newValue) {
+ public onChange(level: SettingLevel, roomId: string, newValue: any) {
// Dispatch font size change so that everything open responds to the change.
dis.dispatch({
action: Action.UpdateSystemFont,
diff --git a/src/settings/handlers/AccountSettingsHandler.js b/src/settings/handlers/AccountSettingsHandler.ts
similarity index 71%
rename from src/settings/handlers/AccountSettingsHandler.js
rename to src/settings/handlers/AccountSettingsHandler.ts
index 4048d8ddea..53180aeba8 100644
--- a/src/settings/handlers/AccountSettingsHandler.js
+++ b/src/settings/handlers/AccountSettingsHandler.ts
@@ -1,6 +1,6 @@
/*
Copyright 2017 Travis Ralston
-Copyright 2019 New Vector Ltd.
+Copyright 2019, 2020 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.
@@ -17,14 +17,16 @@ limitations under the License.
import {MatrixClientPeg} from '../../MatrixClientPeg';
import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler";
-import {SettingLevel} from "../SettingsStore";
import {objectClone, objectKeyChanges} from "../../utils/objects";
+import {SettingLevel} from "../SettingLevel";
+import { WatchManager } from "../WatchManager";
+import { MatrixClient } from "matrix-js-sdk/src/client";
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
const BREADCRUMBS_LEGACY_EVENT_TYPE = "im.vector.riot.breadcrumb_rooms";
const BREADCRUMBS_EVENT_TYPE = "im.vector.setting.breadcrumbs";
const BREADCRUMBS_EVENT_TYPES = [BREADCRUMBS_LEGACY_EVENT_TYPE, BREADCRUMBS_EVENT_TYPE];
const RECENT_EMOJI_EVENT_TYPE = "io.element.recent_emoji";
-
const INTEG_PROVISIONING_EVENT_TYPE = "im.vector.setting.integration_provisioning";
/**
@@ -32,22 +34,19 @@ const INTEG_PROVISIONING_EVENT_TYPE = "im.vector.setting.integration_provisionin
* This handler does not make use of the roomId parameter.
*/
export default class AccountSettingsHandler extends MatrixClientBackedSettingsHandler {
- constructor(watchManager) {
+ constructor(private watchers: WatchManager) {
super();
-
- this._watchers = watchManager;
- this._onAccountData = this._onAccountData.bind(this);
}
- initMatrixClient(oldClient, newClient) {
+ public initMatrixClient(oldClient: MatrixClient, newClient: MatrixClient) {
if (oldClient) {
- oldClient.removeListener("accountData", this._onAccountData);
+ oldClient.removeListener("accountData", this.onAccountData);
}
- newClient.on("accountData", this._onAccountData);
+ newClient.on("accountData", this.onAccountData);
}
- _onAccountData(event, prevEvent) {
+ private onAccountData = (event: MatrixEvent, prevEvent: MatrixEvent) => {
if (event.getType() === "org.matrix.preview_urls") {
let val = event.getContent()['disable'];
if (typeof(val) !== "boolean") {
@@ -56,30 +55,30 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
val = !val;
}
- this._watchers.notifyUpdate("urlPreviewsEnabled", null, SettingLevel.ACCOUNT, val);
+ this.watchers.notifyUpdate("urlPreviewsEnabled", null, SettingLevel.ACCOUNT, val);
} else if (event.getType() === "im.vector.web.settings") {
// Figure out what changed and fire those updates
const prevContent = prevEvent ? prevEvent.getContent() : {};
const changedSettings = objectKeyChanges(prevContent, event.getContent());
for (const settingName of changedSettings) {
const val = event.getContent()[settingName];
- this._watchers.notifyUpdate(settingName, null, SettingLevel.ACCOUNT, val);
+ this.watchers.notifyUpdate(settingName, null, SettingLevel.ACCOUNT, val);
}
} else if (BREADCRUMBS_EVENT_TYPES.includes(event.getType())) {
- this._notifyBreadcrumbsUpdate(event);
+ this.notifyBreadcrumbsUpdate(event);
} else if (event.getType() === INTEG_PROVISIONING_EVENT_TYPE) {
const val = event.getContent()['enabled'];
- this._watchers.notifyUpdate("integrationProvisioning", null, SettingLevel.ACCOUNT, val);
+ this.watchers.notifyUpdate("integrationProvisioning", null, SettingLevel.ACCOUNT, val);
} else if (event.getType() === RECENT_EMOJI_EVENT_TYPE) {
const val = event.getContent()['enabled'];
- this._watchers.notifyUpdate("recent_emoji", null, SettingLevel.ACCOUNT, val);
+ this.watchers.notifyUpdate("recent_emoji", null, SettingLevel.ACCOUNT, val);
}
}
- getValue(settingName, roomId) {
+ public getValue(settingName: string, roomId: string): any {
// Special case URL previews
if (settingName === "urlPreviewsEnabled") {
- const content = this._getSettings("org.matrix.preview_urls") || {};
+ const content = this.getSettings("org.matrix.preview_urls") || {};
// Check to make sure that we actually got a boolean
if (typeof(content['disable']) !== "boolean") return null;
@@ -88,9 +87,9 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
// Special case for breadcrumbs
if (settingName === "breadcrumb_rooms") {
- let content = this._getSettings(BREADCRUMBS_EVENT_TYPE);
+ let content = this.getSettings(BREADCRUMBS_EVENT_TYPE);
if (!content || !content['recent_rooms']) {
- content = this._getSettings(BREADCRUMBS_LEGACY_EVENT_TYPE);
+ content = this.getSettings(BREADCRUMBS_LEGACY_EVENT_TYPE);
// This is a bit of a hack, but it makes things slightly easier
if (content) content['recent_rooms'] = content['rooms'];
@@ -101,17 +100,17 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
// Special case recent emoji
if (settingName === "recent_emoji") {
- const content = this._getSettings(RECENT_EMOJI_EVENT_TYPE);
+ const content = this.getSettings(RECENT_EMOJI_EVENT_TYPE);
return content ? content["recent_emoji"] : null;
}
// Special case integration manager provisioning
if (settingName === "integrationProvisioning") {
- const content = this._getSettings(INTEG_PROVISIONING_EVENT_TYPE);
+ const content = this.getSettings(INTEG_PROVISIONING_EVENT_TYPE);
return content ? content['enabled'] : null;
}
- const settings = this._getSettings() || {};
+ const settings = this.getSettings() || {};
let preferredValue = settings[settingName];
if (preferredValue === null || preferredValue === undefined) {
@@ -124,10 +123,10 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
return preferredValue;
}
- setValue(settingName, roomId, newValue) {
+ public setValue(settingName: string, roomId: string, newValue: any): Promise {
// Special case URL previews
if (settingName === "urlPreviewsEnabled") {
- const content = this._getSettings("org.matrix.preview_urls") || {};
+ const content = this.getSettings("org.matrix.preview_urls") || {};
content['disable'] = !newValue;
return MatrixClientPeg.get().setAccountData("org.matrix.preview_urls", content);
}
@@ -135,9 +134,9 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
// Special case for breadcrumbs
if (settingName === "breadcrumb_rooms") {
// We read the value first just to make sure we preserve whatever random keys might be present.
- let content = this._getSettings(BREADCRUMBS_EVENT_TYPE);
+ let content = this.getSettings(BREADCRUMBS_EVENT_TYPE);
if (!content || !content['recent_rooms']) {
- content = this._getSettings(BREADCRUMBS_LEGACY_EVENT_TYPE);
+ content = this.getSettings(BREADCRUMBS_LEGACY_EVENT_TYPE);
}
if (!content) content = {}; // If we still don't have content, make some
@@ -147,33 +146,33 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
// Special case recent emoji
if (settingName === "recent_emoji") {
- const content = this._getSettings(RECENT_EMOJI_EVENT_TYPE) || {};
+ const content = this.getSettings(RECENT_EMOJI_EVENT_TYPE) || {};
content["recent_emoji"] = newValue;
return MatrixClientPeg.get().setAccountData(RECENT_EMOJI_EVENT_TYPE, content);
}
// Special case integration manager provisioning
if (settingName === "integrationProvisioning") {
- const content = this._getSettings(INTEG_PROVISIONING_EVENT_TYPE) || {};
+ const content = this.getSettings(INTEG_PROVISIONING_EVENT_TYPE) || {};
content['enabled'] = newValue;
return MatrixClientPeg.get().setAccountData(INTEG_PROVISIONING_EVENT_TYPE, content);
}
- const content = this._getSettings() || {};
+ const content = this.getSettings() || {};
content[settingName] = newValue;
return MatrixClientPeg.get().setAccountData("im.vector.web.settings", content);
}
- canSetValue(settingName, roomId) {
+ public canSetValue(settingName: string, roomId: string): boolean {
return true; // It's their account, so they should be able to
}
- isSupported() {
+ public isSupported(): boolean {
const cli = MatrixClientPeg.get();
return cli !== undefined && cli !== null;
}
- _getSettings(eventType = "im.vector.web.settings") {
+ private getSettings(eventType = "im.vector.web.settings"): any { // TODO: [TS] Types on return
const cli = MatrixClientPeg.get();
if (!cli) return null;
@@ -182,11 +181,11 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
return objectClone(event.getContent()); // clone to prevent mutation
}
- _notifyBreadcrumbsUpdate(event) {
+ private notifyBreadcrumbsUpdate(event: MatrixEvent) {
let val = [];
if (event.getType() === BREADCRUMBS_LEGACY_EVENT_TYPE) {
// This seems fishy - try and get the event for the new rooms
- const newType = this._getSettings(BREADCRUMBS_EVENT_TYPE);
+ const newType = this.getSettings(BREADCRUMBS_EVENT_TYPE);
if (newType) val = newType['recent_rooms'];
else val = event.getContent()['rooms'];
} else if (event.getType() === BREADCRUMBS_EVENT_TYPE) {
@@ -194,6 +193,6 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
} else {
return; // for sanity, not because we expect to be here.
}
- this._watchers.notifyUpdate("breadcrumb_rooms", null, SettingLevel.ACCOUNT, val || []);
+ this.watchers.notifyUpdate("breadcrumb_rooms", null, SettingLevel.ACCOUNT, val || []);
}
}
diff --git a/src/settings/handlers/ConfigSettingsHandler.js b/src/settings/handlers/ConfigSettingsHandler.ts
similarity index 81%
rename from src/settings/handlers/ConfigSettingsHandler.js
rename to src/settings/handlers/ConfigSettingsHandler.ts
index 3b5b4b626e..3e8b1724c1 100644
--- a/src/settings/handlers/ConfigSettingsHandler.js
+++ b/src/settings/handlers/ConfigSettingsHandler.ts
@@ -1,6 +1,6 @@
/*
Copyright 2017 Travis Ralston
-Copyright 2019 New Vector Ltd
+Copyright 2019, 2020 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.
@@ -24,7 +24,7 @@ import {isNullOrUndefined} from "matrix-js-sdk/src/utils";
* roomId parameter.
*/
export default class ConfigSettingsHandler extends SettingsHandler {
- getValue(settingName, roomId) {
+ public getValue(settingName: string, roomId: string): any {
const config = SdkConfig.get() || {};
// Special case themes
@@ -37,15 +37,15 @@ export default class ConfigSettingsHandler extends SettingsHandler {
return settingsConfig[settingName];
}
- setValue(settingName, roomId, newValue) {
+ public async setValue(settingName: string, roomId: string, newValue: any): Promise {
throw new Error("Cannot change settings at the config level");
}
- canSetValue(settingName, roomId) {
+ public canSetValue(settingName: string, roomId: string): boolean {
return false;
}
- isSupported() {
+ public isSupported(): boolean {
return true; // SdkConfig is always there
}
}
diff --git a/src/settings/handlers/DefaultSettingsHandler.js b/src/settings/handlers/DefaultSettingsHandler.ts
similarity index 72%
rename from src/settings/handlers/DefaultSettingsHandler.js
rename to src/settings/handlers/DefaultSettingsHandler.ts
index 37bc5b2348..f54c7eafe1 100644
--- a/src/settings/handlers/DefaultSettingsHandler.js
+++ b/src/settings/handlers/DefaultSettingsHandler.ts
@@ -1,6 +1,6 @@
/*
Copyright 2017 Travis Ralston
-Copyright 2019 New Vector Ltd.
+Copyright 2019, 2020 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.
@@ -27,29 +27,27 @@ export default class DefaultSettingsHandler extends SettingsHandler {
* @param {object} defaults The default setting values, keyed by setting name.
* @param {object} invertedDefaults The default inverted setting values, keyed by setting name.
*/
- constructor(defaults, invertedDefaults) {
+ constructor(private defaults: Record, private invertedDefaults: Record) {
super();
- this._defaults = defaults;
- this._invertedDefaults = invertedDefaults;
}
- getValue(settingName, roomId) {
- let value = this._defaults[settingName];
+ public getValue(settingName: string, roomId: string): any {
+ let value = this.defaults[settingName];
if (value === undefined) {
- value = this._invertedDefaults[settingName];
+ value = this.invertedDefaults[settingName];
}
return value;
}
- setValue(settingName, roomId, newValue) {
+ public async setValue(settingName: string, roomId: string, newValue: any): Promise {
throw new Error("Cannot set values on the default level handler");
}
- canSetValue(settingName, roomId) {
+ public canSetValue(settingName: string, roomId: string) {
return false;
}
- isSupported() {
+ public isSupported(): boolean {
return true;
}
}
diff --git a/src/settings/handlers/DeviceSettingsHandler.js b/src/settings/handlers/DeviceSettingsHandler.ts
similarity index 71%
rename from src/settings/handlers/DeviceSettingsHandler.js
rename to src/settings/handlers/DeviceSettingsHandler.ts
index 44f89b9086..2096203598 100644
--- a/src/settings/handlers/DeviceSettingsHandler.js
+++ b/src/settings/handlers/DeviceSettingsHandler.ts
@@ -18,7 +18,8 @@ limitations under the License.
import SettingsHandler from "./SettingsHandler";
import {MatrixClientPeg} from "../../MatrixClientPeg";
-import {SettingLevel} from "../SettingsStore";
+import {SettingLevel} from "../SettingLevel";
+import { CallbackFn, WatchManager } from "../WatchManager";
/**
* Gets and sets settings at the "device" level for the current device.
@@ -29,17 +30,15 @@ export default class DeviceSettingsHandler extends SettingsHandler {
/**
* Creates a new device settings handler
* @param {string[]} featureNames The names of known features.
- * @param {WatchManager} watchManager The watch manager to notify updates to
+ * @param {WatchManager} watchers The watch manager to notify updates to
*/
- constructor(featureNames, watchManager) {
+ constructor(private featureNames: string[], private watchers: WatchManager) {
super();
- this._featureNames = featureNames;
- this._watchers = watchManager;
}
- getValue(settingName, roomId) {
- if (this._featureNames.includes(settingName)) {
- return this._readFeature(settingName);
+ public getValue(settingName: string, roomId: string): any {
+ if (this.featureNames.includes(settingName)) {
+ return this.readFeature(settingName);
}
// Special case notifications
@@ -68,28 +67,28 @@ export default class DeviceSettingsHandler extends SettingsHandler {
return val['value'];
}
- const settings = this._getSettings() || {};
+ const settings = this.getSettings() || {};
return settings[settingName];
}
- setValue(settingName, roomId, newValue) {
- if (this._featureNames.includes(settingName)) {
- this._writeFeature(settingName, newValue);
+ public setValue(settingName: string, roomId: string, newValue: any): Promise {
+ if (this.featureNames.includes(settingName)) {
+ this.writeFeature(settingName, newValue);
return Promise.resolve();
}
// Special case notifications
if (settingName === "notificationsEnabled") {
localStorage.setItem("notifications_enabled", newValue);
- this._watchers.notifyUpdate(settingName, null, SettingLevel.DEVICE, newValue);
+ this.watchers.notifyUpdate(settingName, null, SettingLevel.DEVICE, newValue);
return Promise.resolve();
} else if (settingName === "notificationBodyEnabled") {
localStorage.setItem("notifications_body_enabled", newValue);
- this._watchers.notifyUpdate(settingName, null, SettingLevel.DEVICE, newValue);
+ this.watchers.notifyUpdate(settingName, null, SettingLevel.DEVICE, newValue);
return Promise.resolve();
} else if (settingName === "audioNotificationsEnabled") {
localStorage.setItem("audio_notifications_enabled", newValue);
- this._watchers.notifyUpdate(settingName, null, SettingLevel.DEVICE, newValue);
+ this.watchers.notifyUpdate(settingName, null, SettingLevel.DEVICE, newValue);
return Promise.resolve();
}
@@ -103,35 +102,35 @@ export default class DeviceSettingsHandler extends SettingsHandler {
"lastRightPanelPhaseForGroup",
].includes(settingName)) {
localStorage.setItem(`mx_${settingName}`, JSON.stringify({value: newValue}));
- this._watchers.notifyUpdate(settingName, null, SettingLevel.DEVICE, newValue);
+ this.watchers.notifyUpdate(settingName, null, SettingLevel.DEVICE, newValue);
return Promise.resolve();
}
- const settings = this._getSettings() || {};
+ const settings = this.getSettings() || {};
settings[settingName] = newValue;
localStorage.setItem("mx_local_settings", JSON.stringify(settings));
- this._watchers.notifyUpdate(settingName, null, SettingLevel.DEVICE, newValue);
+ this.watchers.notifyUpdate(settingName, null, SettingLevel.DEVICE, newValue);
return Promise.resolve();
}
- canSetValue(settingName, roomId) {
+ public canSetValue(settingName: string, roomId: string): boolean {
return true; // It's their device, so they should be able to
}
- isSupported() {
+ public isSupported(): boolean {
return localStorage !== undefined && localStorage !== null;
}
- watchSetting(settingName, roomId, cb) {
- this._watchers.watchSetting(settingName, roomId, cb);
+ public watchSetting(settingName: string, roomId: string, cb: CallbackFn) {
+ this.watchers.watchSetting(settingName, roomId, cb);
}
- unwatchSetting(cb) {
- this._watchers.unwatchSetting(cb);
+ public unwatchSetting(cb: CallbackFn) {
+ this.watchers.unwatchSetting(cb);
}
- _getSettings() {
+ private getSettings(): any { // TODO: [TS] Type return
const value = localStorage.getItem("mx_local_settings");
if (!value) return null;
return JSON.parse(value);
@@ -140,7 +139,7 @@ export default class DeviceSettingsHandler extends SettingsHandler {
// Note: features intentionally don't use the same key as settings to avoid conflicts
// and to be backwards compatible.
- _readFeature(featureName) {
+ private readFeature(featureName: string): boolean | null {
if (MatrixClientPeg.get() && MatrixClientPeg.get().isGuest()) {
// Guests should not have any labs features enabled.
return false;
@@ -153,8 +152,8 @@ export default class DeviceSettingsHandler extends SettingsHandler {
return null;
}
- _writeFeature(featureName, enabled) {
- localStorage.setItem("mx_labs_feature_" + featureName, enabled);
- this._watchers.notifyUpdate(featureName, null, SettingLevel.DEVICE, enabled);
+ private writeFeature(featureName: string, enabled: boolean | null) {
+ localStorage.setItem("mx_labs_feature_" + featureName, `${enabled}`);
+ this.watchers.notifyUpdate(featureName, null, SettingLevel.DEVICE, enabled);
}
}
diff --git a/src/settings/handlers/LocalEchoWrapper.js b/src/settings/handlers/LocalEchoWrapper.ts
similarity index 62%
rename from src/settings/handlers/LocalEchoWrapper.js
rename to src/settings/handlers/LocalEchoWrapper.ts
index fd0510296a..5cfcd27aed 100644
--- a/src/settings/handlers/LocalEchoWrapper.js
+++ b/src/settings/handlers/LocalEchoWrapper.ts
@@ -1,6 +1,6 @@
/*
Copyright 2017 Travis Ralston
-Copyright 2019 New Vector Ltd.
+Copyright 2019, 2020 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.
@@ -23,47 +23,48 @@ import SettingsHandler from "./SettingsHandler";
* handler as much as possible to ensure values are not stale.
*/
export default class LocalEchoWrapper extends SettingsHandler {
+ private cache: {
+ [settingName: string]: {
+ [roomId: string]: any;
+ };
+ } = {};
+
/**
* Creates a new local echo wrapper
* @param {SettingsHandler} handler The handler to wrap
*/
- constructor(handler) {
+ constructor(private handler: SettingsHandler) {
super();
- this._handler = handler;
- this._cache = {
- // settingName: { roomId: value }
- };
}
- getValue(settingName, roomId) {
+ public getValue(settingName: string, roomId: string): any {
const cacheRoomId = roomId ? roomId : "UNDEFINED"; // avoid weird keys
- const bySetting = this._cache[settingName];
+ const bySetting = this.cache[settingName];
if (bySetting && bySetting.hasOwnProperty(cacheRoomId)) {
return bySetting[cacheRoomId];
}
- return this._handler.getValue(settingName, roomId);
+ return this.handler.getValue(settingName, roomId);
}
- setValue(settingName, roomId, newValue) {
- if (!this._cache[settingName]) this._cache[settingName] = {};
- const bySetting = this._cache[settingName];
+ public setValue(settingName: string, roomId: string, newValue: any): Promise {
+ if (!this.cache[settingName]) this.cache[settingName] = {};
+ const bySetting = this.cache[settingName];
const cacheRoomId = roomId ? roomId : "UNDEFINED"; // avoid weird keys
bySetting[cacheRoomId] = newValue;
- const handlerPromise = this._handler.setValue(settingName, roomId, newValue);
+ const handlerPromise = this.handler.setValue(settingName, roomId, newValue);
return Promise.resolve(handlerPromise).finally(() => {
delete bySetting[cacheRoomId];
});
}
-
- canSetValue(settingName, roomId) {
- return this._handler.canSetValue(settingName, roomId);
+ public canSetValue(settingName: string, roomId: string): boolean {
+ return this.handler.canSetValue(settingName, roomId);
}
- isSupported() {
- return this._handler.isSupported();
+ public isSupported(): boolean {
+ return this.handler.isSupported();
}
}
diff --git a/src/settings/handlers/MatrixClientBackedSettingsHandler.js b/src/settings/handlers/MatrixClientBackedSettingsHandler.ts
similarity index 68%
rename from src/settings/handlers/MatrixClientBackedSettingsHandler.js
rename to src/settings/handlers/MatrixClientBackedSettingsHandler.ts
index 63725b4dff..76825d1335 100644
--- a/src/settings/handlers/MatrixClientBackedSettingsHandler.js
+++ b/src/settings/handlers/MatrixClientBackedSettingsHandler.ts
@@ -1,5 +1,5 @@
/*
-Copyright 2019 New Vector Ltd.
+Copyright 2019, 2020 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.
@@ -15,6 +15,7 @@ limitations under the License.
*/
import SettingsHandler from "./SettingsHandler";
+import { MatrixClient } from "matrix-js-sdk/src/client";
// Dev note: This whole class exists in the event someone logs out and back in - we want
// to make sure the right MatrixClient is listening for changes.
@@ -23,30 +24,30 @@ import SettingsHandler from "./SettingsHandler";
* Represents the base class for settings handlers which need access to a MatrixClient.
* This class performs no logic and should be overridden.
*/
-export default class MatrixClientBackedSettingsHandler extends SettingsHandler {
- static _matrixClient;
- static _instances = [];
+export default abstract class MatrixClientBackedSettingsHandler extends SettingsHandler {
+ private static _matrixClient: MatrixClient;
+ private static instances: MatrixClientBackedSettingsHandler[] = [];
- static set matrixClient(client) {
+ public static set matrixClient(client: MatrixClient) {
const oldClient = MatrixClientBackedSettingsHandler._matrixClient;
MatrixClientBackedSettingsHandler._matrixClient = client;
- for (const instance of MatrixClientBackedSettingsHandler._instances) {
+ for (const instance of MatrixClientBackedSettingsHandler.instances) {
instance.initMatrixClient(oldClient, client);
}
}
- constructor() {
+ protected constructor() {
super();
- MatrixClientBackedSettingsHandler._instances.push(this);
+ MatrixClientBackedSettingsHandler.instances.push(this);
}
- get client() {
+ public get client(): MatrixClient {
return MatrixClientBackedSettingsHandler._matrixClient;
}
- initMatrixClient() {
+ protected initMatrixClient(oldClient: MatrixClient, newClient: MatrixClient) {
console.warn("initMatrixClient not overridden");
}
}
diff --git a/src/settings/handlers/RoomAccountSettingsHandler.js b/src/settings/handlers/RoomAccountSettingsHandler.ts
similarity index 65%
rename from src/settings/handlers/RoomAccountSettingsHandler.js
rename to src/settings/handlers/RoomAccountSettingsHandler.ts
index b2af81779b..e3449e76c3 100644
--- a/src/settings/handlers/RoomAccountSettingsHandler.js
+++ b/src/settings/handlers/RoomAccountSettingsHandler.ts
@@ -1,6 +1,6 @@
/*
Copyright 2017 Travis Ralston
-Copyright 2019 New Vector Ltd.
+Copyright 2019, 2020 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.
@@ -15,10 +15,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import {MatrixClientPeg} from '../../MatrixClientPeg';
+import { MatrixClientPeg } from '../../MatrixClientPeg';
import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler";
-import {SettingLevel} from "../SettingsStore";
-import {objectClone, objectKeyChanges} from "../../utils/objects";
+import { objectClone, objectKeyChanges } from "../../utils/objects";
+import { SettingLevel } from "../SettingLevel";
+import { WatchManager } from "../WatchManager";
+import { MatrixClient } from "matrix-js-sdk/src/client";
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+import { Room } from "matrix-js-sdk/src/models/room";
const ALLOWED_WIDGETS_EVENT_TYPE = "im.vector.setting.allowed_widgets";
@@ -26,22 +30,19 @@ const ALLOWED_WIDGETS_EVENT_TYPE = "im.vector.setting.allowed_widgets";
* Gets and sets settings at the "room-account" level for the current user.
*/
export default class RoomAccountSettingsHandler extends MatrixClientBackedSettingsHandler {
- constructor(watchManager) {
+ constructor(private watchers: WatchManager) {
super();
-
- this._watchers = watchManager;
- this._onAccountData = this._onAccountData.bind(this);
}
- initMatrixClient(oldClient, newClient) {
+ protected initMatrixClient(oldClient: MatrixClient, newClient: MatrixClient) {
if (oldClient) {
- oldClient.removeListener("Room.accountData", this._onAccountData);
+ oldClient.removeListener("Room.accountData", this.onAccountData);
}
- newClient.on("Room.accountData", this._onAccountData);
+ newClient.on("Room.accountData", this.onAccountData);
}
- _onAccountData(event, room, prevEvent) {
+ private onAccountData = (event: MatrixEvent, room: Room, prevEvent: MatrixEvent) => {
const roomId = room.roomId;
if (event.getType() === "org.matrix.room.preview_urls") {
@@ -52,29 +53,29 @@ export default class RoomAccountSettingsHandler extends MatrixClientBackedSettin
val = !val;
}
- this._watchers.notifyUpdate("urlPreviewsEnabled", roomId, SettingLevel.ROOM_ACCOUNT, val);
+ this.watchers.notifyUpdate("urlPreviewsEnabled", roomId, SettingLevel.ROOM_ACCOUNT, val);
} else if (event.getType() === "org.matrix.room.color_scheme") {
- this._watchers.notifyUpdate("roomColor", roomId, SettingLevel.ROOM_ACCOUNT, event.getContent());
+ this.watchers.notifyUpdate("roomColor", roomId, SettingLevel.ROOM_ACCOUNT, event.getContent());
} else if (event.getType() === "im.vector.web.settings") {
// Figure out what changed and fire those updates
const prevContent = prevEvent ? prevEvent.getContent() : {};
const changedSettings = objectKeyChanges(prevContent, event.getContent());
for (const settingName of changedSettings) {
const val = event.getContent()[settingName];
- this._watchers.notifyUpdate(settingName, roomId, SettingLevel.ROOM_ACCOUNT, val);
+ this.watchers.notifyUpdate(settingName, roomId, SettingLevel.ROOM_ACCOUNT, val);
}
} else if (event.getType() === ALLOWED_WIDGETS_EVENT_TYPE) {
- this._watchers.notifyUpdate("allowedWidgets", roomId, SettingLevel.ROOM_ACCOUNT, event.getContent());
+ this.watchers.notifyUpdate("allowedWidgets", roomId, SettingLevel.ROOM_ACCOUNT, event.getContent());
}
- }
+ };
- getValue(settingName, roomId) {
+ public getValue(settingName: string, roomId: string): any {
// Special case URL previews
if (settingName === "urlPreviewsEnabled") {
- const content = this._getSettings(roomId, "org.matrix.room.preview_urls") || {};
+ const content = this.getSettings(roomId, "org.matrix.room.preview_urls") || {};
// Check to make sure that we actually got a boolean
- if (typeof(content['disable']) !== "boolean") return null;
+ if (typeof (content['disable']) !== "boolean") return null;
return !content['disable'];
}
@@ -83,22 +84,22 @@ export default class RoomAccountSettingsHandler extends MatrixClientBackedSettin
// The event content should already be in an appropriate format, we just need
// to get the right value.
// don't fallback to {} because thats truthy and would imply there is an event specifying tint
- return this._getSettings(roomId, "org.matrix.room.color_scheme");
+ return this.getSettings(roomId, "org.matrix.room.color_scheme");
}
// Special case allowed widgets
if (settingName === "allowedWidgets") {
- return this._getSettings(roomId, ALLOWED_WIDGETS_EVENT_TYPE);
+ return this.getSettings(roomId, ALLOWED_WIDGETS_EVENT_TYPE);
}
- const settings = this._getSettings(roomId) || {};
+ const settings = this.getSettings(roomId) || {};
return settings[settingName];
}
- setValue(settingName, roomId, newValue) {
+ public setValue(settingName: string, roomId: string, newValue: any): Promise {
// Special case URL previews
if (settingName === "urlPreviewsEnabled") {
- const content = this._getSettings(roomId, "org.matrix.room.preview_urls") || {};
+ const content = this.getSettings(roomId, "org.matrix.room.preview_urls") || {};
content['disable'] = !newValue;
return MatrixClientPeg.get().setRoomAccountData(roomId, "org.matrix.room.preview_urls", content);
}
@@ -114,24 +115,24 @@ export default class RoomAccountSettingsHandler extends MatrixClientBackedSettin
return MatrixClientPeg.get().setRoomAccountData(roomId, ALLOWED_WIDGETS_EVENT_TYPE, newValue);
}
- const content = this._getSettings(roomId) || {};
+ const content = this.getSettings(roomId) || {};
content[settingName] = newValue;
return MatrixClientPeg.get().setRoomAccountData(roomId, "im.vector.web.settings", content);
}
- canSetValue(settingName, roomId) {
+ public canSetValue(settingName: string, roomId: string): boolean {
const room = MatrixClientPeg.get().getRoom(roomId);
// If they have the room, they can set their own account data
return room !== undefined && room !== null;
}
- isSupported() {
+ public isSupported(): boolean {
const cli = MatrixClientPeg.get();
return cli !== undefined && cli !== null;
}
- _getSettings(roomId, eventType = "im.vector.web.settings") {
+ private getSettings(roomId: string, eventType = "im.vector.web.settings"): any { // TODO: [TS] Type return
const room = MatrixClientPeg.get().getRoom(roomId);
if (!room) return null;
diff --git a/src/settings/handlers/RoomDeviceSettingsHandler.js b/src/settings/handlers/RoomDeviceSettingsHandler.ts
similarity index 66%
rename from src/settings/handlers/RoomDeviceSettingsHandler.js
rename to src/settings/handlers/RoomDeviceSettingsHandler.ts
index a9cf686c4c..2fcd58c27c 100644
--- a/src/settings/handlers/RoomDeviceSettingsHandler.js
+++ b/src/settings/handlers/RoomDeviceSettingsHandler.ts
@@ -1,6 +1,6 @@
/*
Copyright 2017 Travis Ralston
-Copyright 2019 New Vector Ltd.
+Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -16,71 +16,70 @@ limitations under the License.
*/
import SettingsHandler from "./SettingsHandler";
-import {SettingLevel} from "../SettingsStore";
+import { SettingLevel } from "../SettingLevel";
+import { WatchManager } from "../WatchManager";
/**
* Gets and sets settings at the "room-device" level for the current device in a particular
* room.
*/
export default class RoomDeviceSettingsHandler extends SettingsHandler {
- constructor(watchManager) {
+ constructor(private watchers: WatchManager) {
super();
-
- this._watchers = watchManager;
}
- getValue(settingName, roomId) {
+ public getValue(settingName: string, roomId: string): any {
// Special case blacklist setting to use legacy values
if (settingName === "blacklistUnverifiedDevices") {
- const value = this._read("mx_local_settings");
+ const value = this.read("mx_local_settings");
if (value && value['blacklistUnverifiedDevicesPerRoom']) {
return value['blacklistUnverifiedDevicesPerRoom'][roomId];
}
}
- const value = this._read(this._getKey(settingName, roomId));
+ const value = this.read(this.getKey(settingName, roomId));
if (value) return value.value;
return null;
}
- setValue(settingName, roomId, newValue) {
+ public setValue(settingName: string, roomId: string, newValue: any): Promise {
// Special case blacklist setting for legacy structure
if (settingName === "blacklistUnverifiedDevices") {
- let value = this._read("mx_local_settings");
+ let value = this.read("mx_local_settings");
if (!value) value = {};
if (!value["blacklistUnverifiedDevicesPerRoom"]) value["blacklistUnverifiedDevicesPerRoom"] = {};
value["blacklistUnverifiedDevicesPerRoom"][roomId] = newValue;
localStorage.setItem("mx_local_settings", JSON.stringify(value));
- this._watchers.notifyUpdate(settingName, roomId, SettingLevel.ROOM_DEVICE, newValue);
+ this.watchers.notifyUpdate(settingName, roomId, SettingLevel.ROOM_DEVICE, newValue);
return Promise.resolve();
}
if (newValue === null) {
- localStorage.removeItem(this._getKey(settingName, roomId));
+ localStorage.removeItem(this.getKey(settingName, roomId));
} else {
newValue = JSON.stringify({value: newValue});
- localStorage.setItem(this._getKey(settingName, roomId), newValue);
+ localStorage.setItem(this.getKey(settingName, roomId), newValue);
}
- this._watchers.notifyUpdate(settingName, roomId, SettingLevel.ROOM_DEVICE, newValue);
+ this.watchers.notifyUpdate(settingName, roomId, SettingLevel.ROOM_DEVICE, newValue);
return Promise.resolve();
}
- canSetValue(settingName, roomId) {
+ public canSetValue(settingName: string, roomId: string): boolean {
return true; // It's their device, so they should be able to
}
- isSupported() {
+ public isSupported(): boolean {
return localStorage !== undefined && localStorage !== null;
}
- _read(key) {
+ private read(key: string): any {
const rawValue = localStorage.getItem(key);
if (!rawValue) return null;
return JSON.parse(rawValue);
}
- _getKey(settingName, roomId) {
+ private getKey(settingName: string, roomId: string): string {
return "mx_setting_" + settingName + "_" + roomId;
}
}
diff --git a/src/settings/handlers/RoomSettingsHandler.js b/src/settings/handlers/RoomSettingsHandler.ts
similarity index 66%
rename from src/settings/handlers/RoomSettingsHandler.js
rename to src/settings/handlers/RoomSettingsHandler.ts
index 00dd5b8bec..4b4d4c4ad9 100644
--- a/src/settings/handlers/RoomSettingsHandler.js
+++ b/src/settings/handlers/RoomSettingsHandler.ts
@@ -1,6 +1,6 @@
/*
Copyright 2017 Travis Ralston
-Copyright 2019 New Vector Ltd.
+Copyright 2019, 2020 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.
@@ -15,31 +15,32 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import {MatrixClientPeg} from '../../MatrixClientPeg';
+import { MatrixClientPeg } from '../../MatrixClientPeg';
import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler";
-import {SettingLevel} from "../SettingsStore";
-import {objectClone, objectKeyChanges} from "../../utils/objects";
+import { objectClone, objectKeyChanges } from "../../utils/objects";
+import { SettingLevel } from "../SettingLevel";
+import { WatchManager } from "../WatchManager";
+import { MatrixClient } from "matrix-js-sdk/src/client";
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+import { RoomState } from "matrix-js-sdk/src/models/room-state";
/**
* Gets and sets settings at the "room" level.
*/
export default class RoomSettingsHandler extends MatrixClientBackedSettingsHandler {
- constructor(watchManager) {
+ constructor(private watchers: WatchManager) {
super();
-
- this._watchers = watchManager;
- this._onEvent = this._onEvent.bind(this);
}
- initMatrixClient(oldClient, newClient) {
+ protected initMatrixClient(oldClient: MatrixClient, newClient: MatrixClient) {
if (oldClient) {
- oldClient.removeListener("RoomState.events", this._onEvent);
+ oldClient.removeListener("RoomState.events", this.onEvent);
}
- newClient.on("RoomState.events", this._onEvent);
+ newClient.on("RoomState.events", this.onEvent);
}
- _onEvent(event, state, prevEvent) {
+ private onEvent = (event: MatrixEvent, state: RoomState, prevEvent: MatrixEvent) => {
const roomId = event.getRoomId();
const room = this.client.getRoom(roomId);
@@ -60,45 +61,45 @@ export default class RoomSettingsHandler extends MatrixClientBackedSettingsHandl
val = !val;
}
- this._watchers.notifyUpdate("urlPreviewsEnabled", roomId, SettingLevel.ROOM, val);
+ this.watchers.notifyUpdate("urlPreviewsEnabled", roomId, SettingLevel.ROOM, val);
} else if (event.getType() === "im.vector.web.settings") {
// Figure out what changed and fire those updates
const prevContent = prevEvent ? prevEvent.getContent() : {};
const changedSettings = objectKeyChanges(prevContent, event.getContent());
for (const settingName of changedSettings) {
- this._watchers.notifyUpdate(settingName, roomId, SettingLevel.ROOM, event.getContent()[settingName]);
+ this.watchers.notifyUpdate(settingName, roomId, SettingLevel.ROOM, event.getContent()[settingName]);
}
}
- }
+ };
- getValue(settingName, roomId) {
+ public getValue(settingName: string, roomId: string): any {
// Special case URL previews
if (settingName === "urlPreviewsEnabled") {
- const content = this._getSettings(roomId, "org.matrix.room.preview_urls") || {};
+ const content = this.getSettings(roomId, "org.matrix.room.preview_urls") || {};
// Check to make sure that we actually got a boolean
- if (typeof(content['disable']) !== "boolean") return null;
+ if (typeof (content['disable']) !== "boolean") return null;
return !content['disable'];
}
- const settings = this._getSettings(roomId) || {};
+ const settings = this.getSettings(roomId) || {};
return settings[settingName];
}
- setValue(settingName, roomId, newValue) {
+ public setValue(settingName: string, roomId: string, newValue: any): Promise {
// Special case URL previews
if (settingName === "urlPreviewsEnabled") {
- const content = this._getSettings(roomId, "org.matrix.room.preview_urls") || {};
+ const content = this.getSettings(roomId, "org.matrix.room.preview_urls") || {};
content['disable'] = !newValue;
return MatrixClientPeg.get().sendStateEvent(roomId, "org.matrix.room.preview_urls", content);
}
- const content = this._getSettings(roomId) || {};
+ const content = this.getSettings(roomId) || {};
content[settingName] = newValue;
return MatrixClientPeg.get().sendStateEvent(roomId, "im.vector.web.settings", content, "");
}
- canSetValue(settingName, roomId) {
+ public canSetValue(settingName: string, roomId: string): boolean {
const cli = MatrixClientPeg.get();
const room = cli.getRoom(roomId);
@@ -109,12 +110,12 @@ export default class RoomSettingsHandler extends MatrixClientBackedSettingsHandl
return room.currentState.maySendStateEvent(eventType, cli.getUserId());
}
- isSupported() {
+ public isSupported(): boolean {
const cli = MatrixClientPeg.get();
return cli !== undefined && cli !== null;
}
- _getSettings(roomId, eventType = "im.vector.web.settings") {
+ private getSettings(roomId: string, eventType = "im.vector.web.settings"): any {
const room = MatrixClientPeg.get().getRoom(roomId);
if (!room) return null;
diff --git a/src/settings/handlers/SettingsHandler.js b/src/settings/handlers/SettingsHandler.ts
similarity index 82%
rename from src/settings/handlers/SettingsHandler.js
rename to src/settings/handlers/SettingsHandler.ts
index 7d987fc136..ca9a068fd3 100644
--- a/src/settings/handlers/SettingsHandler.js
+++ b/src/settings/handlers/SettingsHandler.ts
@@ -1,6 +1,6 @@
/*
Copyright 2017 Travis Ralston
-Copyright 2019 New Vector Ltd.
+Copyright 2019, 2020 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.
@@ -19,7 +19,7 @@ limitations under the License.
* Represents the base class for all level handlers. This class performs no logic
* and should be overridden.
*/
-export default class SettingsHandler {
+export default abstract class SettingsHandler {
/**
* Gets the value for a particular setting at this level for a particular room.
* If no room is applicable, the roomId may be null. The roomId may not be
@@ -28,10 +28,7 @@ export default class SettingsHandler {
* @param {String} roomId The room ID to read from, may be null.
* @returns {*} The setting value, or null if not found.
*/
- getValue(settingName, roomId) {
- console.error("Invalid operation: getValue was not overridden");
- return null;
- }
+ public abstract getValue(settingName: string, roomId: string): any;
/**
* Sets the value for a particular setting at this level for a particular room.
@@ -44,10 +41,7 @@ export default class SettingsHandler {
* @param {*} newValue The new value for the setting, may be null.
* @returns {Promise} Resolves when the setting has been saved.
*/
- setValue(settingName, roomId, newValue) {
- console.error("Invalid operation: setValue was not overridden");
- return Promise.reject();
- }
+ public abstract setValue(settingName: string, roomId: string, newValue: any): Promise;
/**
* Determines if the current user is able to set the value of the given setting
@@ -56,15 +50,11 @@ export default class SettingsHandler {
* @param {String} roomId The room ID to check in, may be null
* @returns {boolean} True if the setting can be set by the user, false otherwise.
*/
- canSetValue(settingName, roomId) {
- return false;
- }
+ public abstract canSetValue(settingName: string, roomId: string): boolean;
/**
* Determines if this level is supported on this device.
* @returns {boolean} True if this level is supported on the current device.
*/
- isSupported() {
- return false;
- }
+ public abstract isSupported(): boolean;
}
diff --git a/src/settings/watchers/FontWatcher.ts b/src/settings/watchers/FontWatcher.ts
index 53ef999f9b..6a17bf2aa3 100644
--- a/src/settings/watchers/FontWatcher.ts
+++ b/src/settings/watchers/FontWatcher.ts
@@ -15,10 +15,11 @@ limitations under the License.
*/
import dis from '../../dispatcher/dispatcher';
-import SettingsStore, {SettingLevel} from '../SettingsStore';
+import SettingsStore from '../SettingsStore';
import IWatcher from "./Watcher";
import { toPx } from '../../utils/units';
import { Action } from '../../dispatcher/actions';
+import { SettingLevel } from "../SettingLevel";
export class FontWatcher implements IWatcher {
public static readonly MIN_SIZE = 8;
diff --git a/src/settings/watchers/ThemeWatcher.ts b/src/settings/watchers/ThemeWatcher.ts
index ce0db881c0..4330a15f57 100644
--- a/src/settings/watchers/ThemeWatcher.ts
+++ b/src/settings/watchers/ThemeWatcher.ts
@@ -15,17 +15,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import SettingsStore, { SettingLevel } from '../SettingsStore';
+import SettingsStore from '../SettingsStore';
import dis from '../../dispatcher/dispatcher';
import { Action } from '../../dispatcher/actions';
import ThemeController from "../controllers/ThemeController";
import { setTheme } from "../../theme";
import { ActionPayload } from '../../dispatcher/payloads';
+import { SettingLevel } from "../SettingLevel";
export default class ThemeWatcher {
- // XXX: I think this is unused.
- static _instance = null;
-
private themeWatchRef: string;
private systemThemeWatchRef: string;
private dispatcherRef: string;
diff --git a/src/stores/AsyncStoreWithClient.ts b/src/stores/AsyncStoreWithClient.ts
index f305bcb913..1ed7c6a547 100644
--- a/src/stores/AsyncStoreWithClient.ts
+++ b/src/stores/AsyncStoreWithClient.ts
@@ -17,12 +17,25 @@ limitations under the License.
import { MatrixClient } from "matrix-js-sdk/src/client";
import { AsyncStore } from "./AsyncStore";
import { ActionPayload } from "../dispatcher/payloads";
+import { Dispatcher } from "flux";
+import { MatrixClientPeg } from "../MatrixClientPeg";
export abstract class AsyncStoreWithClient extends AsyncStore {
protected matrixClient: MatrixClient;
protected abstract async onAction(payload: ActionPayload);
+ protected constructor(dispatcher: Dispatcher, initialState: T = {}) {
+ super(dispatcher, initialState);
+
+ if (MatrixClientPeg.get()) {
+ this.matrixClient = MatrixClientPeg.get();
+
+ // noinspection JSIgnoredPromiseFromCall
+ this.onReady();
+ }
+ }
+
protected async onReady() {
// Default implementation is to do nothing.
}
@@ -42,8 +55,14 @@ export abstract class AsyncStoreWithClient extends AsyncStore<
if (!(payload.prevState === 'PREPARED' && payload.state !== 'PREPARED')) {
return;
}
- this.matrixClient = payload.matrixClient;
- await this.onReady();
+
+ if (this.matrixClient !== payload.matrixClient) {
+ if (this.matrixClient) {
+ await this.onNotReady();
+ }
+ this.matrixClient = payload.matrixClient;
+ await this.onReady();
+ }
} else if (payload.action === 'on_client_not_viable' || payload.action === 'on_logged_out') {
if (this.matrixClient) {
await this.onNotReady();
diff --git a/src/stores/BreadcrumbsStore.ts b/src/stores/BreadcrumbsStore.ts
index ea29cb9dfc..24906f678c 100644
--- a/src/stores/BreadcrumbsStore.ts
+++ b/src/stores/BreadcrumbsStore.ts
@@ -14,13 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import SettingsStore, { SettingLevel } from "../settings/SettingsStore";
+import SettingsStore from "../settings/SettingsStore";
import { Room } from "matrix-js-sdk/src/models/room";
import { ActionPayload } from "../dispatcher/payloads";
import { AsyncStoreWithClient } from "./AsyncStoreWithClient";
import defaultDispatcher from "../dispatcher/dispatcher";
import { arrayHasDiff } from "../utils/arrays";
import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
+import { SettingLevel } from "../settings/SettingLevel";
const MAX_ROOMS = 20; // arbitrary
const AUTOJOIN_WAIT_THRESHOLD_MS = 90000; // 90s, the time we wait for an autojoined room to show up
diff --git a/src/stores/NonUrgentToastStore.ts b/src/stores/NonUrgentToastStore.ts
new file mode 100644
index 0000000000..72f896749c
--- /dev/null
+++ b/src/stores/NonUrgentToastStore.ts
@@ -0,0 +1,50 @@
+/*
+Copyright 2020 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 EventEmitter from "events";
+import { ComponentClass } from "../@types/common";
+import { UPDATE_EVENT } from "./AsyncStore";
+
+export type ToastReference = symbol;
+
+export default class NonUrgentToastStore extends EventEmitter {
+ private static _instance: NonUrgentToastStore;
+
+ private toasts = new Map();
+
+ public static get instance(): NonUrgentToastStore {
+ if (!NonUrgentToastStore._instance) {
+ NonUrgentToastStore._instance = new NonUrgentToastStore();
+ }
+ return NonUrgentToastStore._instance;
+ }
+
+ public get components(): ComponentClass[] {
+ return Array.from(this.toasts.values());
+ }
+
+ public addToast(c: ComponentClass): ToastReference {
+ const ref: ToastReference = Symbol();
+ this.toasts.set(ref, c);
+ this.emit(UPDATE_EVENT);
+ return ref;
+ }
+
+ public removeToast(ref: ToastReference) {
+ this.toasts.delete(ref);
+ this.emit(UPDATE_EVENT);
+ }
+}
diff --git a/src/stores/OwnProfileStore.ts b/src/stores/OwnProfileStore.ts
index 45d8829e30..1aa761e1c4 100644
--- a/src/stores/OwnProfileStore.ts
+++ b/src/stores/OwnProfileStore.ts
@@ -111,8 +111,6 @@ export class OwnProfileStore extends AsyncStoreWithClient {
await this.updateState({displayName: profileInfo.displayname, avatarUrl: profileInfo.avatar_url});
};
- // TSLint wants this to be a member, but we don't want that.
- // tslint:disable-next-line
private onStateEvents = throttle(async (ev: MatrixEvent) => {
const myUserId = MatrixClientPeg.get().getUserId();
if (ev.getType() === 'm.room.member' && ev.getSender() === myUserId && ev.getStateKey() === myUserId) {
diff --git a/src/stores/RightPanelStore.ts b/src/stores/RightPanelStore.ts
index dc5eb99a36..c1799978ad 100644
--- a/src/stores/RightPanelStore.ts
+++ b/src/stores/RightPanelStore.ts
@@ -17,10 +17,11 @@ limitations under the License.
import dis from '../dispatcher/dispatcher';
import {pendingVerificationRequestForUser} from '../verification';
import {Store} from 'flux/utils';
-import SettingsStore, {SettingLevel} from "../settings/SettingsStore";
+import SettingsStore from "../settings/SettingsStore";
import {RightPanelPhases, RIGHT_PANEL_PHASES_NO_ARGS} from "./RightPanelStorePhases";
import {ActionPayload} from "../dispatcher/payloads";
import {Action} from '../dispatcher/actions';
+import { SettingLevel } from "../settings/SettingLevel";
interface RightPanelStoreState {
// Whether or not to show the right panel at all. We split out rooms and groups
diff --git a/src/stores/ToastStore.ts b/src/stores/ToastStore.ts
index afb9fe1f8c..038aebc7c9 100644
--- a/src/stores/ToastStore.ts
+++ b/src/stores/ToastStore.ts
@@ -15,9 +15,10 @@ limitations under the License.
*/
import EventEmitter from "events";
-import React, {JSXElementConstructor} from "react";
+import React from "react";
+import { ComponentClass } from "../@types/common";
-export interface IToast> {
+export interface IToast {
key: string;
// higher priority number will be shown on top of lower priority
priority: number;
@@ -55,7 +56,7 @@ export default class ToastStore extends EventEmitter {
*
* @param {object} newToast The new toast
*/
- addOrReplaceToast>(newToast: IToast) {
+ addOrReplaceToast(newToast: IToast) {
const oldIndex = this.toasts.findIndex(t => t.key === newToast.key);
if (oldIndex === -1) {
let newIndex = this.toasts.length;
diff --git a/src/stores/local-echo/EchoChamber.ts b/src/stores/local-echo/EchoChamber.ts
new file mode 100644
index 0000000000..f61e521728
--- /dev/null
+++ b/src/stores/local-echo/EchoChamber.ts
@@ -0,0 +1,31 @@
+/*
+Copyright 2020 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 { RoomEchoChamber } from "./RoomEchoChamber";
+import { Room } from "matrix-js-sdk/src/models/room";
+import { EchoStore } from "./EchoStore";
+
+/**
+ * Semantic access to local echo
+ */
+export class EchoChamber {
+ private constructor() {
+ }
+
+ public static forRoom(room: Room): RoomEchoChamber {
+ return EchoStore.instance.getOrCreateChamberForRoom(room);
+ }
+}
diff --git a/src/stores/local-echo/EchoContext.ts b/src/stores/local-echo/EchoContext.ts
new file mode 100644
index 0000000000..55d6e8020a
--- /dev/null
+++ b/src/stores/local-echo/EchoContext.ts
@@ -0,0 +1,87 @@
+/*
+Copyright 2020 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 { EchoTransaction, RunFn, TransactionStatus } from "./EchoTransaction";
+import { arrayFastClone } from "../../utils/arrays";
+import { IDestroyable } from "../../utils/IDestroyable";
+import { Whenable } from "../../utils/Whenable";
+
+export enum ContextTransactionState {
+ NotStarted,
+ PendingErrors,
+ AllSuccessful
+}
+
+export abstract class EchoContext extends Whenable implements IDestroyable {
+ private _transactions: EchoTransaction[] = [];
+ private _state = ContextTransactionState.NotStarted;
+
+ public get transactions(): EchoTransaction[] {
+ return arrayFastClone(this._transactions);
+ }
+
+ public get state(): ContextTransactionState {
+ return this._state;
+ }
+
+ public get firstFailedTime(): Date {
+ const failedTxn = this.transactions.find(t => t.didPreviouslyFail || t.status === TransactionStatus.Error);
+ if (failedTxn) return failedTxn.startTime;
+ return null;
+ }
+
+ public disownTransaction(txn: EchoTransaction) {
+ const idx = this._transactions.indexOf(txn);
+ if (idx >= 0) this._transactions.splice(idx, 1);
+ txn.destroy();
+ this.checkTransactions();
+ }
+
+ public beginTransaction(auditName: string, runFn: RunFn): EchoTransaction {
+ const txn = new EchoTransaction(auditName, runFn);
+ this._transactions.push(txn);
+ txn.whenAnything(this.checkTransactions);
+
+ // We have no intent to call the transaction again if it succeeds (in fact, it'll
+ // be really angry at us if we do), so call that the end of the road for the events.
+ txn.when(TransactionStatus.Success, () => txn.destroy());
+
+ return txn;
+ }
+
+ private checkTransactions = () => {
+ let status = ContextTransactionState.AllSuccessful;
+ for (const txn of this.transactions) {
+ if (txn.status === TransactionStatus.Error || txn.didPreviouslyFail) {
+ status = ContextTransactionState.PendingErrors;
+ break;
+ } else if (txn.status === TransactionStatus.Pending) {
+ status = ContextTransactionState.NotStarted;
+ // no break as we might hit something which broke
+ }
+ }
+ this._state = status;
+ this.notifyCondition(status);
+ };
+
+ public destroy() {
+ for (const txn of this.transactions) {
+ txn.destroy();
+ }
+ this._transactions = [];
+ super.destroy();
+ }
+}
diff --git a/src/stores/local-echo/EchoStore.ts b/src/stores/local-echo/EchoStore.ts
new file mode 100644
index 0000000000..76e90be45e
--- /dev/null
+++ b/src/stores/local-echo/EchoStore.ts
@@ -0,0 +1,104 @@
+/*
+Copyright 2020 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 { GenericEchoChamber } from "./GenericEchoChamber";
+import { Room } from "matrix-js-sdk/src/models/room";
+import { RoomEchoChamber } from "./RoomEchoChamber";
+import { RoomEchoContext } from "./RoomEchoContext";
+import { AsyncStoreWithClient } from "../AsyncStoreWithClient";
+import defaultDispatcher from "../../dispatcher/dispatcher";
+import { ActionPayload } from "../../dispatcher/payloads";
+import { ContextTransactionState, EchoContext } from "./EchoContext";
+import NonUrgentToastStore, { ToastReference } from "../NonUrgentToastStore";
+import NonUrgentEchoFailureToast from "../../components/views/toasts/NonUrgentEchoFailureToast";
+
+interface IState {
+ toastRef: ToastReference;
+}
+
+type ContextKey = string;
+
+const roomContextKey = (room: Room): ContextKey => `room-${room.roomId}`;
+
+export class EchoStore extends AsyncStoreWithClient {
+ private static _instance: EchoStore;
+
+ private caches = new Map>();
+
+ constructor() {
+ super(defaultDispatcher);
+ }
+
+ public static get instance(): EchoStore {
+ if (!EchoStore._instance) {
+ EchoStore._instance = new EchoStore();
+ }
+ return EchoStore._instance;
+ }
+
+ public get contexts(): EchoContext[] {
+ return Array.from(this.caches.values()).map(e => e.context);
+ }
+
+ public getOrCreateChamberForRoom(room: Room): RoomEchoChamber {
+ if (this.caches.has(roomContextKey(room))) {
+ return this.caches.get(roomContextKey(room)) as RoomEchoChamber;
+ }
+
+ const context = new RoomEchoContext(room);
+ context.whenAnything(() => this.checkContexts());
+
+ const echo = new RoomEchoChamber(context);
+ echo.setClient(this.matrixClient);
+ this.caches.set(roomContextKey(room), echo);
+
+ return echo;
+ }
+
+ private async checkContexts() {
+ let hasOrHadError = false;
+ for (const echo of this.caches.values()) {
+ hasOrHadError = echo.context.state === ContextTransactionState.PendingErrors;
+ if (hasOrHadError) break;
+ }
+
+ if (hasOrHadError && !this.state.toastRef) {
+ const ref = NonUrgentToastStore.instance.addToast(NonUrgentEchoFailureToast);
+ await this.updateState({toastRef: ref});
+ } else if (!hasOrHadError && this.state.toastRef) {
+ NonUrgentToastStore.instance.removeToast(this.state.toastRef);
+ await this.updateState({toastRef: null});
+ }
+ }
+
+ protected async onReady(): Promise {
+ if (!this.caches) return; // can only happen during initialization
+ for (const echo of this.caches.values()) {
+ echo.setClient(this.matrixClient);
+ }
+ }
+
+ protected async onNotReady(): Promise {
+ for (const echo of this.caches.values()) {
+ echo.setClient(null);
+ }
+ }
+
+ protected async onAction(payload: ActionPayload): Promise {
+ // We have nothing to actually listen for
+ return Promise.resolve();
+ }
+}
diff --git a/src/stores/local-echo/EchoTransaction.ts b/src/stores/local-echo/EchoTransaction.ts
new file mode 100644
index 0000000000..2e372136d0
--- /dev/null
+++ b/src/stores/local-echo/EchoTransaction.ts
@@ -0,0 +1,72 @@
+/*
+Copyright 2020 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 { Whenable } from "../../utils/Whenable";
+
+export type RunFn = () => Promise;
+
+export enum TransactionStatus {
+ Pending,
+ Success,
+ Error,
+}
+
+export class EchoTransaction extends Whenable {
+ private _status = TransactionStatus.Pending;
+ private didFail = false;
+
+ public readonly startTime = new Date();
+
+ public constructor(
+ public readonly auditName,
+ public runFn: RunFn,
+ ) {
+ super();
+ }
+
+ public get didPreviouslyFail(): boolean {
+ return this.didFail;
+ }
+
+ public get status(): TransactionStatus {
+ return this._status;
+ }
+
+ public run() {
+ if (this.status === TransactionStatus.Success) {
+ throw new Error("Cannot re-run a successful echo transaction");
+ }
+ this.setStatus(TransactionStatus.Pending);
+ this.runFn()
+ .then(() => this.setStatus(TransactionStatus.Success))
+ .catch(() => this.setStatus(TransactionStatus.Error));
+ }
+
+ public cancel() {
+ // Success basically means "done"
+ this.setStatus(TransactionStatus.Success);
+ }
+
+ private setStatus(status: TransactionStatus) {
+ this._status = status;
+ if (status === TransactionStatus.Error) {
+ this.didFail = true;
+ } else if (status === TransactionStatus.Success) {
+ this.didFail = false;
+ }
+ this.notifyCondition(status);
+ }
+}
diff --git a/src/stores/local-echo/GenericEchoChamber.ts b/src/stores/local-echo/GenericEchoChamber.ts
new file mode 100644
index 0000000000..7a2173f702
--- /dev/null
+++ b/src/stores/local-echo/GenericEchoChamber.ts
@@ -0,0 +1,91 @@
+/*
+Copyright 2020 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 { EchoContext } from "./EchoContext";
+import { EchoTransaction, RunFn, TransactionStatus } from "./EchoTransaction";
+import { MatrixClient } from "matrix-js-sdk/src/client";
+import { EventEmitter } from "events";
+
+export async function implicitlyReverted() {
+ // do nothing :D
+}
+
+export const PROPERTY_UPDATED = "property_updated";
+
+export abstract class GenericEchoChamber extends EventEmitter {
+ private cache = new Map();
+ protected matrixClient: MatrixClient;
+
+ protected constructor(public readonly context: C, private lookupFn: (key: K) => V) {
+ super();
+ }
+
+ public setClient(client: MatrixClient) {
+ const oldClient = this.matrixClient;
+ this.matrixClient = client;
+ this.onClientChanged(oldClient, client);
+ }
+
+ protected abstract onClientChanged(oldClient: MatrixClient, newClient: MatrixClient);
+
+ /**
+ * Gets a value. If the key is in flight, the cached value will be returned. If
+ * the key is not in flight then the lookupFn provided to this class will be
+ * called instead.
+ * @param key The key to look up.
+ * @returns The value for the key.
+ */
+ public getValue(key: K): V {
+ return this.cache.has(key) ? this.cache.get(key).val : this.lookupFn(key);
+ }
+
+ private cacheVal(key: K, val: V, txn: EchoTransaction) {
+ this.cache.set(key, {txn, val});
+ this.emit(PROPERTY_UPDATED, key);
+ }
+
+ private decacheKey(key: K) {
+ if (this.cache.has(key)) {
+ this.context.disownTransaction(this.cache.get(key).txn);
+ this.cache.delete(key);
+ this.emit(PROPERTY_UPDATED, key);
+ }
+ }
+
+ protected markEchoReceived(key: K) {
+ if (this.cache.has(key)) {
+ const txn = this.cache.get(key).txn;
+ this.context.disownTransaction(txn);
+ txn.cancel();
+ }
+ this.decacheKey(key);
+ }
+
+ public setValue(auditName: string, key: K, targetVal: V, runFn: RunFn, revertFn: RunFn) {
+ // Cancel any pending transactions for the same key
+ if (this.cache.has(key)) {
+ this.cache.get(key).txn.cancel();
+ }
+
+ const ctxn = this.context.beginTransaction(auditName, runFn);
+ this.cacheVal(key, targetVal, ctxn); // set the cache now as it won't be updated by the .when() ladder below.
+
+ ctxn.when(TransactionStatus.Pending, () => this.cacheVal(key, targetVal, ctxn))
+ .when(TransactionStatus.Error, () => revertFn());
+
+ ctxn.run();
+ }
+}
diff --git a/src/stores/local-echo/RoomEchoChamber.ts b/src/stores/local-echo/RoomEchoChamber.ts
new file mode 100644
index 0000000000..e113f68c32
--- /dev/null
+++ b/src/stores/local-echo/RoomEchoChamber.ts
@@ -0,0 +1,78 @@
+/*
+Copyright 2020 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 { GenericEchoChamber, implicitlyReverted, PROPERTY_UPDATED } from "./GenericEchoChamber";
+import { getRoomNotifsState, setRoomNotifsState } from "../../RoomNotifs";
+import { RoomEchoContext } from "./RoomEchoContext";
+import { _t } from "../../languageHandler";
+import { Volume } from "../../RoomNotifsTypes";
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+
+export type CachedRoomValues = Volume;
+
+export enum CachedRoomKey {
+ NotificationVolume,
+}
+
+export class RoomEchoChamber extends GenericEchoChamber {
+ private properties = new Map();
+
+ public constructor(context: RoomEchoContext) {
+ super(context, (k) => this.properties.get(k));
+ }
+
+ protected onClientChanged(oldClient, newClient) {
+ this.properties.clear();
+ if (oldClient) {
+ oldClient.removeListener("accountData", this.onAccountData);
+ }
+ if (newClient) {
+ // Register the listeners first
+ newClient.on("accountData", this.onAccountData);
+
+ // Then populate the properties map
+ this.updateNotificationVolume();
+ }
+ }
+
+ private onAccountData = (event: MatrixEvent) => {
+ if (event.getType() === "m.push_rules") {
+ const currentVolume = this.properties.get(CachedRoomKey.NotificationVolume) as Volume;
+ const newVolume = getRoomNotifsState(this.context.room.roomId) as Volume;
+ if (currentVolume !== newVolume) {
+ this.updateNotificationVolume();
+ }
+ }
+ };
+
+ private updateNotificationVolume() {
+ this.properties.set(CachedRoomKey.NotificationVolume, getRoomNotifsState(this.context.room.roomId));
+ this.markEchoReceived(CachedRoomKey.NotificationVolume);
+ this.emit(PROPERTY_UPDATED, CachedRoomKey.NotificationVolume);
+ }
+
+ // ---- helpers below here ----
+
+ public get notificationVolume(): Volume {
+ return this.getValue(CachedRoomKey.NotificationVolume);
+ }
+
+ public set notificationVolume(v: Volume) {
+ this.setValue(_t("Change notification settings"), CachedRoomKey.NotificationVolume, v, async () => {
+ return setRoomNotifsState(this.context.room.roomId, v);
+ }, implicitlyReverted);
+ }
+}
diff --git a/src/stores/local-echo/RoomEchoContext.ts b/src/stores/local-echo/RoomEchoContext.ts
new file mode 100644
index 0000000000..4105f728c3
--- /dev/null
+++ b/src/stores/local-echo/RoomEchoContext.ts
@@ -0,0 +1,24 @@
+/*
+Copyright 2020 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 { EchoContext } from "./EchoContext";
+import { Room } from "matrix-js-sdk/src/models/room";
+
+export class RoomEchoContext extends EchoContext {
+ constructor(public readonly room: Room) {
+ super();
+ }
+}
diff --git a/src/stores/room-list/algorithms/Algorithm.ts b/src/stores/room-list/algorithms/Algorithm.ts
index 2654a8b460..f1a7ab1613 100644
--- a/src/stores/room-list/algorithms/Algorithm.ts
+++ b/src/stores/room-list/algorithms/Algorithm.ts
@@ -212,7 +212,18 @@ export class Algorithm extends EventEmitter {
// We specifically do NOT use the ordered rooms set as it contains the sticky room, which
// means we'll be off by 1 when the user is switching rooms. This leads to visual jumping
// when the user is moving south in the list (not north, because of math).
- let position = this.getOrderedRoomsWithoutSticky()[tag].indexOf(val);
+ const tagList = this.getOrderedRoomsWithoutSticky()[tag] || []; // can be null if filtering
+ let position = tagList.indexOf(val);
+
+ // We do want to see if a tag change happened though - if this did happen then we'll want
+ // to force the position to zero (top) to ensure we can properly handle it.
+ const wasSticky = this._lastStickyRoom.room ? this._lastStickyRoom.room.roomId === val.roomId : false;
+ if (this._lastStickyRoom.tag && tag !== this._lastStickyRoom.tag && wasSticky && position < 0) {
+ console.warn(`Sticky room ${val.roomId} changed tags during sticky room handling`);
+ position = 0;
+ }
+
+ // Sanity check the position to make sure the room is qualified for being sticky
if (position < 0) throw new Error(`${val.roomId} does not appear to be known and cannot be sticky`);
// 🐉 Here be dragons.
diff --git a/src/utils/Whenable.ts b/src/utils/Whenable.ts
new file mode 100644
index 0000000000..49f2571965
--- /dev/null
+++ b/src/utils/Whenable.ts
@@ -0,0 +1,86 @@
+/*
+Copyright 2020 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 { IDestroyable } from "./IDestroyable";
+import { arrayFastClone } from "./arrays";
+
+export type WhenFn = (w: Whenable) => void;
+
+/**
+ * Whenables are a cheap way to have Observable patterns mixed with typical
+ * usage of Promises, without having to tear down listeners or calls. Whenables
+ * are intended to be used when a condition will be met multiple times and
+ * the consumer needs to know *when* that happens.
+ */
+export abstract class Whenable implements IDestroyable {
+ private listeners: {condition: T | null, fn: WhenFn}[] = [];
+
+ /**
+ * Sets up a call to `fn` *when* the `condition` is met.
+ * @param condition The condition to match.
+ * @param fn The function to call.
+ * @returns This.
+ */
+ public when(condition: T, fn: WhenFn): Whenable {
+ this.listeners.push({condition, fn});
+ return this;
+ }
+
+ /**
+ * Sets up a call to `fn` *when* any of the `conditions` are met.
+ * @param conditions The conditions to match.
+ * @param fn The function to call.
+ * @returns This.
+ */
+ public whenAnyOf(conditions: T[], fn: WhenFn): Whenable {
+ for (const condition of conditions) {
+ this.when(condition, fn);
+ }
+ return this;
+ }
+
+ /**
+ * Sets up a call to `fn` *when* any condition is met.
+ * @param fn The function to call.
+ * @returns This.
+ */
+ public whenAnything(fn: WhenFn): Whenable {
+ this.listeners.push({condition: null, fn});
+ return this;
+ }
+
+ /**
+ * Notifies all the listeners of a given condition.
+ * @param condition The new condition that has been met.
+ */
+ protected notifyCondition(condition: T) {
+ const listeners = arrayFastClone(this.listeners); // clone just in case the handler modifies us
+ for (const listener of listeners) {
+ if (listener.condition === null || listener.condition === condition) {
+ try {
+ listener.fn(this);
+ } catch (e) {
+ console.error(`Error calling whenable listener for ${condition}:`, e);
+ }
+ }
+ }
+ }
+
+ public destroy() {
+ this.listeners = [];
+ }
+}
diff --git a/tslint.json b/tslint.json
deleted file mode 100644
index fd99d9d228..0000000000
--- a/tslint.json
+++ /dev/null
@@ -1,74 +0,0 @@
-{
- "rules": {
- "class-name": false,
- "comment-format": [
- true
- ],
- "curly": false,
- "eofline": false,
- "forin": false,
- "indent": [
- true,
- "spaces"
- ],
- "label-position": true,
- "max-line-length": false,
- "member-access": false,
- "member-ordering": [
- true,
- "static-after-instance",
- "variables-before-functions"
- ],
- "no-arg": true,
- "no-bitwise": false,
- "no-console": false,
- "no-construct": true,
- "no-debugger": true,
- "no-duplicate-variable": true,
- "no-empty": false,
- "no-eval": true,
- "no-inferrable-types": true,
- "no-shadowed-variable": true,
- "no-string-literal": false,
- "no-switch-case-fall-through": true,
- "no-trailing-whitespace": true,
- "no-unused-expression": true,
- "no-use-before-declare": false,
- "no-var-keyword": true,
- "object-literal-sort-keys": false,
- "one-line": [
- true,
- "check-open-brace",
- "check-catch",
- "check-else",
- "check-whitespace"
- ],
- "quotemark": false,
- "radix": true,
- "semicolon": [
- true,
- "always",
- "strict-bound-class-methods"
- ],
- "triple-equals": [],
- "typedef-whitespace": [
- true,
- {
- "call-signature": "nospace",
- "index-signature": "nospace",
- "parameter": "nospace",
- "property-declaration": "nospace",
- "variable-declaration": "nospace"
- }
- ],
- "variable-name": false,
- "whitespace": [
- true,
- "check-branch",
- "check-decl",
- "check-operator",
- "check-separator",
- "check-type"
- ]
- }
-}
diff --git a/yarn.lock b/yarn.lock
index 47248af9cd..edbd8c1a2c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -111,6 +111,15 @@
jsesc "^2.5.1"
source-map "^0.5.0"
+"@babel/generator@^7.11.0":
+ version "7.11.0"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.11.0.tgz#4b90c78d8c12825024568cbe83ee6c9af193585c"
+ integrity sha512-fEm3Uzw7Mc9Xi//qU20cBKatTfs2aOtKqmvy/Vm7RkJEGFQ4xc9myCfbXxqK//ZS8MR/ciOHw6meGASJuKmDfQ==
+ dependencies:
+ "@babel/types" "^7.11.0"
+ jsesc "^2.5.1"
+ source-map "^0.5.0"
+
"@babel/helper-annotate-as-pure@^7.10.1":
version "7.10.1"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.1.tgz#f6d08acc6f70bbd59b436262553fb2e259a1a268"
@@ -400,6 +409,13 @@
dependencies:
"@babel/types" "^7.10.4"
+"@babel/helper-split-export-declaration@^7.11.0":
+ version "7.11.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz#f8a491244acf6a676158ac42072911ba83ad099f"
+ integrity sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==
+ dependencies:
+ "@babel/types" "^7.11.0"
+
"@babel/helper-validator-identifier@^7.10.1", "@babel/helper-validator-identifier@^7.10.3":
version "7.10.3"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.3.tgz#60d9847f98c4cea1b279e005fdb7c28be5412d15"
@@ -466,6 +482,11 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.5.tgz#e7c6bf5a7deff957cec9f04b551e2762909d826b"
integrity sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ==
+"@babel/parser@^7.11.0":
+ version "7.11.0"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.11.0.tgz#a9d7e11aead25d3b422d17b2c6502c8dddef6a5d"
+ integrity sha512-qvRvi4oI8xii8NllyEc4MDJjuZiNaRzyb7Y7lup1NqJV8TZHF4O27CcP+72WPn/k1zkgJ6WJfnIbk4jTsVAZHw==
+
"@babel/plugin-proposal-async-generator-functions@^7.10.4":
version "7.10.5"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.5.tgz#3491cabf2f7c179ab820606cec27fed15e0e8558"
@@ -1213,6 +1234,21 @@
globals "^11.1.0"
lodash "^4.17.19"
+"@babel/traverse@^7.11.0":
+ version "7.11.0"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.11.0.tgz#9b996ce1b98f53f7c3e4175115605d56ed07dd24"
+ integrity sha512-ZB2V+LskoWKNpMq6E5UUCrjtDUh5IOTAyIl0dTjIEoXum/iKWkoIEKIRDnUucO6f+2FzNkE0oD4RLKoPIufDtg==
+ dependencies:
+ "@babel/code-frame" "^7.10.4"
+ "@babel/generator" "^7.11.0"
+ "@babel/helper-function-name" "^7.10.4"
+ "@babel/helper-split-export-declaration" "^7.11.0"
+ "@babel/parser" "^7.11.0"
+ "@babel/types" "^7.11.0"
+ debug "^4.1.0"
+ globals "^11.1.0"
+ lodash "^4.17.19"
+
"@babel/types@^7.0.0", "@babel/types@^7.10.1", "@babel/types@^7.10.2", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.7.0":
version "7.10.2"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.2.tgz#30283be31cad0dbf6fb00bd40641ca0ea675172d"
@@ -1231,6 +1267,15 @@
lodash "^4.17.19"
to-fast-properties "^2.0.0"
+"@babel/types@^7.11.0":
+ version "7.11.0"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.11.0.tgz#2ae6bf1ba9ae8c3c43824e5861269871b206e90d"
+ integrity sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.10.4"
+ lodash "^4.17.19"
+ to-fast-properties "^2.0.0"
+
"@cnakazawa/watch@^1.0.3":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a"
@@ -4178,11 +4223,6 @@ estree-walker@^0.6.1:
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362"
integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==
-estree-walker@^0.9.0:
- version "0.9.0"
- resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.9.0.tgz#9116372f09c02fd88fcafb0c04343631012a0aa6"
- integrity sha512-12U47o7XHUX329+x3FzNVjCx3SHEzMF0nkDv7r/HnBzX/xNTKxajBk6gyygaxrAFtLj39219oMfbtxv4KpaOiA==
-
esutils@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
@@ -4525,11 +4565,6 @@ flatted@^2.0.0:
resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138"
integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==
-flow-parser@0.57.3:
- version "0.57.3"
- resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.57.3.tgz#b8d241a1b1cbae043afa7976e39f269988d8fe34"
- integrity sha1-uNJBobHLrgQ6+nl2458mmYjY/jQ=
-
flush-write-stream@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8"