Merge remote-tracking branch 'origin/develop' into bwindels/remove-composer-overlay-icons

This commit is contained in:
Bruno Windels 2020-08-03 18:05:26 +02:00
commit fe1d375dd2
126 changed files with 2735 additions and 915 deletions

39
docs/local-echo-dev.md Normal file
View file

@ -0,0 +1,39 @@
# Local echo (developer docs)
The React SDK provides some local echo functionality to allow for components to do something
quickly and fall back when it fails. This is all available in the `local-echo` directory within
`stores`.
Echo is handled in EchoChambers, with `GenericEchoChamber` being the base implementation for all
chambers. The `EchoChamber` class is provided as semantic access to a `GenericEchoChamber`
implementation, such as the `RoomEchoChamber` (which handles echoable details of a room).
Anything that can be locally echoed will be provided by the `GenericEchoChamber` implementation.
The echo chamber will also need to deal with external changes, and has full control over whether
or not something has successfully been echoed.
An `EchoContext` is provided to echo chambers (usually with a matching type: `RoomEchoContext`
gets provided to a `RoomEchoChamber` for example) with details about their intended area of
effect, as well as manage `EchoTransaction`s. An `EchoTransaction` is simply a unit of work that
needs to be locally echoed.
The `EchoStore` manages echo chamber instances, builds contexts, and is generally less semantically
accessible than the `EchoChamber` class. For separation of concerns, and to try and keep things
tidy, this is an intentional design decision.
**Note**: The local echo stack uses a "whenable" pattern, which is similar to thenables and
`EventEmitter`. Whenables are ways of actioning a changing condition without having to deal
with listeners being torn down. Once the reference count of the Whenable causes garbage collection,
the Whenable's listeners will also be torn down. This is accelerated by the `IDestroyable` interface
usage.
## Audit functionality
The UI supports a "Server isn't responding" dialog which includes a partial audit log-like
structure to it. This is partially the reason for added complexity of `EchoTransaction`s
and `EchoContext`s - this information feeds the UI states which then provide direct retry
mechanisms.
The `EchoStore` is responsible for ensuring that the appropriate non-urgent toast (lower left)
is set up, where the dialog then drives through the contexts and transactions.

View file

@ -105,6 +105,7 @@
"devDependencies": { "devDependencies": {
"@babel/cli": "^7.10.5", "@babel/cli": "^7.10.5",
"@babel/core": "^7.10.5", "@babel/core": "^7.10.5",
"@babel/parser": "^7.11.0",
"@babel/plugin-proposal-class-properties": "^7.10.4", "@babel/plugin-proposal-class-properties": "^7.10.4",
"@babel/plugin-proposal-decorators": "^7.10.5", "@babel/plugin-proposal-decorators": "^7.10.5",
"@babel/plugin-proposal-export-default-from": "^7.10.4", "@babel/plugin-proposal-export-default-from": "^7.10.4",
@ -117,6 +118,7 @@
"@babel/preset-react": "^7.10.4", "@babel/preset-react": "^7.10.4",
"@babel/preset-typescript": "^7.10.4", "@babel/preset-typescript": "^7.10.4",
"@babel/register": "^7.10.5", "@babel/register": "^7.10.5",
"@babel/traverse": "^7.11.0",
"@peculiar/webcrypto": "^1.1.2", "@peculiar/webcrypto": "^1.1.2",
"@types/classnames": "^2.2.10", "@types/classnames": "^2.2.10",
"@types/counterpart": "^0.18.1", "@types/counterpart": "^0.18.1",
@ -145,9 +147,7 @@
"eslint-plugin-flowtype": "^2.50.3", "eslint-plugin-flowtype": "^2.50.3",
"eslint-plugin-react": "^7.20.3", "eslint-plugin-react": "^7.20.3",
"eslint-plugin-react-hooks": "^2.5.1", "eslint-plugin-react-hooks": "^2.5.1",
"estree-walker": "^0.9.0",
"file-loader": "^3.0.1", "file-loader": "^3.0.1",
"flow-parser": "0.57.3",
"glob": "^5.0.15", "glob": "^5.0.15",
"jest": "^24.9.0", "jest": "^24.9.0",
"jest-canvas-mock": "^2.2.0", "jest-canvas-mock": "^2.2.0",

View file

@ -15,6 +15,7 @@
@import "./structures/_MainSplit.scss"; @import "./structures/_MainSplit.scss";
@import "./structures/_MatrixChat.scss"; @import "./structures/_MatrixChat.scss";
@import "./structures/_MyGroups.scss"; @import "./structures/_MyGroups.scss";
@import "./structures/_NonUrgentToastContainer.scss";
@import "./structures/_NotificationPanel.scss"; @import "./structures/_NotificationPanel.scss";
@import "./structures/_RightPanel.scss"; @import "./structures/_RightPanel.scss";
@import "./structures/_RoomDirectory.scss"; @import "./structures/_RoomDirectory.scss";
@ -75,6 +76,7 @@
@import "./views/dialogs/_RoomSettingsDialogBridges.scss"; @import "./views/dialogs/_RoomSettingsDialogBridges.scss";
@import "./views/dialogs/_RoomUpgradeDialog.scss"; @import "./views/dialogs/_RoomUpgradeDialog.scss";
@import "./views/dialogs/_RoomUpgradeWarningDialog.scss"; @import "./views/dialogs/_RoomUpgradeWarningDialog.scss";
@import "./views/dialogs/_ServerOfflineDialog.scss";
@import "./views/dialogs/_SetEmailDialog.scss"; @import "./views/dialogs/_SetEmailDialog.scss";
@import "./views/dialogs/_SetMxIdDialog.scss"; @import "./views/dialogs/_SetMxIdDialog.scss";
@import "./views/dialogs/_SetPasswordDialog.scss"; @import "./views/dialogs/_SetPasswordDialog.scss";
@ -215,6 +217,7 @@
@import "./views/settings/tabs/user/_SecurityUserSettingsTab.scss"; @import "./views/settings/tabs/user/_SecurityUserSettingsTab.scss";
@import "./views/settings/tabs/user/_VoiceUserSettingsTab.scss"; @import "./views/settings/tabs/user/_VoiceUserSettingsTab.scss";
@import "./views/terms/_InlineTermsAgreement.scss"; @import "./views/terms/_InlineTermsAgreement.scss";
@import "./views/toasts/_NonUrgentEchoFailureToast.scss";
@import "./views/verification/_VerificationShowSas.scss"; @import "./views/verification/_VerificationShowSas.scss";
@import "./views/voip/_CallContainer.scss"; @import "./views/voip/_CallContainer.scss";
@import "./views/voip/_CallView.scss"; @import "./views/voip/_CallView.scss";

View file

@ -109,3 +109,7 @@ limitations under the License.
.mx_FilePanel .mx_EventTile:hover .mx_EventTile_line { .mx_FilePanel .mx_EventTile:hover .mx_EventTile_line {
background-color: $primary-bg-color; background-color: $primary-bg-color;
} }
.mx_FilePanel_empty::before {
mask-image: url('$(res)/img/element-icons/room/files.svg');
}

View file

@ -0,0 +1,35 @@
/*
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.
*/
.mx_NonUrgentToastContainer {
position: absolute;
bottom: 30px;
left: 28px;
z-index: 101; // same level as other toasts
.mx_NonUrgentToastContainer_toast {
padding: 10px 12px;
border-radius: 8px;
width: 320px;
font-size: $font-13px;
margin-top: 8px;
// We don't use variables on the colours because we want it to be the same
// in all themes.
background-color: #17191c;
color: #fff;
}
}

View file

@ -99,3 +99,7 @@ limitations under the License.
.mx_NotificationPanel .mx_EventTile_content { .mx_NotificationPanel .mx_EventTile_content {
margin-right: 0px; margin-right: 0px;
} }
.mx_NotificationPanel_empty::before {
mask-image: url('$(res)/img/element-icons/notifications.svg');
}

View file

@ -144,3 +144,28 @@ limitations under the License.
order: 2; order: 2;
margin: auto; margin: auto;
} }
.mx_RightPanel_empty {
margin-right: -42px;
h2 {
font-weight: 700;
margin: 16px 0;
}
h2, p {
font-size: $font-14px;
}
&::before {
content: '';
display: block;
margin: 11px auto 29px auto;
height: 42px;
width: 42px;
background-color: $rightpanel-button-color;
mask-repeat: no-repeat;
mask-size: contain;
mask-position: center;
}
}

View file

@ -53,11 +53,12 @@ limitations under the License.
.mx_RebrandDialog_chevron::after { .mx_RebrandDialog_chevron::after {
content: ''; content: '';
display: inline-block; display: inline-block;
width: 24px; width: 30px;
height: 24px; height: 30px;
mask-position: center; mask-position: center;
mask-size: contain; mask-size: contain;
mask-repeat: no-repeat; mask-repeat: no-repeat;
background-color: $muted-fg-color; background-color: $muted-fg-color;
mask-image: url('$(res)/img/feather-customised/chevron-right.svg'); mask-image: url('$(res)/img/feather-customised/chevron-down.svg');
transform: rotate(-90deg);
} }

View file

@ -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.
*/
.mx_ServerOfflineDialog {
.mx_ServerOfflineDialog_content {
padding-right: 85px;
color: $primary-fg-color;
hr {
border-color: $primary-fg-color;
opacity: 0.1;
border-bottom: none;
}
ul {
padding: 16px;
li:nth-child(n + 2) {
margin-top: 16px;
}
}
.mx_ServerOfflineDialog_content_context {
.mx_ServerOfflineDialog_content_context_timestamp {
display: inline-block;
width: 115px;
color: $muted-fg-color;
line-height: 24px; // same as avatar
vertical-align: top;
}
.mx_ServerOfflineDialog_content_context_timeline {
display: inline-block;
width: calc(100% - 155px); // 115px timestamp width + 40px right margin
.mx_ServerOfflineDialog_content_context_timeline_header {
span {
margin-left: 8px;
vertical-align: middle;
}
}
.mx_ServerOfflineDialog_content_context_txn {
position: relative;
margin-top: 8px;
.mx_ServerOfflineDialog_content_context_txn_desc {
width: calc(100% - 100px); // 100px is an arbitrary margin for the button
}
.mx_AccessibleButton {
float: right;
padding: 0;
}
}
}
}
}
}

View file

@ -145,13 +145,14 @@ limitations under the License.
&::after { &::after {
content: ""; content: "";
position: absolute; position: absolute;
width: 24px; width: 26px;
height: 24px; height: 26px;
right: -28px; // - (24 + 4) right: -27.5px; // - (width: 26 + spacing to align with X above: 1.5)
top: -3px;
mask-repeat: no-repeat; mask-repeat: no-repeat;
mask-position: center; mask-position: center;
mask-size: contain; mask-size: contain;
mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); mask-image: url('$(res)/img/feather-customised/chevron-down-thin.svg');
background-color: $primary-fg-color; background-color: $primary-fg-color;
} }

View file

@ -67,8 +67,8 @@ limitations under the License.
bottom: 0; bottom: 0;
left: 0; left: 0;
right: 0; right: 0;
mask: url('$(res)/img/icon-jump-to-bottom.svg'); mask-image: url('$(res)/img/feather-customised/chevron-down-thin.svg');
mask-repeat: no-repeat; mask-repeat: no-repeat;
mask-position: 9px 14px; mask-size: contain;
background: $muted-fg-color; background: $muted-fg-color;
} }

View file

@ -142,26 +142,24 @@ limitations under the License.
.mx_RoomSublist_collapseBtn { .mx_RoomSublist_collapseBtn {
display: inline-block; display: inline-block;
position: relative; position: relative;
width: 12px; width: 14px;
height: 12px; height: 14px;
margin-right: 8px; margin-right: 6px;
&::before { &::before {
content: ''; content: '';
width: 12px; width: 18px;
height: 12px; height: 18px;
position: absolute; position: absolute;
top: 1px;
left: 1px;
mask-position: center; mask-position: center;
mask-size: contain; mask-size: contain;
mask-repeat: no-repeat; mask-repeat: no-repeat;
background-color: $primary-fg-color; background-color: $roomlist-header-color;
mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); mask-image: url('$(res)/img/feather-customised/chevron-down.svg');
} }
&.mx_RoomSublist_collapseBtn_collapsed::before { &.mx_RoomSublist_collapseBtn_collapsed::before {
mask-image: url('$(res)/img/feather-customised/chevron-right.svg'); transform: rotate(-90deg);
} }
} }
} }
@ -251,22 +249,24 @@ limitations under the License.
.mx_RoomSublist_showNButtonChevron { .mx_RoomSublist_showNButtonChevron {
position: relative; position: relative;
width: 16px; width: 18px;
height: 16px; height: 18px;
margin-left: 12px; margin-left: 12px;
margin-right: 18px; margin-right: 16px;
mask-position: center; mask-position: center;
mask-size: contain; mask-size: contain;
mask-repeat: no-repeat; mask-repeat: no-repeat;
background: $roomtile-preview-color; background: $roomlist-header-color;
left: -1px; // adjust for image position
} }
.mx_RoomSublist_showMoreButtonChevron { .mx_RoomSublist_showMoreButtonChevron,
.mx_RoomSublist_showLessButtonChevron {
mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); mask-image: url('$(res)/img/feather-customised/chevron-down.svg');
} }
.mx_RoomSublist_showLessButtonChevron { .mx_RoomSublist_showLessButtonChevron {
mask-image: url('$(res)/img/feather-customised/chevron-up.svg'); transform: rotate(180deg);
} }
} }

View file

@ -28,7 +28,7 @@ limitations under the License.
content: ""; content: "";
position: absolute; position: absolute;
top: -8px; top: -8px;
left: 11px; left: 10.5px;
width: 4px; width: 4px;
height: 4px; height: 4px;
border-radius: 16px; border-radius: 16px;
@ -49,12 +49,13 @@ limitations under the License.
.mx_TopUnreadMessagesBar_scrollUp::before { .mx_TopUnreadMessagesBar_scrollUp::before {
content: ""; content: "";
position: absolute; position: absolute;
width: 38px; width: 36px;
height: 38px; height: 36px;
mask-image: url('$(res)/img/icon-jump-to-first-unread.svg'); mask-image: url('$(res)/img/feather-customised/chevron-down-thin.svg');
mask-repeat: no-repeat; mask-repeat: no-repeat;
mask-position: 9px 13px; mask-size: contain;
background: $muted-fg-color; background: $muted-fg-color;
transform: rotate(180deg);
} }
.mx_TopUnreadMessagesBar_markAsRead { .mx_TopUnreadMessagesBar_markAsRead {

View file

@ -0,0 +1,37 @@
/*
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.
*/
.mx_NonUrgentEchoFailureToast {
.mx_NonUrgentEchoFailureToast_icon {
display: inline-block;
width: $font-18px;
height: $font-18px;
mask-position: center;
mask-size: contain;
mask-repeat: no-repeat;
background-color: #fff; // we know that non-urgent toasts are always styled the same
mask-image: url('$(res)/img/element-icons/cloud-off.svg');
margin-right: 8px;
}
span { // includes the i18n block
vertical-align: middle;
}
.mx_AccessibleButton {
padding: 0;
}
}

View file

@ -0,0 +1,3 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.53033 0.46967C1.23744 0.176777 0.762563 0.176777 0.46967 0.46967C0.176777 0.762563 0.176777 1.23744 0.46967 1.53033L4.3982 5.45886C3.81109 6.13809 3.38896 7.01315 3.21555 7.99387C1.96379 8.20624 1 9.465 1 10.981C1 12.6455 2.16209 14 3.59014 14H12.9393L16.4697 17.5303C16.7626 17.8232 17.2374 17.8232 17.5303 17.5303C17.8232 17.2374 17.8232 16.7626 17.5303 16.4697L1.53033 0.46967ZM17 10.9817C16.998 11.8303 16.6946 12.5985 16.2081 13.1475L7.07635 4.01569C7.18805 4.00529 7.30083 4 7.41451 4C8.75982 4 9.99711 4.71787 10.8072 5.94503C11.0993 5.85476 11.4011 5.80939 11.7058 5.80939C13.0303 5.80939 14.2138 6.65743 14.8199 8.00337C16.0519 8.23522 17 9.48685 17 10.9817Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 839 B

View file

@ -0,0 +1,3 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 7.5L9 10.5L12 7.5" stroke="black" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 217 B

View file

@ -1,3 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 9L12 15L18 9" stroke="#2E2F32" stroke-linecap="round" stroke-linejoin="round"/> <path d="M6 7.5L9 10.5L12 7.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 195 B

After

Width:  |  Height:  |  Size: 217 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-right"><polyline points="9 18 15 12 9 6"></polyline></svg>

Before

Width:  |  Height:  |  Size: 270 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-up"><polyline points="18 15 12 9 6 15"></polyline></svg>

Before

Width:  |  Height:  |  Size: 268 B

View file

@ -1,32 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
id="svg4"
width="18.666187"
height="8.7375822"
viewBox="0 0 18.666187 8.7375818"
version="1.1"
style="fill:none">
<metadata
id="metadata8">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8" />
<path
id="path2-8-3"
style="fill:none;stroke:#000000;stroke-width:1.29999995;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 17.909095,0.75599092 9.3330939,7.987602 0.75709259,0.75599092" />
</svg>

Before

Width:  |  Height:  |  Size: 1,016 B

View file

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
style="fill:none"
version="1.1"
viewBox="0 0 18.666187 8.7375818"
height="8.7375822"
width="18.666187">
<defs
id="defs8" />
<path
d="M 17.909095,7.981591 9.3330939,0.74997995 0.75709259,7.981591"
style="fill:none;stroke:#000000;stroke-width:1.29999995;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path2-8-3" />
</svg>

Before

Width:  |  Height:  |  Size: 559 B

View file

@ -18,6 +18,10 @@ $primary-fg-color: $text-primary-color;
$primary-bg-color: $bg-color; $primary-bg-color: $bg-color;
$muted-fg-color: $header-panel-text-primary-color; $muted-fg-color: $header-panel-text-primary-color;
// additional text colors
$secondary-fg-color: #A9B2BC;
$tertiary-fg-color: #8E99A4;
// used for dialog box text // used for dialog box text
$light-fg-color: $header-panel-text-secondary-color; $light-fg-color: $header-panel-text-secondary-color;
@ -112,10 +116,10 @@ $theme-button-bg-color: #e3e8f0;
$roomlist-button-bg-color: rgba(141, 151, 165, 0.2); // Buttons include the filter box, explore button, and sublist buttons $roomlist-button-bg-color: rgba(141, 151, 165, 0.2); // Buttons include the filter box, explore button, and sublist buttons
$roomlist-bg-color: rgba(33, 38, 44, 0.90); $roomlist-bg-color: rgba(33, 38, 44, 0.90);
$roomlist-header-color: #8E99A4; $roomlist-header-color: $tertiary-fg-color;
$roomsublist-divider-color: $primary-fg-color; $roomsublist-divider-color: $primary-fg-color;
$roomtile-preview-color: #A9B2BC; $roomtile-preview-color: $secondary-fg-color;
$roomtile-default-badge-bg-color: #61708b; $roomtile-default-badge-bg-color: #61708b;
$roomtile-selected-bg-color: rgba(141, 151, 165, 0.2); $roomtile-selected-bg-color: rgba(141, 151, 165, 0.2);

View file

@ -19,8 +19,8 @@ $accent-bg-color: rgba(3, 179, 129, 0.16);
$notice-primary-color: #ff4b55; $notice-primary-color: #ff4b55;
$notice-primary-bg-color: rgba(255, 75, 85, 0.16); $notice-primary-bg-color: rgba(255, 75, 85, 0.16);
$primary-fg-color: #2e2f32; $primary-fg-color: #2e2f32;
$roomlist-header-color: $primary-fg-color; $secondary-fg-color: #737D8C;
$notice-secondary-color: $roomlist-header-color; $tertiary-fg-color: #8D99A5;
$header-panel-bg-color: #f3f8fd; $header-panel-bg-color: #f3f8fd;
// typical text (dark-on-white in light skin) // typical text (dark-on-white in light skin)
@ -52,10 +52,6 @@ $info-bg-color: #2A9EDF;
$mention-user-pill-bg-color: $warning-color; $mention-user-pill-bg-color: $warning-color;
$other-user-pill-bg-color: rgba(0, 0, 0, 0.1); $other-user-pill-bg-color: rgba(0, 0, 0, 0.1);
// pinned events indicator
$pinned-unread-color: $notice-primary-color;
$pinned-color: $notice-secondary-color;
// informational plinth // informational plinth
$info-plinth-bg-color: #f7f7f7; $info-plinth-bg-color: #f7f7f7;
$info-plinth-fg-color: #888; $info-plinth-fg-color: #888;
@ -178,9 +174,10 @@ $theme-button-bg-color: #e3e8f0;
$roomlist-button-bg-color: #fff; // Buttons include the filter box, explore button, and sublist buttons $roomlist-button-bg-color: #fff; // Buttons include the filter box, explore button, and sublist buttons
$roomlist-bg-color: rgba(245, 245, 245, 0.90); $roomlist-bg-color: rgba(245, 245, 245, 0.90);
$roomlist-header-color: $tertiary-fg-color;
$roomsublist-divider-color: $primary-fg-color; $roomsublist-divider-color: $primary-fg-color;
$roomtile-preview-color: #737D8C; $roomtile-preview-color: $secondary-fg-color;
$roomtile-default-badge-bg-color: #61708b; $roomtile-default-badge-bg-color: #61708b;
$roomtile-selected-bg-color: #FFF; $roomtile-selected-bg-color: #FFF;
@ -199,8 +196,14 @@ $username-variant6-color: #2dc2c5;
$username-variant7-color: #5c56f5; $username-variant7-color: #5c56f5;
$username-variant8-color: #74d12c; $username-variant8-color: #74d12c;
$notice-secondary-color: $roomlist-header-color;
$panel-divider-color: transparent; $panel-divider-color: transparent;
// pinned events indicator
$pinned-unread-color: $notice-primary-color;
$pinned-color: $notice-secondary-color;
// ******************** // ********************
$widget-menu-bar-bg-color: $secondary-accent-color; $widget-menu-bar-bg-color: $secondary-accent-color;

View file

@ -18,7 +18,7 @@ limitations under the License.
/** /**
* Regenerates the translations en_EN file by walking the source tree and * Regenerates the translations en_EN file by walking the source tree and
* parsing each file with flow-parser. Emits a JSON file with the * parsing each file with the appropriate parser. Emits a JSON file with the
* translatable strings mapped to themselves in the order they appeared * translatable strings mapped to themselves in the order they appeared
* in the files and grouped by the file they appeared in. * in the files and grouped by the file they appeared in.
* *
@ -29,8 +29,8 @@ const path = require('path');
const walk = require('walk'); const walk = require('walk');
const flowParser = require('flow-parser'); const parser = require("@babel/parser");
const estreeWalker = require('estree-walker'); const traverse = require("@babel/traverse");
const TRANSLATIONS_FUNCS = ['_t', '_td']; const TRANSLATIONS_FUNCS = ['_t', '_td'];
@ -44,17 +44,9 @@ const OUTPUT_FILE = 'src/i18n/strings/en_EN.json';
// to a project that's actively maintained. // to a project that's actively maintained.
const SEARCH_PATHS = ['src', 'res']; const SEARCH_PATHS = ['src', 'res'];
const FLOW_PARSER_OPTS = {
esproposal_class_instance_fields: true,
esproposal_class_static_fields: true,
esproposal_decorators: true,
esproposal_export_star_as: true,
types: true,
};
function getObjectValue(obj, key) { function getObjectValue(obj, key) {
for (const prop of obj.properties) { for (const prop of obj.properties) {
if (prop.key.type == 'Identifier' && prop.key.name == key) { if (prop.key.type === 'Identifier' && prop.key.name === key) {
return prop.value; return prop.value;
} }
} }
@ -62,11 +54,11 @@ function getObjectValue(obj, key) {
} }
function getTKey(arg) { function getTKey(arg) {
if (arg.type == 'Literal') { if (arg.type === 'Literal' || arg.type === "StringLiteral") {
return arg.value; return arg.value;
} else if (arg.type == 'BinaryExpression' && arg.operator == '+') { } else if (arg.type === 'BinaryExpression' && arg.operator === '+') {
return getTKey(arg.left) + getTKey(arg.right); return getTKey(arg.left) + getTKey(arg.right);
} else if (arg.type == 'TemplateLiteral') { } else if (arg.type === 'TemplateLiteral') {
return arg.quasis.map((q) => { return arg.quasis.map((q) => {
return q.value.raw; return q.value.raw;
}).join(''); }).join('');
@ -110,17 +102,44 @@ function getFormatStrings(str) {
} }
function getTranslationsJs(file) { function getTranslationsJs(file) {
const tree = flowParser.parse(fs.readFileSync(file, { encoding: 'utf8' }), FLOW_PARSER_OPTS); const contents = fs.readFileSync(file, { encoding: 'utf8' });
const trs = new Set(); const trs = new Set();
estreeWalker.walk(tree, { try {
enter: function(node, parent) { const plugins = [
if ( // https://babeljs.io/docs/en/babel-parser#plugins
node.type == 'CallExpression' && "classProperties",
TRANSLATIONS_FUNCS.includes(node.callee.name) "objectRestSpread",
) { "throwExpressions",
"exportDefaultFrom",
"decorators-legacy",
];
if (file.endsWith(".js") || file.endsWith(".jsx")) {
// all JS is assumed to be flow or react
plugins.push("flow", "jsx");
} else if (file.endsWith(".ts")) {
// TS can't use JSX unless it's a TSX file (otherwise angle casts fail)
plugins.push("typescript");
} else if (file.endsWith(".tsx")) {
// When the file is a TSX file though, enable JSX parsing
plugins.push("typescript", "jsx");
}
const babelParsed = parser.parse(contents, {
allowImportExportEverywhere: true,
errorRecovery: true,
sourceFilename: file,
tokens: true,
plugins,
});
traverse.default(babelParsed, {
enter: (p) => {
const node = p.node;
if (p.isCallExpression() && node.callee && TRANSLATIONS_FUNCS.includes(node.callee.name)) {
const tKey = getTKey(node.arguments[0]); const tKey = getTKey(node.arguments[0]);
// This happens whenever we call _t with non-literals (ie. whenever we've // This happens whenever we call _t with non-literals (ie. whenever we've
// had to use a _td to compensate) so is expected. // had to use a _td to compensate) so is expected.
if (tKey === null) return; if (tKey === null) return;
@ -164,7 +183,7 @@ function getTranslationsJs(file) {
} }
let isPlural = false; let isPlural = false;
if (node.arguments.length > 1 && node.arguments[1].type == 'ObjectExpression') { if (node.arguments.length > 1 && node.arguments[1].type === 'ObjectExpression') {
const countVal = getObjectValue(node.arguments[1], 'count'); const countVal = getObjectValue(node.arguments[1], 'count');
if (countVal) { if (countVal) {
isPlural = true; isPlural = true;
@ -183,8 +202,12 @@ function getTranslationsJs(file) {
trs.add(tKey); trs.add(tKey);
} }
} }
} },
}); });
} catch (e) {
console.error(e);
process.exit(1);
}
return trs; return trs;
} }

View file

@ -14,7 +14,11 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { JSXElementConstructor } from "react";
// Based on https://stackoverflow.com/a/53229857/3532235 // Based on https://stackoverflow.com/a/53229857/3532235
export type Without<T, U> = {[P in Exclude<keyof T, keyof U>] ? : never}; export type Without<T, U> = {[P in Exclude<keyof T, keyof U>] ? : never};
export type XOR<T, U> = (T | U) extends object ? (Without<T, U> & U) | (Without<U, T> & T) : T | U; export type XOR<T, U> = (T | U) extends object ? (Without<T, U> & U) | (Without<U, T> & T) : T | U;
export type Writeable<T> = { -readonly [P in keyof T]: T[P] }; export type Writeable<T> = { -readonly [P in keyof T]: T[P] };
export type ComponentClass = keyof JSX.IntrinsicElements | JSXElementConstructor<any>;

View file

@ -25,6 +25,7 @@ import { PlatformPeg } from "../PlatformPeg";
import RoomListLayoutStore from "../stores/room-list/RoomListLayoutStore"; import RoomListLayoutStore from "../stores/room-list/RoomListLayoutStore";
import {IntegrationManagers} from "../integrations/IntegrationManagers"; import {IntegrationManagers} from "../integrations/IntegrationManagers";
import {ModalManager} from "../Modal"; import {ModalManager} from "../Modal";
import SettingsStore from "../settings/SettingsStore";
declare global { declare global {
interface Window { interface Window {
@ -43,6 +44,7 @@ declare global {
mxPlatformPeg: PlatformPeg; mxPlatformPeg: PlatformPeg;
mxIntegrationManagers: typeof IntegrationManagers; mxIntegrationManagers: typeof IntegrationManagers;
singletonModalManager: ModalManager; singletonModalManager: ModalManager;
mxSettingsStore: SettingsStore;
} }
// workaround for https://github.com/microsoft/TypeScript/issues/30933 // workaround for https://github.com/microsoft/TypeScript/issues/30933

View file

@ -62,10 +62,11 @@ import Matrix from 'matrix-js-sdk';
import dis from './dispatcher/dispatcher'; import dis from './dispatcher/dispatcher';
import WidgetUtils from './utils/WidgetUtils'; import WidgetUtils from './utils/WidgetUtils';
import WidgetEchoStore from './stores/WidgetEchoStore'; import WidgetEchoStore from './stores/WidgetEchoStore';
import SettingsStore, { SettingLevel } from './settings/SettingsStore'; import SettingsStore from './settings/SettingsStore';
import {generateHumanReadableId} from "./utils/NamingUtils"; import {generateHumanReadableId} from "./utils/NamingUtils";
import {Jitsi} from "./widgets/Jitsi"; import {Jitsi} from "./widgets/Jitsi";
import {WidgetType} from "./widgets/WidgetType"; import {WidgetType} from "./widgets/WidgetType";
import {SettingLevel} from "./settings/SettingLevel";
global.mxCalls = { global.mxCalls = {
//room_id: MatrixCall //room_id: MatrixCall

View file

@ -15,7 +15,8 @@
*/ */
import * as Matrix from 'matrix-js-sdk'; import * as Matrix from 'matrix-js-sdk';
import SettingsStore, {SettingLevel} from "./settings/SettingsStore"; import SettingsStore from "./settings/SettingsStore";
import {SettingLevel} from "./settings/SettingLevel";
export default { export default {
hasAnyLabeledDevices: async function() { hasAnyLabeledDevices: async function() {

View file

@ -256,7 +256,7 @@ class _MatrixClientPeg implements IMatrixClientPeg {
deviceId: creds.deviceId, deviceId: creds.deviceId,
pickleKey: creds.pickleKey, pickleKey: creds.pickleKey,
timelineSupport: true, timelineSupport: true,
forceTURN: !SettingsStore.getValue('webRtcAllowPeerToPeer', false), forceTURN: !SettingsStore.getValue('webRtcAllowPeerToPeer'),
fallbackICEServerAllowed: !!SettingsStore.getValue('fallbackICEServerAllowed'), fallbackICEServerAllowed: !!SettingsStore.getValue('fallbackICEServerAllowed'),
verificationMethods: [ verificationMethods: [
verificationMethods.SAS, verificationMethods.SAS,

View file

@ -27,10 +27,11 @@ import dis from './dispatcher/dispatcher';
import * as sdk from './index'; import * as sdk from './index';
import { _t } from './languageHandler'; import { _t } from './languageHandler';
import Modal from './Modal'; import Modal from './Modal';
import SettingsStore, {SettingLevel} from "./settings/SettingsStore"; import SettingsStore from "./settings/SettingsStore";
import { import {
hideToast as hideNotificationsToast, hideToast as hideNotificationsToast,
} from "./toasts/DesktopNotificationsToast"; } from "./toasts/DesktopNotificationsToast";
import {SettingLevel} from "./settings/SettingLevel";
/* /*
* Dispatches: * Dispatches:

View file

@ -20,9 +20,10 @@ import PropTypes from 'prop-types';
import dis from "../../../../dispatcher/dispatcher"; import dis from "../../../../dispatcher/dispatcher";
import { _t } from '../../../../languageHandler'; import { _t } from '../../../../languageHandler';
import SettingsStore, {SettingLevel} from "../../../../settings/SettingsStore"; import SettingsStore from "../../../../settings/SettingsStore";
import EventIndexPeg from "../../../../indexing/EventIndexPeg"; import EventIndexPeg from "../../../../indexing/EventIndexPeg";
import {Action} from "../../../../dispatcher/actions"; import {Action} from "../../../../dispatcher/actions";
import {SettingLevel} from "../../../../settings/SettingLevel";
/* /*
* Allows the user to disable the Event Index. * Allows the user to disable the Event Index.

View file

@ -19,11 +19,12 @@ import * as sdk from '../../../../index';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { _t } from '../../../../languageHandler'; import { _t } from '../../../../languageHandler';
import SdkConfig from '../../../../SdkConfig'; import SdkConfig from '../../../../SdkConfig';
import SettingsStore, {SettingLevel} from "../../../../settings/SettingsStore"; import SettingsStore from "../../../../settings/SettingsStore";
import Modal from '../../../../Modal'; import Modal from '../../../../Modal';
import {formatBytes, formatCountLong} from "../../../../utils/FormattingUtils"; import {formatBytes, formatCountLong} from "../../../../utils/FormattingUtils";
import EventIndexPeg from "../../../../indexing/EventIndexPeg"; import EventIndexPeg from "../../../../indexing/EventIndexPeg";
import {SettingLevel} from "../../../../settings/SettingLevel";
/* /*
* Allows the user to introspect the event index state and disable it. * Allows the user to introspect the event index state and disable it.

View file

@ -210,6 +210,11 @@ const FilePanel = createReactClass({
const TimelinePanel = sdk.getComponent("structures.TimelinePanel"); const TimelinePanel = sdk.getComponent("structures.TimelinePanel");
const Loader = sdk.getComponent("elements.Spinner"); const Loader = sdk.getComponent("elements.Spinner");
const emptyState = (<div className="mx_RightPanel_empty mx_FilePanel_empty">
<h2>{_t('No files visible in this room')}</h2>
<p>{_t('Attach files from chat or just drag and drop them anywhere in a room.')}</p>
</div>);
if (this.state.timelineSet) { if (this.state.timelineSet) {
// console.log("rendering TimelinePanel for timelineSet " + this.state.timelineSet.room.roomId + " " + // console.log("rendering TimelinePanel for timelineSet " + this.state.timelineSet.room.roomId + " " +
// "(" + this.state.timelineSet._timelines.join(", ") + ")" + " with key " + this.props.roomId); // "(" + this.state.timelineSet._timelines.join(", ") + ")" + " with key " + this.props.roomId);
@ -223,7 +228,7 @@ const FilePanel = createReactClass({
onPaginationRequest={this.onPaginationRequest} onPaginationRequest={this.onPaginationRequest}
tileShape="file_grid" tileShape="file_grid"
resizeNotifier={this.props.resizeNotifier} resizeNotifier={this.props.resizeNotifier}
empty={_t('There are no visible files in this room')} empty={emptyState}
/> />
</div> </div>
); );

View file

@ -54,6 +54,7 @@ import LeftPanel from "./LeftPanel";
import CallContainer from '../views/voip/CallContainer'; import CallContainer from '../views/voip/CallContainer';
import { ViewRoomDeltaPayload } from "../../dispatcher/payloads/ViewRoomDeltaPayload"; import { ViewRoomDeltaPayload } from "../../dispatcher/payloads/ViewRoomDeltaPayload";
import RoomListStore from "../../stores/room-list/RoomListStore"; import RoomListStore from "../../stores/room-list/RoomListStore";
import NonUrgentToastContainer from "./NonUrgentToastContainer";
import { ToggleRightPanelPayload } from "../../dispatcher/payloads/ToggleRightPanelPayload"; import { ToggleRightPanelPayload } from "../../dispatcher/payloads/ToggleRightPanelPayload";
// We need to fetch each pinned message individually (if we don't already have it) // We need to fetch each pinned message individually (if we don't already have it)
@ -688,6 +689,7 @@ class LoggedInView extends React.Component<IProps, IState> {
</DragDropContext> </DragDropContext>
</div> </div>
<CallContainer /> <CallContainer />
<NonUrgentToastContainer />
</MatrixClientContext.Provider> </MatrixClientContext.Provider>
); );
} }

View file

@ -51,7 +51,7 @@ import { getHomePageUrl } from '../../utils/pages';
import createRoom from "../../createRoom"; import createRoom from "../../createRoom";
import {_t, _td, getCurrentLanguage} from '../../languageHandler'; import {_t, _td, getCurrentLanguage} from '../../languageHandler';
import SettingsStore, { SettingLevel } from "../../settings/SettingsStore"; import SettingsStore from "../../settings/SettingsStore";
import ThemeController from "../../settings/controllers/ThemeController"; import ThemeController from "../../settings/controllers/ThemeController";
import { startAnyRegistrationFlow } from "../../Registration.js"; import { startAnyRegistrationFlow } from "../../Registration.js";
import { messageForSyncError } from '../../utils/ErrorUtils'; import { messageForSyncError } from '../../utils/ErrorUtils';
@ -75,6 +75,7 @@ import {showToast as showNotificationsToast} from "../../toasts/DesktopNotificat
import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload"; import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
import ErrorDialog from "../views/dialogs/ErrorDialog"; import ErrorDialog from "../views/dialogs/ErrorDialog";
import { RoomNotificationStateStore } from "../../stores/notifications/RoomNotificationStateStore"; import { RoomNotificationStateStore } from "../../stores/notifications/RoomNotificationStateStore";
import { SettingLevel } from "../../settings/SettingLevel";
/** constants for MatrixChat.state.view */ /** constants for MatrixChat.state.view */
export enum Views { export enum Views {

View file

@ -0,0 +1,63 @@
/*
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 * as React from "react";
import { ComponentClass } from "../../@types/common";
import NonUrgentToastStore from "../../stores/NonUrgentToastStore";
import { UPDATE_EVENT } from "../../stores/AsyncStore";
interface IProps {
}
interface IState {
toasts: ComponentClass[],
}
export default class NonUrgentToastContainer extends React.PureComponent<IProps, IState> {
public constructor(props, context) {
super(props, context);
this.state = {
toasts: NonUrgentToastStore.instance.components,
};
NonUrgentToastStore.instance.on(UPDATE_EVENT, this.onUpdateToasts);
}
public componentWillUnmount() {
NonUrgentToastStore.instance.off(UPDATE_EVENT, this.onUpdateToasts);
}
private onUpdateToasts = () => {
this.setState({toasts: NonUrgentToastStore.instance.components});
};
public render() {
const toasts = this.state.toasts.map((t, i) => {
return (
<div className="mx_NonUrgentToastContainer_toast" key={`toast-${i}`}>
{React.createElement(t, {})}
</div>
);
});
return (
<div className="mx_NonUrgentToastContainer" role="alert">
{toasts}
</div>
);
}
}

View file

@ -36,6 +36,11 @@ const NotificationPanel = createReactClass({
const TimelinePanel = sdk.getComponent("structures.TimelinePanel"); const TimelinePanel = sdk.getComponent("structures.TimelinePanel");
const Loader = sdk.getComponent("elements.Spinner"); const Loader = sdk.getComponent("elements.Spinner");
const emptyState = (<div className="mx_RightPanel_empty mx_NotificationPanel_empty">
<h2>{_t('Youre all caught up')}</h2>
<p>{_t('You have no visible notifications in this room.')}</p>
</div>);
const timelineSet = MatrixClientPeg.get().getNotifTimelineSet(); const timelineSet = MatrixClientPeg.get().getNotifTimelineSet();
if (timelineSet) { if (timelineSet) {
return ( return (
@ -46,7 +51,7 @@ const NotificationPanel = createReactClass({
timelineSet={timelineSet} timelineSet={timelineSet}
showUrlPreview={false} showUrlPreview={false}
tileShape="notif" tileShape="notif"
empty={_t('You have no visible notifications')} empty={emptyState}
/> />
</div> </div>
); );

View file

@ -48,7 +48,7 @@ import RightPanel from './RightPanel';
import RoomViewStore from '../../stores/RoomViewStore'; import RoomViewStore from '../../stores/RoomViewStore';
import RoomScrollStateStore from '../../stores/RoomScrollStateStore'; import RoomScrollStateStore from '../../stores/RoomScrollStateStore';
import WidgetEchoStore from '../../stores/WidgetEchoStore'; import WidgetEchoStore from '../../stores/WidgetEchoStore';
import SettingsStore, {SettingLevel} from "../../settings/SettingsStore"; import SettingsStore from "../../settings/SettingsStore";
import AccessibleButton from "../views/elements/AccessibleButton"; import AccessibleButton from "../views/elements/AccessibleButton";
import RightPanelStore from "../../stores/RightPanelStore"; import RightPanelStore from "../../stores/RightPanelStore";
import {haveTileForEvent} from "../views/rooms/EventTile"; import {haveTileForEvent} from "../views/rooms/EventTile";
@ -56,6 +56,7 @@ import RoomContext from "../../contexts/RoomContext";
import MatrixClientContext from "../../contexts/MatrixClientContext"; import MatrixClientContext from "../../contexts/MatrixClientContext";
import { shieldStatusForRoom } from '../../utils/ShieldUtils'; import { shieldStatusForRoom } from '../../utils/ShieldUtils';
import {Action} from "../../dispatcher/actions"; import {Action} from "../../dispatcher/actions";
import {SettingLevel} from "../../settings/SettingLevel";
const DEBUG = false; const DEBUG = false;
let debuglog = function() {}; let debuglog = function() {};

View file

@ -26,7 +26,7 @@ import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
import RedesignFeedbackDialog from "../views/dialogs/RedesignFeedbackDialog"; import RedesignFeedbackDialog from "../views/dialogs/RedesignFeedbackDialog";
import Modal from "../../Modal"; import Modal from "../../Modal";
import LogoutDialog from "../views/dialogs/LogoutDialog"; import LogoutDialog from "../views/dialogs/LogoutDialog";
import SettingsStore, {SettingLevel} from "../../settings/SettingsStore"; import SettingsStore from "../../settings/SettingsStore";
import {getCustomTheme} from "../../theme"; import {getCustomTheme} from "../../theme";
import {getHostingLink} from "../../utils/HostingLink"; import {getHostingLink} from "../../utils/HostingLink";
import {ButtonEvent} from "../views/elements/AccessibleButton"; import {ButtonEvent} from "../views/elements/AccessibleButton";
@ -37,6 +37,7 @@ import { UPDATE_EVENT } from "../../stores/AsyncStore";
import BaseAvatar from '../views/avatars/BaseAvatar'; import BaseAvatar from '../views/avatars/BaseAvatar';
import classNames from "classnames"; import classNames from "classnames";
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton"; import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
import { SettingLevel } from "../../settings/SettingLevel";
interface IProps { interface IProps {
isMinimized: boolean; isMinimized: boolean;

View file

@ -16,10 +16,11 @@ limitations under the License.
import SdkConfig from "../../../SdkConfig"; import SdkConfig from "../../../SdkConfig";
import {getCurrentLanguage} from "../../../languageHandler"; import {getCurrentLanguage} from "../../../languageHandler";
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import PlatformPeg from "../../../PlatformPeg"; import PlatformPeg from "../../../PlatformPeg";
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import React from 'react'; import React from 'react';
import {SettingLevel} from "../../../settings/SettingLevel";
function onChange(newLang) { function onChange(newLang) {
if (getCurrentLanguage() !== newLang) { if (getCurrentLanguage() !== newLang) {

View file

@ -19,8 +19,8 @@ import PropTypes from 'prop-types';
import createReactClass from 'create-react-class'; import createReactClass from 'create-react-class';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import {SettingLevel} from "../../../settings/SettingsStore";
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import {SettingLevel} from "../../../settings/SettingLevel";
export default createReactClass({ export default createReactClass({
propTypes: { propTypes: {

View file

@ -0,0 +1,124 @@
/*
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 * as React from 'react';
import BaseDialog from './BaseDialog';
import { _t } from '../../../languageHandler';
import { EchoStore } from "../../../stores/local-echo/EchoStore";
import { formatTime } from "../../../DateUtils";
import SettingsStore from "../../../settings/SettingsStore";
import { RoomEchoContext } from "../../../stores/local-echo/RoomEchoContext";
import RoomAvatar from "../avatars/RoomAvatar";
import { TransactionStatus } from "../../../stores/local-echo/EchoTransaction";
import Spinner from "../elements/Spinner";
import AccessibleButton from "../elements/AccessibleButton";
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
interface IProps {
onFinished: (bool) => void;
}
export default class ServerOfflineDialog extends React.PureComponent<IProps> {
public componentDidMount() {
EchoStore.instance.on(UPDATE_EVENT, this.onEchosUpdated);
}
public componentWillUnmount() {
EchoStore.instance.off(UPDATE_EVENT, this.onEchosUpdated);
}
private onEchosUpdated = () => {
this.forceUpdate(); // no state to worry about
};
private renderTimeline(): React.ReactElement[] {
return EchoStore.instance.contexts.map((c, i) => {
if (!c.firstFailedTime) return null; // not useful
if (!(c instanceof RoomEchoContext)) throw new Error("Cannot render unknown context: " + c);
const header = (
<div className="mx_ServerOfflineDialog_content_context_timeline_header">
<RoomAvatar width={24} height={24} room={c.room} />
<span>{c.room.name}</span>
</div>
);
const entries = c.transactions
.filter(t => t.status === TransactionStatus.Error || t.didPreviouslyFail)
.map((t, j) => {
let button = <Spinner w={19} h={19} />;
if (t.status === TransactionStatus.Error) {
button = (
<AccessibleButton kind="link" onClick={() => t.run()}>{_t("Resend")}</AccessibleButton>
);
}
return (
<div className="mx_ServerOfflineDialog_content_context_txn" key={`txn-${j}`}>
<span className="mx_ServerOfflineDialog_content_context_txn_desc">
{t.auditName}
</span>
{button}
</div>
);
});
return (
<div className="mx_ServerOfflineDialog_content_context" key={`context-${i}`}>
<div className="mx_ServerOfflineDialog_content_context_timestamp">
{formatTime(c.firstFailedTime, SettingsStore.getValue("showTwelveHourTimestamps"))}
</div>
<div className="mx_ServerOfflineDialog_content_context_timeline">
{header}
{entries}
</div>
</div>
)
});
}
public render() {
let timeline = this.renderTimeline().filter(c => !!c); // remove nulls for next check
if (timeline.length === 0) {
timeline = [<div key={1}>{_t("You're all caught up.")}</div>];
}
const serverName = MatrixClientPeg.getHomeserverName();
return <BaseDialog title={_t("Server isn't responding")}
className='mx_ServerOfflineDialog'
contentId='mx_Dialog_content'
onFinished={this.props.onFinished}
hasCancel={true}
>
<div className="mx_ServerOfflineDialog_content">
<p>{_t(
"Your server isn't responding to some of your requests. " +
"Below are some of the most likely reasons.",
)}</p>
<ul>
<li>{_t("The server (%(serverName)s) took too long to respond.", {serverName})}</li>
<li>{_t("Your firewall or anti-virus is blocking the request.")}</li>
<li>{_t("A browser extension is preventing the request.")}</li>
<li>{_t("The server is offline.")}</li>
<li>{_t("The server has denied your request.")}</li>
<li>{_t("Your area is experiencing difficulties connecting to the internet.")}</li>
<li>{_t("A connection error occurred while trying to contact the server.")}</li>
<li>{_t("The server is not configured to indicate what the problem is (CORS).")}</li>
</ul>
<hr />
<h2>{_t("Recent changes that have not yet been received")}</h2>
{timeline}
</div>
</BaseDialog>;
}
}

View file

@ -17,10 +17,11 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import {_t} from "../../../languageHandler"; import {_t} from "../../../languageHandler";
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import * as sdk from "../../../index"; import * as sdk from "../../../index";
import LabelledToggleSwitch from "../elements/LabelledToggleSwitch"; import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
import WidgetUtils from "../../../utils/WidgetUtils"; import WidgetUtils from "../../../utils/WidgetUtils";
import {SettingLevel} from "../../../settings/SettingLevel";
export default class WidgetOpenIDPermissionsDialog extends React.Component { export default class WidgetOpenIDPermissionsDialog extends React.Component {
static propTypes = { static propTypes = {

View file

@ -35,12 +35,13 @@ import dis from '../../../dispatcher/dispatcher';
import ActiveWidgetStore from '../../../stores/ActiveWidgetStore'; import ActiveWidgetStore from '../../../stores/ActiveWidgetStore';
import classNames from 'classnames'; import classNames from 'classnames';
import {IntegrationManagers} from "../../../integrations/IntegrationManagers"; import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import {aboveLeftOf, ContextMenu, ContextMenuButton} from "../../structures/ContextMenu"; import {aboveLeftOf, ContextMenu, ContextMenuButton} from "../../structures/ContextMenu";
import PersistedElement from "./PersistedElement"; import PersistedElement from "./PersistedElement";
import {WidgetType} from "../../../widgets/WidgetType"; import {WidgetType} from "../../../widgets/WidgetType";
import {Capability} from "../../../widgets/WidgetApi"; import {Capability} from "../../../widgets/WidgetApi";
import {sleep} from "../../../utils/promise"; import {sleep} from "../../../utils/promise";
import {SettingLevel} from "../../../settings/SettingLevel";
const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:']; const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:'];
const ENABLE_REACT_PERF = false; const ENABLE_REACT_PERF = false;

View file

@ -15,8 +15,9 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import Draggable, {ILocationState} from './Draggable'; import Draggable, {ILocationState} from './Draggable';
import { SettingLevel } from "../../../settings/SettingLevel";
interface IProps { interface IProps {
// Current room // Current room

View file

@ -20,11 +20,12 @@ import SettingsStore from "../../../settings/SettingsStore";
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import ToggleSwitch from "./ToggleSwitch"; import ToggleSwitch from "./ToggleSwitch";
import StyledCheckbox from "./StyledCheckbox"; import StyledCheckbox from "./StyledCheckbox";
import { SettingLevel } from "../../../settings/SettingLevel";
interface IProps { interface IProps {
// The setting must be a boolean // The setting must be a boolean
name: string; name: string;
level: string; level: SettingLevel;
roomId?: string; // for per-room settings roomId?: string; // for per-room settings
label?: string; // untranslated label?: string; // untranslated
isExplicit?: boolean; isExplicit?: boolean;
@ -52,8 +53,8 @@ export default class SettingsFlag extends React.Component<IProps, IState> {
}; };
} }
private onChange = (checked: boolean): void => { private onChange = async (checked: boolean) => {
this.save(checked); await this.save(checked);
this.setState({ value: checked }); this.setState({ value: checked });
if (this.props.onChange) this.props.onChange(checked); if (this.props.onChange) this.props.onChange(checked);
}; };
@ -62,8 +63,8 @@ export default class SettingsFlag extends React.Component<IProps, IState> {
this.onChange(e.target.checked); this.onChange(e.target.checked);
}; };
private save = (val?: boolean): void => { private save = async (val?: boolean) => {
return SettingsStore.setValue( await SettingsStore.setValue(
this.props.name, this.props.name,
this.props.roomId, this.props.roomId,
this.props.level, this.props.level,

View file

@ -550,7 +550,9 @@ const RedactMessagesButton = ({member}) => {
let eventsToRedact = []; let eventsToRedact = [];
while (timeline) { while (timeline) {
eventsToRedact = timeline.getEvents().reduce((events, event) => { eventsToRedact = timeline.getEvents().reduce((events, event) => {
if (event.getSender() === userId && !event.isRedacted() && !event.isRedaction()) { if (event.getSender() === userId && !event.isRedacted() && !event.isRedaction() &&
event.getType() !== "m.room.create"
) {
return events.concat(event); return events.concat(event);
} else { } else {
return events; return events;

View file

@ -20,7 +20,8 @@ import createReactClass from 'create-react-class';
import Tinter from '../../../Tinter'; import Tinter from '../../../Tinter';
import dis from '../../../dispatcher/dispatcher'; import dis from '../../../dispatcher/dispatcher';
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import {SettingLevel} from "../../../settings/SettingLevel";
const ROOM_COLORS = [ const ROOM_COLORS = [
// magic room default values courtesy of Ribot // magic room default values courtesy of Ribot

View file

@ -22,10 +22,11 @@ import PropTypes from 'prop-types';
import createReactClass from 'create-react-class'; import createReactClass from 'create-react-class';
import * as sdk from "../../../index"; import * as sdk from "../../../index";
import { _t, _td } from '../../../languageHandler'; import { _t, _td } from '../../../languageHandler';
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import dis from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher";
import {MatrixClientPeg} from "../../../MatrixClientPeg"; import {MatrixClientPeg} from "../../../MatrixClientPeg";
import {Action} from "../../../dispatcher/actions"; import {Action} from "../../../dispatcher/actions";
import {SettingLevel} from "../../../settings/SettingLevel";
export default createReactClass({ export default createReactClass({

View file

@ -21,7 +21,8 @@ import * as sdk from "../../../index";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import Modal from "../../../Modal"; import Modal from "../../../Modal";
import {MatrixClientPeg} from "../../../MatrixClientPeg"; import {MatrixClientPeg} from "../../../MatrixClientPeg";
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import {SettingLevel} from "../../../settings/SettingLevel";
export default class RoomRecoveryReminder extends React.PureComponent { export default class RoomRecoveryReminder extends React.PureComponent {
static propTypes = { static propTypes = {

View file

@ -23,6 +23,7 @@ import classNames from "classnames";
import { RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex"; import { RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex";
import AccessibleButton, { ButtonEvent } from "../../views/elements/AccessibleButton"; import AccessibleButton, { ButtonEvent } from "../../views/elements/AccessibleButton";
import dis from '../../../dispatcher/dispatcher'; import dis from '../../../dispatcher/dispatcher';
import defaultDispatcher from '../../../dispatcher/dispatcher';
import { Key } from "../../../Keyboard"; import { Key } from "../../../Keyboard";
import ActiveRoomObserver from "../../../ActiveRoomObserver"; import ActiveRoomObserver from "../../../ActiveRoomObserver";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
@ -30,31 +31,26 @@ import {
ChevronFace, ChevronFace,
ContextMenu, ContextMenu,
ContextMenuTooltipButton, ContextMenuTooltipButton,
MenuItemRadio,
MenuItemCheckbox,
MenuItem, MenuItem,
MenuItemCheckbox,
MenuItemRadio,
} from "../../structures/ContextMenu"; } from "../../structures/ContextMenu";
import { DefaultTagID, TagID } from "../../../stores/room-list/models"; import { DefaultTagID, TagID } from "../../../stores/room-list/models";
import { MessagePreviewStore, ROOM_PREVIEW_CHANGED } from "../../../stores/room-list/MessagePreviewStore"; import { MessagePreviewStore, ROOM_PREVIEW_CHANGED } from "../../../stores/room-list/MessagePreviewStore";
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar"; import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
import { import { ALL_MESSAGES, ALL_MESSAGES_LOUD, MENTIONS_ONLY, MUTE, } from "../../../RoomNotifs";
getRoomNotifsState,
setRoomNotifsState,
ALL_MESSAGES,
ALL_MESSAGES_LOUD,
MENTIONS_ONLY,
MUTE,
} from "../../../RoomNotifs";
import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { MatrixClientPeg } from "../../../MatrixClientPeg";
import NotificationBadge from "./NotificationBadge"; import NotificationBadge from "./NotificationBadge";
import { Volume } from "../../../RoomNotifsTypes"; import { Volume } from "../../../RoomNotifsTypes";
import RoomListStore from "../../../stores/room-list/RoomListStore"; import RoomListStore from "../../../stores/room-list/RoomListStore";
import RoomListActions from "../../../actions/RoomListActions"; import RoomListActions from "../../../actions/RoomListActions";
import defaultDispatcher from "../../../dispatcher/dispatcher";
import { ActionPayload } from "../../../dispatcher/payloads"; import { ActionPayload } from "../../../dispatcher/payloads";
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore"; import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
import { NOTIFICATION_STATE_UPDATE, NotificationState } from "../../../stores/notifications/NotificationState"; import { NOTIFICATION_STATE_UPDATE, NotificationState } from "../../../stores/notifications/NotificationState";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { EchoChamber } from "../../../stores/local-echo/EchoChamber";
import { CachedRoomKey, RoomEchoChamber } from "../../../stores/local-echo/RoomEchoChamber";
import { PROPERTY_UPDATED } from "../../../stores/local-echo/GenericEchoChamber";
interface IProps { interface IProps {
room: Room; room: Room;
@ -112,6 +108,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
private dispatcherRef: string; private dispatcherRef: string;
private roomTileRef = createRef<HTMLDivElement>(); private roomTileRef = createRef<HTMLDivElement>();
private notificationState: NotificationState; private notificationState: NotificationState;
private roomProps: RoomEchoChamber;
constructor(props: IProps) { constructor(props: IProps) {
super(props); super(props);
@ -130,12 +127,19 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
MessagePreviewStore.instance.on(ROOM_PREVIEW_CHANGED, this.onRoomPreviewChanged); MessagePreviewStore.instance.on(ROOM_PREVIEW_CHANGED, this.onRoomPreviewChanged);
this.notificationState = RoomNotificationStateStore.instance.getRoomState(this.props.room); this.notificationState = RoomNotificationStateStore.instance.getRoomState(this.props.room);
this.notificationState.on(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate); this.notificationState.on(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate);
this.roomProps = EchoChamber.forRoom(this.props.room);
this.roomProps.on(PROPERTY_UPDATED, this.onRoomPropertyUpdate);
} }
private onNotificationUpdate = () => { private onNotificationUpdate = () => {
this.forceUpdate(); // notification state changed - update this.forceUpdate(); // notification state changed - update
}; };
private onRoomPropertyUpdate = (property: CachedRoomKey) => {
if (property === CachedRoomKey.NotificationVolume) this.onNotificationUpdate();
// else ignore - not important for this tile
};
private get showContextMenu(): boolean { private get showContextMenu(): boolean {
return !this.props.isMinimized && this.props.tag !== DefaultTagID.Invite; return !this.props.isMinimized && this.props.tag !== DefaultTagID.Invite;
} }
@ -307,17 +311,9 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
ev.stopPropagation(); ev.stopPropagation();
if (MatrixClientPeg.get().isGuest()) return; if (MatrixClientPeg.get().isGuest()) return;
// get key before we go async and React discards the nativeEvent this.roomProps.notificationVolume = newState;
const key = (ev as React.KeyboardEvent).key;
try {
// TODO add local echo - https://github.com/vector-im/riot-web/issues/14280
await setRoomNotifsState(this.props.room.roomId, newState);
} catch (error) {
// TODO: some form of error notification to the user to inform them that their state change failed.
// See https://github.com/vector-im/riot-web/issues/14281
console.error(error);
}
const key = (ev as React.KeyboardEvent).key;
if (key === Key.ENTER) { if (key === Key.ENTER) {
// Implements https://www.w3.org/TR/wai-aria-practices/#keyboard-interaction-12 // Implements https://www.w3.org/TR/wai-aria-practices/#keyboard-interaction-12
this.setState({notificationsMenuPosition: null}); // hide the menu this.setState({notificationsMenuPosition: null}); // hide the menu
@ -335,7 +331,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
return null; return null;
} }
const state = getRoomNotifsState(this.props.room.roomId); const state = this.roomProps.notificationVolume;
let contextMenu = null; let contextMenu = null;
if (this.state.notificationsMenuPosition) { if (this.state.notificationsMenuPosition) {

View file

@ -18,7 +18,7 @@ import React from 'react';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import {_t} from "../../../languageHandler"; import {_t} from "../../../languageHandler";
import {SettingLevel} from "../../../settings/SettingsStore"; import {SettingLevel} from "../../../settings/SettingLevel";
const SETTING_MANUALLY_VERIFY_ALL_SESSIONS = "e2ee.manuallyVerifyAllSessions"; const SETTING_MANUALLY_VERIFY_ALL_SESSIONS = "e2ee.manuallyVerifyAllSessions";

View file

@ -20,10 +20,11 @@ import { _t } from '../../../languageHandler';
import SdkConfig from "../../../SdkConfig"; import SdkConfig from "../../../SdkConfig";
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import Modal from '../../../Modal'; import Modal from '../../../Modal';
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import AccessibleButton from "../elements/AccessibleButton"; import AccessibleButton from "../elements/AccessibleButton";
import {formatBytes, formatCountLong} from "../../../utils/FormattingUtils"; import {formatBytes, formatCountLong} from "../../../utils/FormattingUtils";
import EventIndexPeg from "../../../indexing/EventIndexPeg"; import EventIndexPeg from "../../../indexing/EventIndexPeg";
import {SettingLevel} from "../../../settings/SettingLevel";
export default class EventIndexPanel extends React.Component { export default class EventIndexPanel extends React.Component {
constructor() { constructor() {

View file

@ -20,7 +20,7 @@ import createReactClass from 'create-react-class';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {MatrixClientPeg} from '../../../MatrixClientPeg';
import SettingsStore, {SettingLevel} from '../../../settings/SettingsStore'; import SettingsStore from '../../../settings/SettingsStore';
import Modal from '../../../Modal'; import Modal from '../../../Modal';
import { import {
NotificationUtils, NotificationUtils,
@ -31,6 +31,7 @@ import {
import SdkConfig from "../../../SdkConfig"; import SdkConfig from "../../../SdkConfig";
import LabelledToggleSwitch from "../elements/LabelledToggleSwitch"; import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
import AccessibleButton from "../elements/AccessibleButton"; import AccessibleButton from "../elements/AccessibleButton";
import {SettingLevel} from "../../../settings/SettingLevel";
// TODO: this "view" component still has far too much application logic in it, // TODO: this "view" component still has far too much application logic in it,
// which should be factored out to other files. // which should be factored out to other files.

View file

@ -18,7 +18,8 @@ import React from 'react';
import {_t} from "../../../languageHandler"; import {_t} from "../../../languageHandler";
import {IntegrationManagers} from "../../../integrations/IntegrationManagers"; import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
import * as sdk from '../../../index'; 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 { export default class SetIntegrationManager extends React.Component {
constructor() { constructor() {

View file

@ -21,7 +21,7 @@ import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
import AccessibleButton from "../../../elements/AccessibleButton"; import AccessibleButton from "../../../elements/AccessibleButton";
import Notifier from "../../../../../Notifier"; import Notifier from "../../../../../Notifier";
import SettingsStore from '../../../../../settings/SettingsStore'; import SettingsStore from '../../../../../settings/SettingsStore';
import { SettingLevel } from '../../../../../settings/SettingsStore'; import {SettingLevel} from "../../../../../settings/SettingLevel";
export default class NotificationsSettingsTab extends React.Component { export default class NotificationsSettingsTab extends React.Component {
static propTypes = { static propTypes = {

View file

@ -20,9 +20,9 @@ import {_t} from "../../../../../languageHandler";
import {MatrixClientPeg} from "../../../../../MatrixClientPeg"; import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
import * as sdk from "../../../../.."; import * as sdk from "../../../../..";
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch"; import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
import {SettingLevel} from "../../../../../settings/SettingsStore";
import Modal from "../../../../../Modal"; import Modal from "../../../../../Modal";
import QuestionDialog from "../../../dialogs/QuestionDialog"; import QuestionDialog from "../../../dialogs/QuestionDialog";
import {SettingLevel} from "../../../../../settings/SettingLevel";
export default class SecurityRoomSettingsTab extends React.Component { export default class SecurityRoomSettingsTab extends React.Component {
static propTypes = { static propTypes = {

View file

@ -18,7 +18,7 @@ limitations under the License.
import React from 'react'; import React from 'react';
import {_t} from "../../../../../languageHandler"; import {_t} from "../../../../../languageHandler";
import SdkConfig from "../../../../../SdkConfig"; import SdkConfig from "../../../../../SdkConfig";
import SettingsStore, {SettingLevel} from "../../../../../settings/SettingsStore"; import SettingsStore from "../../../../../settings/SettingsStore";
import { enumerateThemes } from "../../../../../theme"; import { enumerateThemes } from "../../../../../theme";
import ThemeWatcher from "../../../../../settings/watchers/ThemeWatcher"; import ThemeWatcher from "../../../../../settings/watchers/ThemeWatcher";
import Slider from "../../../elements/Slider"; import Slider from "../../../elements/Slider";
@ -35,6 +35,7 @@ import Field from '../../../elements/Field';
import EventTilePreview from '../../../elements/EventTilePreview'; import EventTilePreview from '../../../elements/EventTilePreview';
import StyledRadioGroup from "../../../elements/StyledRadioGroup"; import StyledRadioGroup from "../../../elements/StyledRadioGroup";
import classNames from 'classnames'; import classNames from 'classnames';
import { SettingLevel } from "../../../../../settings/SettingLevel";
interface IProps { interface IProps {
} }

View file

@ -20,7 +20,6 @@ import React from 'react';
import {_t} from "../../../../../languageHandler"; import {_t} from "../../../../../languageHandler";
import ProfileSettings from "../../ProfileSettings"; import ProfileSettings from "../../ProfileSettings";
import * as languageHandler from "../../../../../languageHandler"; import * as languageHandler from "../../../../../languageHandler";
import {SettingLevel} from "../../../../../settings/SettingsStore";
import SettingsStore from "../../../../../settings/SettingsStore"; import SettingsStore from "../../../../../settings/SettingsStore";
import LanguageDropdown from "../../../elements/LanguageDropdown"; import LanguageDropdown from "../../../elements/LanguageDropdown";
import AccessibleButton from "../../../elements/AccessibleButton"; import AccessibleButton from "../../../elements/AccessibleButton";
@ -37,6 +36,7 @@ import IdentityAuthClient from "../../../../../IdentityAuthClient";
import {abbreviateUrl} from "../../../../../utils/UrlUtils"; import {abbreviateUrl} from "../../../../../utils/UrlUtils";
import { getThreepidsWithBindStatus } from '../../../../../boundThreepids'; import { getThreepidsWithBindStatus } from '../../../../../boundThreepids';
import Spinner from "../../../elements/Spinner"; import Spinner from "../../../elements/Spinner";
import {SettingLevel} from "../../../../../settings/SettingLevel";
export default class GeneralUserSettingsTab extends React.Component { export default class GeneralUserSettingsTab extends React.Component {
static propTypes = { static propTypes = {

View file

@ -17,9 +17,10 @@ limitations under the License.
import React from 'react'; import React from 'react';
import {_t} from "../../../../../languageHandler"; import {_t} from "../../../../../languageHandler";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import SettingsStore, {SettingLevel} from "../../../../../settings/SettingsStore"; import SettingsStore from "../../../../../settings/SettingsStore";
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch"; import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
import * as sdk from "../../../../../index"; import * as sdk from "../../../../../index";
import {SettingLevel} from "../../../../../settings/SettingLevel";
export class LabsSettingToggle extends React.Component { export class LabsSettingToggle extends React.Component {
static propTypes = { static propTypes = {

View file

@ -17,12 +17,12 @@ limitations under the License.
import React from 'react'; import React from 'react';
import {_t} from "../../../../../languageHandler"; import {_t} from "../../../../../languageHandler";
import {SettingLevel} from "../../../../../settings/SettingsStore";
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch"; import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
import SettingsStore from "../../../../../settings/SettingsStore"; import SettingsStore from "../../../../../settings/SettingsStore";
import Field from "../../../elements/Field"; import Field from "../../../elements/Field";
import * as sdk from "../../../../.."; import * as sdk from "../../../../..";
import PlatformPeg from "../../../../../PlatformPeg"; import PlatformPeg from "../../../../../PlatformPeg";
import {SettingLevel} from "../../../../../settings/SettingLevel";
export default class PreferencesUserSettingsTab extends React.Component { export default class PreferencesUserSettingsTab extends React.Component {
static ROOM_LIST_SETTINGS = [ static ROOM_LIST_SETTINGS = [

View file

@ -19,7 +19,6 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import {_t} from "../../../../../languageHandler"; import {_t} from "../../../../../languageHandler";
import SdkConfig from "../../../../../SdkConfig"; import SdkConfig from "../../../../../SdkConfig";
import {SettingLevel} from "../../../../../settings/SettingsStore";
import {MatrixClientPeg} from "../../../../../MatrixClientPeg"; import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
import * as FormattingUtils from "../../../../../utils/FormattingUtils"; import * as FormattingUtils from "../../../../../utils/FormattingUtils";
import AccessibleButton from "../../../elements/AccessibleButton"; import AccessibleButton from "../../../elements/AccessibleButton";
@ -29,6 +28,7 @@ import * as sdk from "../../../../..";
import {sleep} from "../../../../../utils/promise"; import {sleep} from "../../../../../utils/promise";
import dis from "../../../../../dispatcher/dispatcher"; import dis from "../../../../../dispatcher/dispatcher";
import {privateShouldBeEncrypted} from "../../../../../createRoom"; import {privateShouldBeEncrypted} from "../../../../../createRoom";
import {SettingLevel} from "../../../../../settings/SettingLevel";
export class IgnoredUser extends React.Component { export class IgnoredUser extends React.Component {
static propTypes = { static propTypes = {

View file

@ -21,10 +21,10 @@ import SdkConfig from "../../../../../SdkConfig";
import CallMediaHandler from "../../../../../CallMediaHandler"; import CallMediaHandler from "../../../../../CallMediaHandler";
import Field from "../../../elements/Field"; import Field from "../../../elements/Field";
import AccessibleButton from "../../../elements/AccessibleButton"; import AccessibleButton from "../../../elements/AccessibleButton";
import {SettingLevel} from "../../../../../settings/SettingsStore";
import {MatrixClientPeg} from "../../../../../MatrixClientPeg"; import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
import * as sdk from "../../../../../index"; import * as sdk from "../../../../../index";
import Modal from "../../../../../Modal"; import Modal from "../../../../../Modal";
import {SettingLevel} from "../../../../../settings/SettingLevel";
export default class VoiceUserSettingsTab extends React.Component { export default class VoiceUserSettingsTab extends React.Component {
constructor() { constructor() {

View file

@ -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 (
<div className="mx_NonUrgentEchoFailureToast">
<span className="mx_NonUrgentEchoFailureToast_icon" />
{_t("Your server isn't responding to some <a>requests</a>.", {}, {
'a': (sub) => (
<AccessibleButton kind="link" onClick={this.openDialog}>{sub}</AccessibleButton>
),
})}
</div>
)
}
}

View file

@ -15,8 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import SettingsStore, {SettingLevel} from "../settings/SettingsStore"; import SettingsStore from "../settings/SettingsStore";
import {orderBy} from "lodash"; import {orderBy} from "lodash";
import { SettingLevel } from "../settings/SettingLevel";
interface ILegacyFormat { interface ILegacyFormat {
[emoji: string]: [number, number]; // [count, date] [emoji: string]: [number, number]; // [count, date]

View file

@ -443,6 +443,7 @@
"%(senderName)s: %(message)s": "%(senderName)s: %(message)s", "%(senderName)s: %(message)s": "%(senderName)s: %(message)s",
"%(senderName)s: %(reaction)s": "%(senderName)s: %(reaction)s", "%(senderName)s: %(reaction)s": "%(senderName)s: %(reaction)s",
"%(senderName)s: %(stickerName)s": "%(senderName)s: %(stickerName)s", "%(senderName)s: %(stickerName)s": "%(senderName)s: %(stickerName)s",
"Change notification settings": "Change notification settings",
"New spinner design": "New spinner design", "New spinner design": "New spinner design",
"Message Pinning": "Message Pinning", "Message Pinning": "Message Pinning",
"Custom user status messages": "Custom user status messages", "Custom user status messages": "Custom user status messages",
@ -613,6 +614,7 @@
"Headphones": "Headphones", "Headphones": "Headphones",
"Folder": "Folder", "Folder": "Folder",
"Pin": "Pin", "Pin": "Pin",
"Your server isn't responding to some <a>requests</a>.": "Your server isn't responding to some <a>requests</a>.",
"From %(deviceName)s (%(deviceId)s)": "From %(deviceName)s (%(deviceId)s)", "From %(deviceName)s (%(deviceId)s)": "From %(deviceName)s (%(deviceId)s)",
"Decline (%(counter)s)": "Decline (%(counter)s)", "Decline (%(counter)s)": "Decline (%(counter)s)",
"Accept <policyLink /> to continue:": "Accept <policyLink /> to continue:", "Accept <policyLink /> to continue:": "Accept <policyLink /> 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.", "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 <a>report a bug</a>.": "This usually only affects how the room is processed on the server. If you're having problems with your %(brand)s, please <a>report a bug</a>.", "This usually only affects how the room is processed on the server. If you're having problems with your %(brand)s, please <a>report a bug</a>.": "This usually only affects how the room is processed on the server. If you're having problems with your %(brand)s, please <a>report a bug</a>.",
"You'll upgrade this room from <oldVersion /> to <newVersion />.": "You'll upgrade this room from <oldVersion /> to <newVersion />.", "You'll upgrade this room from <oldVersion /> to <newVersion />.": "You'll upgrade this room from <oldVersion /> to <newVersion />.",
"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?", "Sign out and remove encryption keys?": "Sign out and remove encryption keys?",
"Clear Storage and Sign Out": "Clear Storage and Sign Out", "Clear Storage and Sign Out": "Clear Storage and Sign Out",
"Send Logs": "Send Logs", "Send Logs": "Send Logs",
@ -1852,7 +1867,6 @@
"Reject invitation": "Reject invitation", "Reject invitation": "Reject invitation",
"Are you sure you want to reject the invitation?": "Are you sure you want to reject the 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", "Unable to reject invite": "Unable to reject invite",
"Resend": "Resend",
"Resend edit": "Resend edit", "Resend edit": "Resend edit",
"Resend %(unsentCount)s reaction(s)": "Resend %(unsentCount)s reaction(s)", "Resend %(unsentCount)s reaction(s)": "Resend %(unsentCount)s reaction(s)",
"Resend removal": "Resend removal", "Resend removal": "Resend removal",
@ -1960,7 +1974,8 @@
"Couldn't load page": "Couldn't load page", "Couldn't load page": "Couldn't load page",
"You must <a>register</a> to use this functionality": "You must <a>register</a> to use this functionality", "You must <a>register</a> to use this functionality": "You must <a>register</a> to use this functionality",
"You must join the room to see its files": "You must join the room to see its files", "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.",
"<h1>HTML for your community's page</h1>\n<p>\n Use the long description to introduce new members to the community, or distribute\n some important <a href=\"foo\">links</a>\n</p>\n<p>\n You can even use 'img' tags\n</p>\n": "<h1>HTML for your community's page</h1>\n<p>\n Use the long description to introduce new members to the community, or distribute\n some important <a href=\"foo\">links</a>\n</p>\n<p>\n You can even use 'img' tags\n</p>\n", "<h1>HTML for your community's page</h1>\n<p>\n Use the long description to introduce new members to the community, or distribute\n some important <a href=\"foo\">links</a>\n</p>\n<p>\n You can even use 'img' tags\n</p>\n": "<h1>HTML for your community's page</h1>\n<p>\n Use the long description to introduce new members to the community, or distribute\n some important <a href=\"foo\">links</a>\n</p>\n<p>\n You can even use 'img' tags\n</p>\n",
"Add rooms to the community summary": "Add rooms to the community summary", "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?", "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", "Communities": "Communities",
"Create a new community": "Create a new community", "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.", "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", "Youre all caught up": "Youre 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 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.", "%(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.", "The homeserver may be unavailable or overloaded.": "The homeserver may be unavailable or overloaded.",

View file

@ -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.", "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", "Verify yourself & others to keep your chats safe": "Kontrolu vin mem kaj aliajn por sekurigi viajn babilojn",
"Channel: %(channelName)s": "Kanalo: %(channelName)s", "Channel: %(channelName)s": "Kanalo: %(channelName)s",
"Show less": "Montri pli", "Show less": "Montri malpli",
"Show more": "Montri malpli", "Show more": "Montri pli",
"Help": "Helpo", "Help": "Helpo",
"Session verified": "Salutaĵo kontroliĝis", "Session verified": "Salutaĵo kontroliĝis",
"Copy": "Kopii", "Copy": "Kopii",
@ -2376,7 +2376,7 @@
"Set a Security Phrase": "Agordi Sekurecan frazon", "Set a Security Phrase": "Agordi Sekurecan frazon",
"Confirm Security Phrase": "Konfirmi Sekurecan frazon", "Confirm Security Phrase": "Konfirmi Sekurecan frazon",
"Save your Security Key": "Konservi vian Sekurecan ŝlosilon", "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 rooms with unread messages first": "Montri ĉambrojn kun nelegitaj mesaĝoj kiel unuajn",
"Show previews of messages": "Montri antaŭrigardojn al mesaĝoj", "Show previews of messages": "Montri antaŭrigardojn al mesaĝoj",
"This room is public": "Ĉi tiu ĉambro estas publika", "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?", "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", "Enable advanced debugging for the room list": "Ŝalti altnivelan erarserĉadon por la ĉambrobreto",
"* %(senderName)s %(emote)s": "* %(senderName)s %(emote)s", "* %(senderName)s %(emote)s": "* %(senderName)s %(emote)s",
"Custom Tag": "Propra etikedo" "Custom Tag": "Propra etikedo",
"Feedback": "Prikomenti"
} }

View file

@ -1714,10 +1714,10 @@
"<strong>%(role)s</strong> in %(roomName)s": "<strong>%(role)s</strong> jututoas %(roomName)s", "<strong>%(role)s</strong> in %(roomName)s": "<strong>%(role)s</strong> jututoas %(roomName)s",
"Failed to change power level": "Õiguste muutmine ei õnnestunud", "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.", "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?", "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 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?", "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": "Blokeeri kasutaja", "Deactivate user": "Deaktiveeri kasutaja",
"Failed to deactivate user": "Kasutaja blokeerimine ei õnnestunud", "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.", "This client does not support end-to-end encryption.": "See klient ei toeta läbivat krüptimist.",
"Security": "Turvalisus", "Security": "Turvalisus",
"Using this widget may share data <helpIcon /> with %(widgetDomain)s & your Integration Manager.": "Selle vidina kasutamisel võidakse jagada andmeid <helpIcon /> saitidega %(widgetDomain)s ning sinu vidinahalduriga.", "Using this widget may share data <helpIcon /> with %(widgetDomain)s & your Integration Manager.": "Selle vidina kasutamisel võidakse jagada andmeid <helpIcon /> 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.", "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.", "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 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."
} }

View file

@ -326,7 +326,7 @@
"Upload an avatar:": "Igo abatarra:", "Upload an avatar:": "Igo abatarra:",
"This server does not support authentication with a phone number.": "Zerbitzari honek ez du telefono zenbakia erabiliz autentifikatzea onartzen.", "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", "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.", "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)|one": "(~%(count)s emaitza)",
"(~%(count)s results)|other": "(~%(count)s emaitza)", "(~%(count)s results)|other": "(~%(count)s emaitza)",
@ -734,7 +734,7 @@
"All Rooms": "Gela guztiak", "All Rooms": "Gela guztiak",
"Wednesday": "Asteazkena", "Wednesday": "Asteazkena",
"You cannot delete this message. (%(code)s)": "Ezin duzu mezu hau ezabatu. (%(code)s)", "You cannot delete this message. (%(code)s)": "Ezin duzu mezu hau ezabatu. (%(code)s)",
"Quote": "Aipua", "Quote": "Aipatu",
"Send logs": "Bidali egunkariak", "Send logs": "Bidali egunkariak",
"All messages": "Mezu guztiak", "All messages": "Mezu guztiak",
"Call invitation": "Dei gonbidapena", "Call invitation": "Dei gonbidapena",
@ -1539,7 +1539,7 @@
"View": "Ikusi", "View": "Ikusi",
"Find a room…": "Bilatu gela bat…", "Find a room…": "Bilatu gela bat…",
"Find a room… (e.g. %(exampleRoom)s)": "Bilatu gela bat… (adib. %(exampleRoom)s)", "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 <a>Create a new room</a>.": "Ezin baduzu bila ari zaren gela aurkitu, eskatu gonbidapen bat edo <a>Sortu gela berri bat</a>.", "If you can't find the room you're looking for, ask for an invite or <a>Create a new room</a>.": "Ez baduzu bilatzen duzuna aurkitzen, eskatu gonbidapen bat edo <a>Sortu gela berri bat</a>.",
"Explore rooms": "Arakatu gelak", "Explore rooms": "Arakatu gelak",
"Community Autocomplete": "Komunitate osatze automatikoa", "Community Autocomplete": "Komunitate osatze automatikoa",
"Emoji Autocomplete": "Emoji 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.", "If your other sessions do not have the key for this message you will not be able to decrypt them.": "Zure beste saioek ez badute mezu honen gakoa ezin izango duzu deszifratu.",
"<requestLink>Re-request encryption keys</requestLink> from your other sessions.": "<requestLink>Eskatu berriro zifratze gakoak</requestLink> zure beste saioei.", "<requestLink>Re-request encryption keys</requestLink> from your other sessions.": "<requestLink>Eskatu berriro zifratze gakoak</requestLink> zure beste saioei.",
"Waiting for %(displayName)s to accept…": "%(displayName)s(e)k onartu bitartean zain…", "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:", "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.", "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.", "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", "Security & privacy": "Segurtasuna eta pribatutasuna",
"All settings": "Ezarpen guztiak", "All settings": "Ezarpen guztiak",
"Feedback": "Iruzkinak", "Feedback": "Iruzkinak",
"Use a different passphrase?": "Erabili pasa-esaldi desberdin bat?" "Use a different passphrase?": "Erabili pasa-esaldi desberdin bat?",
"Were 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"
} }

View file

@ -2388,5 +2388,7 @@
"Click to view edits": "Preme para ver as edicións", "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?", "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", "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."
} }

View file

@ -2387,5 +2387,22 @@
"Click to view edits": "A szerkesztések megtekintéséhez kattints", "Click to view edits": "A szerkesztések megtekintéséhez kattints",
"Youre already signed in and good to go here, but you can also grab the latest versions of the app on all platforms at <a>element.io/get-started</a>.": "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: <a>element.io/get-started</a>.", "Youre already signed in and good to go here, but you can also grab the latest versions of the app on all platforms at <a>element.io/get-started</a>.": "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: <a>element.io/get-started</a>.",
"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.", "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 <a>element.io</a>.": "Add meg az Element Matrix Services matrix szerveredet. Használhatod a saját domain-edet vagy az <a>element.io</a> 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 <a>element.io</a>.": "Add meg az Element Matrix Services matrix szerveredet. Használhatod a saját domain-edet vagy az <a>element.io</a> 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 <a>requests</a>.": "A szervered nem válaszol néhány <a>kérésre</a>.",
"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"
} }

View file

@ -298,7 +298,7 @@
"Low Priority": "Lítill forgangur", "Low Priority": "Lítill forgangur",
"Direct Chat": "Beint spjall", "Direct Chat": "Beint spjall",
"View Community": "Skoða samfélag", "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", "Name": "Nafn",
"Failed to upload image": "Gat ekki sent inn mynd", "Failed to upload image": "Gat ekki sent inn mynd",
"Add rooms to this community": "Bæta spjallrásum í þetta samfélag", "Add rooms to this community": "Bæta spjallrásum í þetta samfélag",
@ -456,5 +456,7 @@
"Notify the whole room": "Tilkynna öllum á spjallrásinni", "Notify the whole room": "Tilkynna öllum á spjallrásinni",
"Room Notification": "Tilkynning á spjallrás", "Room Notification": "Tilkynning á spjallrás",
"Passphrases must match": "Lykilfrasar verða að stemma", "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 <chromeLink>Chrome</chromeLink>, <firefoxLink>Firefox</firefoxLink>, or <safariLink>Safari</safariLink> for the best experience.": "vinsamlegast setja upp <chromeLink>Chrome</chromeLink>, <firefoxLink>Firefox</firefoxLink>, eða <safariLink>Safari</safariLink> fyrir besta reynsluna."
} }

View file

@ -1360,5 +1360,15 @@
"Leave Room": "部屋を退出", "Leave Room": "部屋を退出",
"Failed to connect to integration manager": "インテグレーションマネージャへの接続に失敗しました", "Failed to connect to integration manager": "インテグレーションマネージャへの接続に失敗しました",
"Start verification again from their profile.": "プロフィールから再度検証を開始してください。", "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 <policyLink /> to continue:": "<policyLink /> に同意して続行:",
"Always show the window menu bar": "常にウィンドウメニューバーを表示する",
"Create room": "部屋を作成",
"Show %(count)s more|other": "さらに %(count)s 件を表示",
"Show %(count)s more|one": "さらに %(count)s 件を表示"
} }

View file

@ -3,7 +3,7 @@
"This phone number is already in use": ".i xa'o pilno fa da le fonxa judri", "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", "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", "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", "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", "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", "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", "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", "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", "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", "Sun": "jy. dy. ze",
"Mon": "jy. dy. pa", "Mon": "jy. dy. pa",
"Tue": "jy. dy. re", "Tue": "jy. dy. re",
@ -100,7 +100,7 @@
"/ddg is not a command": "zoi ny. /ddg .ny. na nu minde", "/ddg is not a command": "zoi ny. /ddg .ny. na nu minde",
"Changes your display nickname": "", "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", "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", "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", "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", "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", "Accept": "nu fonjo'e",
"Error": "nabmi", "Error": "nabmi",
"Incorrect verification code": ".i na'e drani ke lacri lerpoi", "Incorrect verification code": ".i na'e drani ke lacri lerpoi",
"Submit": "benji", "Submit": "nu zilbe'i",
"Phone": "lo fonxa", "Phone": "fonxa",
"Add": "jmina", "Add": "jmina",
"Failed to upload profile picture!": ".i da nabmi lo nu kibdu'a le pixra sinxa", "Failed to upload profile picture!": ".i da nabmi lo nu kibdu'a le pixra sinxa",
"No display name": ".i na da cmene", "No display name": ".i na da cmene",

View file

@ -9,7 +9,7 @@
"Are you sure you want to reject the invitation?": "Você tem certeza que deseja rejeitar este convite?", "Are you sure you want to reject the invitation?": "Você tem certeza que deseja rejeitar este convite?",
"Banned users": "Usuárias/os banidas/os", "Banned users": "Usuárias/os banidas/os",
"Bans user with given id": "Banir usuários com o identificador informado", "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", "Changes your display nickname": "Troca o seu apelido",
"Click here to fix": "Clique aqui para resolver isso", "Click here to fix": "Clique aqui para resolver isso",
"Commands": "Comandos", "Commands": "Comandos",
@ -29,7 +29,7 @@
"Failed to leave room": "Falha ao tentar deixar a sala", "Failed to leave room": "Falha ao tentar deixar a sala",
"Failed to reject invitation": "Falha ao tentar rejeitar convite", "Failed to reject invitation": "Falha ao tentar rejeitar convite",
"Failed to unban": "Não foi possível desfazer o banimento", "Failed to unban": "Não foi possível desfazer o banimento",
"Favourite": "Favorito", "Favourite": "Favoritar",
"Favourites": "Favoritos", "Favourites": "Favoritos",
"Filter room members": "Filtrar integrantes da sala", "Filter room members": "Filtrar integrantes da sala",
"Forget room": "Esquecer sala", "Forget room": "Esquecer sala",
@ -66,7 +66,7 @@
"Privileged Users": "Usuárias/os privilegiadas/os", "Privileged Users": "Usuárias/os privilegiadas/os",
"Profile": "Perfil", "Profile": "Perfil",
"Reject invitation": "Rejeitar convite", "Reject invitation": "Rejeitar convite",
"Remove": "Remover", "Remove": "Apagar",
"Return to login screen": "Retornar à tela de login", "Return to login screen": "Retornar à tela de login",
"Room Colour": "Cores da sala", "Room Colour": "Cores da sala",
"Rooms": "Salas", "Rooms": "Salas",
@ -275,7 +275,7 @@
"Failed to invite": "Falha ao enviar o convite", "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:", "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", "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", "Unknown error": "Erro desconhecido",
"Incorrect password": "Senha incorreta", "Incorrect password": "Senha incorreta",
"Unable to restore session": "Não foi possível restaurar a sessão", "Unable to restore session": "Não foi possível restaurar a sessão",
@ -311,17 +311,17 @@
"Invited": "Convidada(o)", "Invited": "Convidada(o)",
"Results from DuckDuckGo": "Resultados de DuckDuckGo", "Results from DuckDuckGo": "Resultados de DuckDuckGo",
"Verified key": "Chave verificada", "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 avatar for %(roomName)s": "%(senderDisplayName)s alterou a imagem da sala %(roomName)s",
"%(senderDisplayName)s changed the room avatar to <img/>": "%(senderDisplayName)s alterou a imagem da sala para <img/>", "%(senderDisplayName)s changed the room avatar to <img/>": "%(senderDisplayName)s alterou a foto da sala para <img/>",
"No Microphones detected": "Não foi detectado nenhum microfone", "No Microphones detected": "Não foi detectado nenhum microfone",
"No Webcams detected": "Não foi detectada nenhuma Webcam", "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", "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", "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", "Microphone": "Microfone",
"Camera": "Câmera de vídeo", "Camera": "Câmera de vídeo",
"Add a topic": "Adicionar um tópico", "Add a topic": "Adicionar uma descrição",
"Anyone": "Qualquer pessoa", "Anyone": "Qualquer pessoa",
"Are you sure you want to leave the room '%(roomName)s'?": "Você tem certeza que deseja sair da sala '%(roomName)s'?", "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", "Custom level": "Nível personalizado",
@ -400,7 +400,7 @@
"Edit": "Editar", "Edit": "Editar",
"Unpin Message": "Desafixar Mensagem", "Unpin Message": "Desafixar Mensagem",
"Add rooms to this community": "Adicionar salas na comunidade", "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", "The platform you're on": "A plataforma que você está usando",
"Your language of choice": "O idioma que você selecionou", "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", "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", "Off": "Desativado",
"Mentions only": "Apenas menções", "Mentions only": "Apenas menções",
"Wednesday": "Quarta", "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", "Enable email notifications": "Ativar notificações por email",
"Event Type": "Tipo do Evento", "Event Type": "Tipo do Evento",
"Download this file": "Baixar este arquivo", "Download this file": "Baixar este arquivo",
@ -762,7 +762,7 @@
"Checking for an update...": "Verificando se há atualizações...", "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", "Every page you use in the app": "Toda a página que você usa no aplicativo",
"e.g. <CurrentPageURL>": "ex. <CurrentPageURL>", "e.g. <CurrentPageURL>": "ex. <CurrentPageURL>",
"Your device resolution": "Sua resolução de dispositivo", "Your device resolution": "A resolução do seu aparelho",
"Call in Progress": "Chamada em andamento", "Call in Progress": "Chamada em andamento",
"A call is currently being placed!": "Uma chamada está sendo feita atualmente!", "A call is currently being placed!": "Uma chamada está sendo feita atualmente!",
"A call is already in progress!": "Uma chamada já está em andamento!", "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", "Unable to load key backup status": "Não é possível carregar o status da chave de backup",
"Backup version: ": "Versão do Backup: ", "Backup version: ": "Versão do Backup: ",
"Algorithm: ": "Algoritmo: ", "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", "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", "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.", "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: ", "Failed to send logs: ": "Falha ao enviar registros: ",
"Submit debug logs": "Submeter registros de depuração", "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.", "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 <a>create a GitHub issue</a> to describe your problem.": "Antes de enviar os registros, você deve <a>criar uma questão no GitHub</a> para descrever seu problema.", "Before submitting logs, you must <a>create a GitHub issue</a> to describe your problem.": "Antes de enviar os registros, você deve <a>criar um bilhete de erro no GitHub</a> para descrever seu problema.",
"Unable to load commit detail: %(msg)s": "Não é possível carregar os detalhes do commit: %(msg)s", "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", "Incompatible Database": "Banco de dados incompatível",
"Continue With Encryption Disabled": "Continuar com criptografia desativada", "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. <b>This action is irreversible.</b>": "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. <b>Esta ação é irreversível.</ b>", "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. <b>This action is irreversible.</b>": "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. <b>Esta ação é irreversível.</ b>",
@ -933,7 +933,7 @@
"You can't send any messages until you review and agree to <consentLink>our terms and conditions</consentLink>.": "Você não pode enviar nenhuma mensagem até revisar e concordar com <consentLink>nossos termos e condições</consentLink>.", "You can't send any messages until you review and agree to <consentLink>our terms and conditions</consentLink>.": "Você não pode enviar nenhuma mensagem até revisar e concordar com <consentLink>nossos termos e condições</consentLink>.",
"Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please <a>contact your service administrator</a> to continue using the service.": "Sua mensagem não foi enviada porque este homeserver atingiu seu Limite de usuário ativo mensal. Por favor, <a>entre em contato com o seu administrador de serviços</a> para continuar usando o serviço.", "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please <a>contact your service administrator</a> to continue using the service.": "Sua mensagem não foi enviada porque este homeserver atingiu seu Limite de usuário ativo mensal. Por favor, <a>entre em contato com o seu administrador de serviços</a> para continuar usando o serviço.",
"Your message wasn't sent because this homeserver has exceeded a resource limit. Please <a>contact your service administrator</a> to continue using the service.": "Sua mensagem não foi enviada porque este homeserver excedeu o limite de recursos. Por favor, <a>entre em contato com o seu administrador de serviços</a> para continuar usando o serviço.", "Your message wasn't sent because this homeserver has exceeded a resource limit. Please <a>contact your service administrator</a> to continue using the service.": "Sua mensagem não foi enviada porque este homeserver excedeu o limite de recursos. Por favor, <a>entre em contato com o seu administrador de serviços</a> 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", "Legal": "Legal",
"No Audio Outputs detected": "Nenhuma saída de áudio detectada", "No Audio Outputs detected": "Nenhuma saída de áudio detectada",
"Audio Output": "Saída de áudio", "Audio Output": "Saída de áudio",
@ -945,7 +945,7 @@
"Sign in with single sign-on": "Entre com o logon único", "Sign in with single sign-on": "Entre com o logon único",
"That matches!": "Isto corresponde!", "That matches!": "Isto corresponde!",
"That doesn't match.": "Isto não 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", "Download": "Baixar",
"<b>Print it</b> and store it somewhere safe": "<b>Imprima-o</ b> e armazene-o em algum lugar seguro", "<b>Print it</b> and store it somewhere safe": "<b>Imprima-o</ b> e armazene-o em algum lugar seguro",
"<b>Save it</b> on a USB key or backup drive": "<b>Salve isto</ b> em uma chave USB ou unidade de backup", "<b>Save it</b> on a USB key or backup drive": "<b>Salve isto</ b> em uma chave USB ou unidade de backup",
@ -968,8 +968,8 @@
"Invite anyway": "Convide mesmo assim", "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)", "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", "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", "Gets or sets the room topic": "Consultar ou definir a descrição da sala",
"This room has no topic.": "Esta sala não tem assunto.", "This room has no topic.": "Esta sala não tem descrição.",
"Sets the room name": "Define o nome da sala", "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)", "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", "Render simple counters in room header": "Renderizar contadores simples no cabeçalho da sala",
@ -1127,19 +1127,19 @@
"Room version:": "Versão da sala:", "Room version:": "Versão da sala:",
"Developer options": "Opções de desenvolvedor", "Developer options": "Opções de desenvolvedor",
"Room Addresses": "Endereços da sala", "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 room name": "Alterar nome da sala",
"Change main address for the room": "Alterar o endereço principal 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 history visibility": "Alterar a visibilidade do histórico",
"Change permissions": "Alterar permissões", "Change permissions": "Alterar permissões",
"Change topic": "Alterar o tópico", "Change topic": "Alterar a descrição",
"Modify widgets": "Modificar widgets", "Modify widgets": "Modificar widgets",
"Default role": "Papel padrão", "Default role": "Papel padrão",
"Send messages": "Enviar mensagens", "Send messages": "Enviar mensagens",
"Invite users": "Convidar usuários", "Invite users": "Convidar usuários",
"Use Single Sign On to continue": "Use \"Single Sign On\" para continuar", "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.", "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", "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.", "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", "Confirm": "Confirmar",
@ -1147,17 +1147,17 @@
"Confirm adding phone number": "Confirmar adição de número de telefone", "Confirm adding phone number": "Confirmar adição de número de telefone",
"Add Phone Number": "Adicionar 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 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)", "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)", "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", "Call failed due to misconfigured server": "A chamada caiu por conta de má configuração no servidor",
"Please ask the administrator of your homeserver (<code>%(homeserverDomain)s</code>) to configure a TURN server in order for calls to work reliably.": "Por favor, peça aos administradores do seu servidor (<code>%(homeserverDomain)s</code>) para configurar um servidor TURN para que as chamadas funcionem de forma estável.", "Please ask the administrator of your homeserver (<code>%(homeserverDomain)s</code>) to configure a TURN server in order for calls to work reliably.": "Por favor, peça ao administrador do seu servidor (<code>%(homeserverDomain)s</code>) 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 <code>turn.matrix.org</code>, 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 <code>turn.matrix.org</code>, mas não será tão fiável e partilhará o seu IP com esse servidor. Também pode gerir isso nas definições.", "Alternatively, you can try to use the public server at <code>turn.matrix.org</code>, 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 <code>turn.matrix.org</code>. 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": "Tentar utilizar turn.matrix.org", "Try using turn.matrix.org": "Tente utilizar turn.matrix.org",
"Replying With Files": "Responder com arquivos", "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?", "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 arquivo '%(fileName)s' não pôde ser enviado.", "The file '%(fileName)s' failed to upload.": "O envio do arquivo '%(fileName)s' falhou.",
"The server does not support the room version specified.": "Este servidor não suporta a versão de sala especificada.", "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?", "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?", "Are you sure you want to cancel entering passphrase?": "Tem certeza que quer cancelar a introdução da frase de senha?",
"Go Back": "Voltar", "Go Back": "Voltar",
@ -1189,7 +1189,7 @@
"Changes the avatar of the current room": "Altera a imagem da sala atual", "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 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", "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": "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.", "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", "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", "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", "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.", "%(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.|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 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.", "%(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.": "%(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 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 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 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 uma regra banindo salas correspondendo a", "%(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 banindo servidores correspondendo a", "%(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", "%(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 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 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", "%(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", "%(num)s days from now": "dentro de %(num)s dias",
"%(name)s (%(userId)s)": "%(name)s (%(userId)s)", "%(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.", "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 <UsageDataLink>anonymous usage data</UsageDataLink> which helps us improve %(brand)s. This will use a <PolicyLink>cookie</PolicyLink>.": "Envie <UsageDataLink>dados anônimos de uso</UsageDataLink> que nos ajudam a melhorar o %(brand)s. Isso necessitará do uso de um <PolicyLink>cookie</PolicyLink>.", "Send <UsageDataLink>anonymous usage data</UsageDataLink> which helps us improve %(brand)s. This will use a <PolicyLink>cookie</PolicyLink>.": "Envie <UsageDataLink>dados anônimos de uso</UsageDataLink> que nos ajudam a melhorar o %(brand)s. Isso necessitará do uso de um <PolicyLink>cookie</PolicyLink>.",
"I want to help": "Quero ajudar", "I want to help": "Quero ajudar",
"Review where youre logged in": "Revisar onde você está logada(o)", "Review where youre logged in": "Revisar onde você está logada(o)",
@ -1291,7 +1291,7 @@
"Review": "Revisar", "Review": "Revisar",
"Later": "Mais tarde", "Later": "Mais tarde",
"Your homeserver has exceeded its user limit.": "Seu servidor ultrapassou seu limite de usuárias(os).", "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 <a>server admin</a>.": "Entre em contato com sua(seu) <a>administrador(a) do servidor</a>.", "Contact your <a>server admin</a>.": "Entre em contato com sua(seu) <a>administrador(a) do servidor</a>.",
"Ok": "Ok", "Ok": "Ok",
"Set password": "Definir senha", "Set password": "Definir senha",
@ -1301,12 +1301,12 @@
"Verify this session": "Verificar esta sessão", "Verify this session": "Verificar esta sessão",
"Upgrade": "Atualizar", "Upgrade": "Atualizar",
"Verify": "Verificar", "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", "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ê?", "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", "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!", "A new version of %(brand)s is available!": "Uma nova versão do %(brand)s está disponível!",
"Guest": "Convidada(o)", "Guest": "Convidada(o)",
"You joined the call": "Você entrou na chamada", "You joined the call": "Você entrou na chamada",
@ -1319,10 +1319,10 @@
"%(senderName)s started a call": "%(senderName)s iniciou uma chamada", "%(senderName)s started a call": "%(senderName)s iniciou uma chamada",
"Waiting for answer": "Esperando por uma resposta", "Waiting for answer": "Esperando por uma resposta",
"%(senderName)s is calling": "%(senderName)s está chamando", "%(senderName)s is calling": "%(senderName)s está chamando",
"* %(senderName)s %(emote)s": "* %(senderName)s", "* %(senderName)s %(emote)s": "* %(senderName)s %(emote)s",
"%(senderName)s: %(message)s": "%(senderName)s:", "%(senderName)s: %(message)s": "%(senderName)s: %(message)s",
"%(senderName)s: %(reaction)s": "%(senderName)s:", "%(senderName)s: %(reaction)s": "%(senderName)s: %(reaction)s",
"%(senderName)s: %(stickerName)s": "%(senderName)s:", "%(senderName)s: %(stickerName)s": "%(senderName)s: %(stickerName)s",
"New spinner design": "Novo design do spinner", "New spinner design": "Novo design do spinner",
"Multiple integration managers": "Múltiplos gestores de integrações", "Multiple integration managers": "Múltiplos gestores de integrações",
"Try out new ways to ignore people (experimental)": "Tente novas maneiras de ignorar pessoas (experimental)", "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 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", "Show hidden events in timeline": "Mostrar eventos ocultos na timeline",
"Low bandwidth mode": "Modo de baixo uso de internet", "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)", "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", "Show previews/thumbnails for images": "Mostrar miniaturas e resumos para imagens",
"Enable message search in encrypted rooms": "Ativar busca de mensagens em salas criptografadas", "Enable message search in encrypted rooms": "Ativar busca de mensagens em salas criptografadas",
@ -1378,14 +1378,14 @@
"Decline (%(counter)s)": "Recusar (%(counter)s)", "Decline (%(counter)s)": "Recusar (%(counter)s)",
"Accept <policyLink /> to continue:": "Aceitar <policyLink /> para continuar:", "Accept <policyLink /> to continue:": "Aceitar <policyLink /> para continuar:",
"Upload": "Enviar", "Upload": "Enviar",
"This bridge was provisioned by <user />.": "Esta ponte foi disponibilizada por", "This bridge was provisioned by <user />.": "Esta ponte foi disponibilizada por <user />.",
"This bridge is managed by <user />.": "Esta ponte é gerida por <user />.", "This bridge is managed by <user />.": "Esta ponte é gerida por <user />.",
"Workspace: %(networkName)s": "Espaço de trabalho:", "Workspace: %(networkName)s": "Espaço de trabalho: %(networkName)s",
"Channel: %(channelName)s": "Canal:", "Channel: %(channelName)s": "Canal: %(channelName)s",
"Show less": "Mostrar menos", "Show less": "Mostrar menos",
"Show more": "Mostrar mais", "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.", "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.", "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.", "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.", "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", "not stored": "não armazenado",
"Backup has a <validity>valid</validity> signature from this user": "A cópia de segurança (backup) tem uma assinatura <validity>válida</validity> deste(a) usuário(a)", "Backup has a <validity>valid</validity> signature from this user": "A cópia de segurança (backup) tem uma assinatura <validity>válida</validity> deste(a) usuário(a)",
"Backup has a <validity>invalid</validity> signature from this user": "A cópia de segurança (backup) tem uma assinatura <validity>inválida</validity> deste(a) usuário(a)", "Backup has a <validity>invalid</validity> signature from this user": "A cópia de segurança (backup) tem uma assinatura <validity>inválida</validity> deste(a) usuário(a)",
"Backup has a signature from <verify>unknown</verify> user with ID %(deviceId)s": "Fazer cópia de segurança (backup) de usuária(o) <verify>desconhecida(o)</verify> com ID", "Backup has a signature from <verify>unknown</verify> user with ID %(deviceId)s": "A cópia de segurança tem uma assinatura de um(a) usuário <verify>desconhecido</verify> com ID %(deviceId)s",
"Backup has a signature from <verify>unknown</verify> session with ID %(deviceId)s": "Fazer cópia de segurança (backup) de uma sessão <verify>desconhecida</verify> com ID", "Backup has a signature from <verify>unknown</verify> session with ID %(deviceId)s": "A cópia de segurança tem uma assinatura de uma sessão <verify>desconhecida</verify> com ID %(deviceId)s",
"Backup has a <validity>valid</validity> signature from this session": "A cópia de segurança (backup) tem uma assinatura <validity>válida</validity> desta sessão", "Backup has a <validity>valid</validity> signature from this session": "A cópia de segurança (backup) tem uma assinatura <validity>válida</validity> desta sessão",
"Backup has an <validity>invalid</validity> signature from this session": "A cópia de segurança (backup) tem uma assinatura <validity>inválida</validity> desta sessão", "Backup has an <validity>invalid</validity> signature from this session": "A cópia de segurança (backup) tem uma assinatura <validity>inválida</validity> desta sessão",
"Backup has a <validity>valid</validity> signature from <verify>verified</verify> session <device></device>": "A cópia de segurança (backup) tem uma assinatura <validity>válida</validity> da sessão <verify>verificada</verify> <device></device>", "Backup has a <validity>valid</validity> signature from <verify>verified</verify> session <device></device>": "A cópia de segurança (backup) tem uma assinatura <validity>válida</validity> da sessão <verify>verificada</verify> <device></device>",
"Backup has a <validity>valid</validity> signature from <verify>unverified</verify> session <device></device>": "A cópia de segurança (backup) tem uma assinatura <validity>válida</validity> da sessão <verify>não verificada</verify>", "Backup has a <validity>valid</validity> signature from <verify>unverified</verify> session <device></device>": "A cópia de segurança tem uma assinatura <validity>válida</validity> de uma sessão <verify>não verificada</verify> <device></device>",
"Backup has an <validity>invalid</validity> signature from <verify>verified</verify> session <device></device>": "A cópia de segurança (backup) tem uma assinatura <validity>inválida</validity> de uma sessão <verify>verificada</verify>", "Backup has an <validity>invalid</validity> signature from <verify>verified</verify> session <device></device>": "A cópia de segurança tem uma assinatura <validity>inválida</validity> de uma sessão <verify>verificada</verify> <device></device>",
"Backup has an <validity>invalid</validity> signature from <verify>unverified</verify> session <device></device>": "A cópia de segurança (backup) tem uma assinatura <validity>inválida</validity> de uma sessão <verify>não verificada</verify> <device></device>", "Backup has an <validity>invalid</validity> signature from <verify>unverified</verify> session <device></device>": "A cópia de segurança (backup) tem uma assinatura <validity>inválida</validity> de uma sessão <verify>não verificada</verify> <device></device>",
"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", "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", "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", "%(name)s wants to verify": "%(name)s deseja verificar",
"Smileys & People": "Emoticons e Pessoas", "Smileys & People": "Emoticons e Pessoas",
"Widgets do not use message encryption.": "Widgets não usam criptografia de mensagens.", "Widgets do not use message encryption.": "Widgets não usam criptografia de mensagens.",
"Please <newIssueLink>create a new issue</newIssueLink> on GitHub so that we can investigate this bug.": "Por favor, <newIssueLink>crie uma nova issue</newIssueLink> no GitHub para que possamos investigar esta falha.", "Please <newIssueLink>create a new issue</newIssueLink> on GitHub so that we can investigate this bug.": "Por favor, <newIssueLink>crie um novo bilhete de erro</newIssueLink> 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.", "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.", "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.", "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 cant disable this later. Bridges & most bots wont work yet.": "Você não poderá desabilitar depois. Pontes e a maioria dos bots não funcionarão no momento.", "You cant disable this later. Bridges & most bots wont 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", "You'll lose access to your encrypted messages": "Você perderá acesso às suas mensagens criptografadas",
"Session key": "Chave da sessão", "Session key": "Chave da sessão",
"Verify session": "Verificar 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:", "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:",
"Youre already signed in and good to go here, but you can also grab the latest versions of the app on all platforms at <a>element.io/get-started</a>.": "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 <a>element.io/get-started</a>.", "Youre already signed in and good to go here, but you can also grab the latest versions of the app on all platforms at <a>element.io/get-started</a>.": "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 <a>element.io/get-started</a>.",
"Go to Element": "Ir a Element", "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.", "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 <a>element.io</a>.": "Entre com a localização do seu Servidor Matrix. Pode ser seu próprio domínio ou ser um subdomínio de <a>element.io</a>.", "Enter the location of your Element Matrix Services homeserver. It may use your own domain name or be a subdomain of <a>element.io</a>.": "Entre com a localização do seu Servidor Matrix. Pode ser seu próprio domínio ou ser um subdomínio de <a>element.io</a>.",
"Create your Matrix account on %(serverName)s": "Criar sua conta Matrix em %(serverName)s", "Create your Matrix account on %(serverName)s": "Criar sua conta Matrix em %(serverName)s",
"Create your Matrix account on <underlinedServerName />": "Criar sua conta Matrix em", "Create your Matrix account on <underlinedServerName />": "Crie sua conta Matrix em <underlinedServerName />",
"Welcome to %(appName)s": "Desejamos boas vindas ao %(appName)s", "Welcome to %(appName)s": "Desejamos boas vindas ao %(appName)s",
"Liberate your communication": "Liberte sua comunicação", "Liberate your communication": "Liberte sua comunicação",
"Send a Direct Message": "Envie uma mensagem direta", "Send a Direct Message": "Envie uma mensagem direta",
@ -1700,5 +1700,454 @@
"Verification Requests": "Solicitações de verificação", "Verification Requests": "Solicitações de verificação",
"Integrations are disabled": "As integrações estão desativadas", "Integrations are disabled": "As integrações estão desativadas",
"Integrations not allowed": "As integrações não estão permitidas", "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 <roomVersion />, which this homeserver has marked as <i>unstable</i>.": "Esta sala está executando a versão <roomVersion />, que este servidor marcou como <i>instável</i>.",
"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 <helpIcon /> with %(widgetDomain)s & your Integration Manager.": "Se você usar esse widget, os dados poderão ser compartilhados <helpIcon /> com %(widgetDomain)s & seu Gerenciador de Integrações.",
"Using this widget may share data <helpIcon /> with %(widgetDomain)s.": "Se você usar esse widget, os dados <helpIcon /> 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 <b>%(serverName)s</b>": "Tem certeza de que deseja remover <b>%(serverName)s</b>",
"%(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 <current /> and connect to <new /> instead?": "Desconectar-se do servidor de identidade <current /> e conectar-se em <new /> 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 <idserver />?": "Desconectar-se do servidor de identidade <idserver />?",
"Disconnect": "Desconectar",
"You should <b>remove your personal data</b> from identity server <idserver /> before disconnecting. Unfortunately, identity server <idserver /> is currently offline or cannot be reached.": "Você deve <b>remover seus dados pessoais</b> do servidor de identidade <idserver /> antes de desconectar. Infelizmente, o servidor de identidade <idserver /> 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 <idserver />": "entre em contato com os administradores do servidor de identidade <idserver />",
"Disconnect anyway": "Desconectar de qualquer maneira",
"You are still <b>sharing your personal data</b> on the identity server <idserver />.": "Você ainda está <b>compartilhando seus dados pessoais</b> no servidor de identidade <idserver />.",
"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 <server></server> to discover and be discoverable by existing contacts you know. You can change your identity server below.": "No momento, você está usando <server></server> 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 <server /> to discover and be discoverable by existing contacts you know, enter another identity server below.": "Se você não quiser usar <server /> 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. <a>Update now.</a>": "Nova versão disponível. <a>Atualize agora.</a>",
"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 <a>Security Disclosure Policy</a>.": "Para relatar um problema de segurança relacionado à tecnologia Matrix, leia a <a>Política de Divulgação de Segurança</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?",
"<userName/> wants to chat": "<userName/> quer conversar",
"Do you want to join %(roomName)s?": "Deseja se juntar a %(roomName)s?",
"<userName/> invited you": "<userName/> 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. <a>Learn more.</a>": "Faça backup de suas chaves com segurança para evitar perdê-las. <a>Saiba mais.</a>",
"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 <code>/help</code> to list available commands. Did you mean to send this as a message?": "Você pode usar <code>/help</code> 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? <a>Get your own server</a>": "Quer mais do que uma comunidade? <a>Obtenha seu próprio servidor</a>",
"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 <b>(%(serverName)s)</b> to manage bots, widgets, and sticker packs.": "Use o Gerenciador de Integrações em <b>(%(serverName)s)</b> 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. <a>Learn more</a>.": "Personalize sua experiência com os recursos experimentais. <a>Saiba mais</a>.",
"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, <code>@bot:*</code> 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, <code>@bot:*</code> 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 youre 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",
"<strong>%(role)s</strong> in %(roomName)s": "<strong>%(role)s</strong> 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. <a>Show anyways.</a>": "Você ignorou este usuário, portanto, a mensagem dele está oculta. <a>Mostrar mesmo assim.</a>",
"You verified %(name)s": "Você verificou %(name)s",
"Use an identity server to invite by email. <default>Use the default (%(defaultIdentityServerName)s)</default> or manage in <settings>Settings</settings>.": "Use um servidor de identidade para convidar por e-mail. <default>Use o padrão (%(defaultIdentityServerName)s)</default> ou um servidor personalizado em <settings>Configurações</settings>.",
"Use an identity server to invite by email. Manage in <settings>Settings</settings>.": "Use um servidor de identidade para convidar por e-mail. Gerencie o servidor em <settings>Configurações</settings>.",
"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 <userId/>), email address or <a>share this room</a>.": "Convide alguém com seu nome, nome de usuário (por exemplo: <userId/>), endereço de e-mail ou <a>compartilhe esta sala</a>.",
"Use your account to sign in to the latest version of the app at <a />": "Use sua conta para fazer login na versão mais recente do aplicativo em <a />",
"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 <a>report a bug</a>.": "Isso geralmente afeta apenas como a sala é processada no servidor. Se você tiver problemas com o %(brand)s, <a>informe um erro</a>.",
"You'll upgrade this room from <oldVersion /> to <newVersion />.": "Você atualizará esta sala de <oldVersion /> para <newVersion />.",
"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 <a>send us logs</a>.": "Para nos ajudar a evitar isso no futuro, <a>envie-nos os registros</a>.",
"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 <b>too large</b> to upload. The file size limit is %(limit)s but this file is %(sizeOfThisFile)s.": "Este arquivo é <b>muito grande</b> para ser enviado. O limite do tamanho de arquivos é %(limit)s, enquanto que o tamanho desse arquivo é %(sizeOfThisFile)s.",
"These files are <b>too large</b> to upload. The file size limit is %(limit)s.": "Esses arquivos são <b>muito grandes</b> para serem enviados. O limite do tamanho de arquivos é %(limit)s.",
"Some files are <b>too large</b> to be uploaded. The file size limit is %(limit)s.": "Alguns arquivos são <b>muito grandes</b> 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? <a>Set a new one</a>": "Esqueceu sua senha? <a>Defina uma nova</a>",
"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 <a>What does this mean?</a>": "Digite o URL de um servidor local <a>O que isso significa?</a>",
"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 <underlinedServerName />": "Faça login com sua conta Matrix em <underlinedServerName />",
"Sign in with SSO": "Faça login com SSO (Login Único)",
"Please install <chromeLink>Chrome</chromeLink>, <firefoxLink>Firefox</firefoxLink>, or <safariLink>Safari</safariLink> for the best experience.": "Por favor, instale o <chromeLink>Chrome</chromeLink>, o <firefoxLink>Firefox</firefoxLink> ou o <safariLink>Safari</safariLink> 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 <underlinedServerName />": "Sua conta Matrix em <underlinedServerName />",
"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"
} }

View file

@ -1547,8 +1547,8 @@
"%(creator)s created and configured the room.": "%(creator)s создал и настроил комнату.", "%(creator)s created and configured the room.": "%(creator)s создал и настроил комнату.",
"Preview": "Заглянуть", "Preview": "Заглянуть",
"View": "Просмотр", "View": "Просмотр",
"Find a room…": "Найди комнату…", "Find a room…": "Поиск комнат…",
"Find a room… (e.g. %(exampleRoom)s)": "Найди комнату... (напр. %(exampleRoom)s)", "Find a room… (e.g. %(exampleRoom)s)": "Поиск комнат... (напр. %(exampleRoom)s)",
"Explore rooms": "Список комнат", "Explore rooms": "Список комнат",
"No identity server is configured: add one in server settings to reset your password.": "Идентификационный сервер не настроен: добавьте его в настройки сервера, чтобы сбросить пароль.", "No identity server is configured: add one in server settings to reset your password.": "Идентификационный сервер не настроен: добавьте его в настройки сервера, чтобы сбросить пароль.",
"Command Autocomplete": "Автозаполнение команды", "Command Autocomplete": "Автозаполнение команды",
@ -1609,8 +1609,8 @@
"Verifies a user, session, and pubkey tuple": "Проверяет пользователя, сессию и публичные ключи", "Verifies a user, session, and pubkey tuple": "Проверяет пользователя, сессию и публичные ключи",
"Unknown (user, session) pair:": "Неизвестная (пользователь:сессия) пара:", "Unknown (user, session) pair:": "Неизвестная (пользователь:сессия) пара:",
"Session already verified!": "Сессия уже подтверждена!", "Session already verified!": "Сессия уже подтверждена!",
"Never send encrypted messages to unverified sessions 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": "Никогда не отправляйте зашифрованные сообщения в непроверенные сессии в эту комнату из этой сессии", "Never send encrypted messages to unverified sessions in this room from this session": "Никогда не отправлять зашифрованные сообщения непроверенным сессиям в этой комнате и в этой сессии",
"Your keys are <b>not being backed up from this session</b>.": "Ваши ключи <b>не резервируются с этой сессии</b>.", "Your keys are <b>not being backed up from this session</b>.": "Ваши ключи <b>не резервируются с этой сессии</b>.",
"Server or user ID to ignore": "Сервер или ID пользователя для игнорирования", "Server or user ID to ignore": "Сервер или ID пользователя для игнорирования",
"Subscribed lists": "Подписанные списки", "Subscribed lists": "Подписанные списки",
@ -2172,5 +2172,8 @@
"This address is already in use": "Этот адрес уже используется", "This address is already in use": "Этот адрес уже используется",
"Are you sure you want to remove <b>%(serverName)s</b>": "Вы уверены, что хотите удалить <b>%(serverName)s</b>", "Are you sure you want to remove <b>%(serverName)s</b>": "Вы уверены, что хотите удалить <b>%(serverName)s</b>",
"Enter the name of a new server you want to explore.": "Введите имя нового сервера для просмотра.", "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": "Переключить в тёмный режим"
} }

8
src/i18n/strings/si.json Normal file
View file

@ -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": "විද්‍යුත් තැපැල් ලිපිනය එක් කරන්න"
}

View file

@ -2387,5 +2387,7 @@
"%(brand)s iOS": "%(brand)s iOS", "%(brand)s iOS": "%(brand)s iOS",
"%(brand)s X for Android": "%(brand)s X për Android", "%(brand)s X for Android": "%(brand)s X për Android",
"* %(senderName)s %(emote)s": "* %(senderName)s %(emote)s", "* %(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."
} }

View file

@ -6,7 +6,7 @@
"Dismiss": "Відхилити", "Dismiss": "Відхилити",
"Error": "Помилка", "Error": "Помилка",
"Failed to forget room %(errCode)s": "Не вдалось видалити кімнату %(errCode)s", "Failed to forget room %(errCode)s": "Не вдалось видалити кімнату %(errCode)s",
"Favourite": "Вибране", "Favourite": "Улюблені",
"Mute": "Стишити", "Mute": "Стишити",
"Notifications": "Сповіщення", "Notifications": "Сповіщення",
"Operation failed": "Не вдалося виконати дію", "Operation failed": "Не вдалося виконати дію",
@ -78,7 +78,7 @@
"Unpin Message": "Відкріпити повідомлення", "Unpin Message": "Відкріпити повідомлення",
"Register": "Зареєструватися", "Register": "Зареєструватися",
"Rooms": "Кімнати", "Rooms": "Кімнати",
"Add rooms to this community": "Добавити кімнати в це суспільство", "Add rooms to this community": "Додати кімнати в цю спільноту",
"This email address is already in use": "Ця е-пошта вже використовується", "This email address is already in use": "Ця е-пошта вже використовується",
"This phone number is already in use": "Цей телефонний номер вже використовується", "This phone number is already in use": "Цей телефонний номер вже використовується",
"Fetching third party location failed": "Не вдалось отримати стороннє місцеперебування", "Fetching third party location failed": "Не вдалось отримати стороннє місцеперебування",
@ -240,33 +240,33 @@
"You cannot place a call with yourself.": "Ви не можете подзвонити самим собі.", "You cannot place a call with yourself.": "Ви не можете подзвонити самим собі.",
"Warning!": "Увага!", "Warning!": "Увага!",
"Upload Failed": "Помилка відвантаження", "Upload Failed": "Помилка відвантаження",
"Sun": "Нд", "Sun": "нд",
"Mon": "Пн", "Mon": "пн",
"Tue": "Вт", "Tue": "вт",
"Wed": "Ср", "Wed": "ср",
"Thu": "Чт", "Thu": "чт",
"Fri": "Пт", "Fri": "пт",
"Sat": "Сб", "Sat": "сб",
"Jan": "Січ", "Jan": "січ.",
"Feb": "Лют", "Feb": "лют.",
"Mar": "Бер", "Mar": "бер.",
"Apr": "Квіт", "Apr": "квіт.",
"May": "Трав", "May": "трав.",
"Jun": "Чер", "Jun": "черв.",
"Jul": "Лип", "Jul": "лип.",
"Aug": "Сер", "Aug": "серп.",
"Sep": "Вер", "Sep": "вер.",
"Oct": "Жов", "Oct": "жовт.",
"Nov": "Лис", "Nov": "лист.",
"Dec": "Гру", "Dec": "груд.",
"PM": "PM", "PM": "пп",
"AM": "AM", "AM": "дп",
"%(weekDayName)s %(time)s": "%(weekDayName)s %(time)s", "%(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 %(time)s": "%(weekDayName)s, %(day)s %(monthName)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": "%(weekDayName)s, %(day)s %(monthName)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 %(fullYear)s %(time)s": "%(weekDayName)s, %(day)s %(monthName)s %(fullYear)s %(time)s",
"Who would you like to add to this community?": "Кого ви хочете додати до цієї спільноти?", "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": "Іде виклик", "Call in Progress": "Іде виклик",
"A call is currently being placed!": "Зараз іде виклик!", "A call is currently being placed!": "Зараз іде виклик!",
"A call is already in progress!": "Вже здійснюється дзвінок!", "A call is already in progress!": "Вже здійснюється дзвінок!",
@ -311,9 +311,9 @@
"Changes your display nickname": "Змінює ваш нік", "Changes your display nickname": "Змінює ваш нік",
"Invites user with given id to current room": "Запрошує користувача з вказаним ідентифікатором до кімнати", "Invites user with given id to current room": "Запрошує користувача з вказаним ідентифікатором до кімнати",
"Leave room": "Покинути кімнату", "Leave room": "Покинути кімнату",
"Kicks user with given id": "Вилучити з кімнати користувача з вказаним ідентифікатором", "Kicks user with given id": "Викидає з кімнати користувача з вказаним ідентифікатором",
"Ignores a user, hiding their messages from you": "Ігнорує користувача, приховуючи повідомлення від них", "Ignores a user, hiding their messages from you": "Ігнорує користувача, приховуючи його повідомлення від вас",
"Ignored user": "Користувача ігноровано", "Ignored user": "Зігнорований користувач",
"You are now ignoring %(userId)s": "Ви ігноруєте %(userId)s", "You are now ignoring %(userId)s": "Ви ігноруєте %(userId)s",
"Stops ignoring a user, showing their messages going forward": "Припиняє ігнорувати користувача, від цього моменту показуючи їхні повідомлення", "Stops ignoring a user, showing their messages going forward": "Припиняє ігнорувати користувача, від цього моменту показуючи їхні повідомлення",
"Unignored user": "Припинено ігнорування користувача", "Unignored user": "Припинено ігнорування користувача",
@ -321,8 +321,8 @@
"Define the power level of a user": "Вказати рівень повноважень користувача", "Define the power level of a user": "Вказати рівень повноважень користувача",
"Deops user with given id": "Знімає права оператора з користувача з вказаним ідентифікатором", "Deops user with given id": "Знімає права оператора з користувача з вказаним ідентифікатором",
"Opens the Developer Tools dialog": "Відкриває вікно інструментів розробника", "Opens the Developer Tools dialog": "Відкриває вікно інструментів розробника",
"Verified key": "Перевірений ключ", "Verified key": "Звірений ключ",
"Displays action": "Показує дію", "Displays action": "Відбиває дію",
"Reason": "Причина", "Reason": "Причина",
"%(senderName)s requested a VoIP conference.": "%(senderName)s бажає розпочати дзвінок-конференцію.", "%(senderName)s requested a VoIP conference.": "%(senderName)s бажає розпочати дзвінок-конференцію.",
"%(senderName)s invited %(targetName)s.": "%(senderName)s запросив/ла %(targetName)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 unbanned %(targetName)s.": "%(senderName)s розблокував/ла %(targetName)s.",
"%(senderName)s kicked %(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.", "%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s відкликав/ла запрошення %(targetName)s.",
"%(senderDisplayName)s sent an image.": "%(senderDisplayName)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 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 вилучив/ла основу адресу цієї кімнати.", "%(senderName)s removed the main address for this room.": "%(senderName)s прибрав(-ла) основу адресу цієї кімнати.",
"Someone": "Хтось", "Someone": "Хтось",
"(not supported by this browser)": "(не підтримується цією веб-переглядачкою)", "(not supported by this browser)": "(не підтримується цією веб-переглядачкою)",
"(could not connect media)": "(не можливо під'єднати медіа)", "(could not connect media)": "(не можливо під'єднати медіа)",
@ -403,7 +403,7 @@
"Password": "Пароль", "Password": "Пароль",
"New Password": "Новий пароль", "New Password": "Новий пароль",
"Confirm password": "Підтвердження пароля", "Confirm password": "Підтвердження пароля",
"Last seen": "Востаннє з'являвся", "Last seen": "Востаннє в мережі",
"Failed to set display name": "Не вдалося встановити ім'я для показу", "Failed to set display name": "Не вдалося встановити ім'я для показу",
"The maximum permitted number of widgets have already been added to this room.": "Максимально дозволену кількість віджетів уже додано до цієї кімнати.", "The maximum permitted number of widgets have already been added to this room.": "Максимально дозволену кількість віджетів уже додано до цієї кімнати.",
"Drop File Here": "Киньте файл сюди", "Drop File Here": "Киньте файл сюди",
@ -460,7 +460,7 @@
"Sends a message as plain text, without interpreting it as markdown": "Надсилає повідомлення як чистий текст, не використовуючи markdown", "Sends a message as plain text, without interpreting it as markdown": "Надсилає повідомлення як чистий текст, не використовуючи markdown",
"Upgrades a room to a new version": "Покращує кімнату до нової версії", "Upgrades a room to a new version": "Покращує кімнату до нової версії",
"You do not have the required permissions to use this command.": "Вам бракує дозволу на використання цієї команди.", "You do not have the required permissions to use this command.": "Вам бракує дозволу на використання цієї команди.",
"<b>Warning</b>: Upgrading a room will <i>not automatically migrate room members to the new version of the room.</i> 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.": "<b>Увага!</b>: Покращення кімнати <i>не перенесе автоматично усіх учасників до нової версії кімнати.</i> Ми опублікуємо посилання на нову кімнату у старій версії кімнати, а учасники мають власноруч клацнути це посилання, щоб приєднатися до нової кімнати.", "<b>Warning</b>: Upgrading a room will <i>not automatically migrate room members to the new version of the room.</i> 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.": "<b>Увага!</b>: Поліпшення кімнати <i>не перенесе автоматично усіх учасників до нової версії кімнати.</i> Ми опублікуємо посилання на нову кімнату у старій версії кімнати, а учасники мають власноруч клацнути це посилання, щоб приєднатися до нової кімнати.",
"Changes your display nickname in the current room only": "Змінює ваше псевдо тільки для поточної кімнати", "Changes your display nickname in the current room only": "Змінює ваше псевдо тільки для поточної кімнати",
"Changes the avatar of the current room": "Змінює аватар поточної кімнати", "Changes the avatar of the current room": "Змінює аватар поточної кімнати",
"Changes your avatar in this current room only": "Змінює ваш аватар для поточної кімнати", "Changes your avatar in this current room only": "Змінює ваш аватар для поточної кімнати",
@ -469,13 +469,13 @@
"This room has no topic.": "Ця кімната не має теми.", "This room has no topic.": "Ця кімната не має теми.",
"Sets the room name": "Встановлює назву кімнати", "Sets the room name": "Встановлює назву кімнати",
"Use an identity server": "Використовувати сервер ідентифікації", "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": "Розблоковує користувача з вказаним ідентифікатором", "Unbans user with given ID": "Розблоковує користувача з вказаним ідентифікатором",
"Adds a custom widget by URL to the room": "Додає власний віджет до кімнати за посиланням", "Adds a custom widget by URL to the room": "Додає власний віджет до кімнати за посиланням",
"Please supply a https:// or http:// widget URL": "Вкажіть посилання на віджет — https:// або http://", "Please supply a https:// or http:// widget URL": "Вкажіть посилання на віджет — https:// або http://",
"You cannot modify widgets in this room.": "Ви не можете змінювати віджети у цій кімнаті.", "You cannot modify widgets in this room.": "Ви не можете змінювати віджети у цій кімнаті.",
"Forces the current outbound group session in an encrypted room to be discarded": "Примусово відкидає поточний вихідний груповий сеанс у шифрованій кімнаті", "Forces the current outbound group session in an encrypted room to be discarded": "Примусово відкидає поточний вихідний груповий сеанс у зашифрованій кімнаті",
"Sends the given message coloured as a rainbow": "Надсилає вказане повідомлення розфарбоване веселкою", "Sends the given message coloured as a rainbow": "Надсилає вказане повідомлення, розфарбоване веселкою",
"Your %(brand)s is misconfigured": "Ваш %(brand)s налаштовано неправильно", "Your %(brand)s is misconfigured": "Ваш %(brand)s налаштовано неправильно",
"Join the discussion": "Приєднатися до обговорення", "Join the discussion": "Приєднатися до обговорення",
"Upload": "Обрати", "Upload": "Обрати",
@ -639,47 +639,47 @@
"Room Settings - %(roomName)s": "Налаштування кімнати - %(roomName)s", "Room Settings - %(roomName)s": "Налаштування кімнати - %(roomName)s",
"A verification email will be sent to your inbox to confirm setting your new password.": "Ми відправимо перевіряльний електронний лист до вас для підтвердження зміни пароля.", "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": "Щоб повернутися до своєї обліківки в майбутньому, вам потрібно встановити пароль", "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": "Приєднатися до кімнати зі вказаною адресою", "Joins room with given address": "Приєднатися до кімнати зі вказаною адресою",
"Unrecognised room address:": "Не вдалося знайти адресу кімнати:", "Unrecognised room address:": "Невпізнана адреса кімнати:",
"Command failed": "Не вдалося виконати команду", "Command failed": "Не вдалося виконати команду",
"Could not find user in room": "Не вдалося знайти користувача в кімнаті", "Could not find user in room": "Не вдалося знайти користувача в кімнаті",
"Please supply a widget URL or embed code": "Вкажіть URL або код вставки віджету", "Please supply a widget URL or embed code": "Вкажіть URL або код вставки віджету",
"Verifies a user, session, and pubkey tuple": "Перевіряє користувача, сесію та публічні ключі", "Verifies a user, session, and pubkey tuple": "Звіряє користувача, сеанс та кортеж відкритого ключа",
"Unknown (user, session) pair:": "Невідома (користувач:сесія) пара:", "Unknown (user, session) pair:": "Невідома пара (користувача, сеансу):",
"Session already verified!": "Сесія вже підтверджена!", "Session already verified!": "Сеанс вже підтверджений!",
"WARNING: Session already verified, but keys do NOT MATCH!": "УВАГА: Сесія вже підтверджена, проте ключі НЕ ЗБІГАЮТЬСЯ!", "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\". Це може означати, що ваші повідомлення перехоплюються!", "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.": "Ключ підпису, який ви надали, відповідає ключу підпису, який ви отримали від %(userId)s's сесії %(deviceId)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": "Надсилає заданий смайлик, пофарбований у вигляді веселки", "Sends the given emote coloured as a rainbow": "Надсилає вказаний смайлик, розфарбований веселкою",
"Displays list of commands with usages and descriptions": "Відображає список команд із описом та користуванням", "Displays list of commands with usages and descriptions": "Відбиває перелік команд із прикладами вжитку та описом",
"Displays information about a user": "Показати інформацію про користувача", "Displays information about a user": "Відбиває інформацію про користувача",
"Send a bug report with logs": "Відправити звіт про помилку з логами", "Send a bug report with logs": "Надіслати звіт про ваду разом з журналами",
"Opens chat with the given user": "Відкрити чат з даним користувачем", "Opens chat with the given user": "Відкриває балачку з вказаним користувачем",
"Sends a message to the given user": "Відправити повідомлення даному користувачу", "Sends a message to the given user": "Надсилає повідомлення вказаному користувачеві",
"%(senderName)s made no change.": "%(senderName)s не вніс змін.", "%(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 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 upgraded this room.": "%(senderDisplayName)s поліпшив(-ла) цю кімнату.",
"%(senderDisplayName)s made the room public to whoever knows the link.": "%(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 made the room invite only.": "%(senderDisplayName)s зробив(-ла) кімнату доступною лише за запрошеннями.",
"%(senderDisplayName)s changed the join rule to %(rule)s": "%(senderDisplayName)s змінює правило входу на \"%(rule)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 allowed guests to join the room.": "%(senderDisplayName)s дозволив(-ла) гостям приєднуватися до кімнати.",
"%(senderDisplayName)s has prevented guests from joining 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 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 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 в цій кімнаті.", "%(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.|other": "%(senderName)s додав(-ла) альтернативні адреси %(addresses)s для цієї кімнати.",
"%(senderName)s added the alternative addresses %(addresses)s for this room.|one": "%(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.|other": "%(senderName)s прибрав(-ла) альтернативні адреси %(addresses)s для цієї кімнати.",
"%(senderName)s removed the alternative addresses %(addresses)s for this room.|one": "%(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 alternative addresses for this room.": "%(senderName)s змінив(-ла) альтернативні адреси для цієї кімнати.",
"%(senderName)s changed the main and 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 changed the addresses for this room.": "%(senderName)s змінив(-ла) адреси для цієї кімнати.",
"%(senderName)s placed a voice call.": "%(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 voice call. (not supported by this browser)": "%(senderName)s розпочав(-ла) голосовий виклик. (не підтримується цим переглядачем)",
"%(senderName)s placed a video call.": "%(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 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 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 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 rooms matching %(glob)s": "%(senderName)s видалив(ла) правило блокування кімнат по шаблону %(glob)s",
"%(senderName)s removed the rule banning servers 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 youre logged in": "Перевірте, де ви ввійшли", "Review where youre logged in": "Перевірте, де ви ввійшли",
"Your homeserver has exceeded its user limit.": "Ваш домашній сервер перевищив свій ліміт користувачів.", "Your homeserver has exceeded its user limit.": "Ваш домашній сервер перевищив свій ліміт користувачів.",
"Your homeserver has exceeded one of its resource limits.": "Ваш домашній сервер перевищив один із своїх ресурсних лімітів.", "Your homeserver has exceeded one of its resource limits.": "Ваш домашній сервер перевищив один із своїх ресурсних лімітів.",
"Contact your <a>server admin</a>.": "Зверніться до <a>адміністратора серверу<a>.", "Contact your <a>server admin</a>.": "Зверніться до <a>адміністратора серверу</a>.",
"Ok": "Гаразд", "Ok": "Гаразд",
"Set password": "Встановити пароль", "Set password": "Встановити пароль",
"Set up encryption": "Налаштування шифрування", "Set up encryption": "Налаштування шифрування",
"Encryption upgrade available": "Доступне оновлення шифрування", "Encryption upgrade available": "Доступне поліпшене шифрування",
"Set up": "Налаштувати", "Set up": "Налаштувати",
"Upgrade": "Оновлення", "Upgrade": "Поліпшити",
"Other users may not trust it": "Інші користувачі можуть не довіряти цьому", "Other users may not trust it": "Інші користувачі можуть не довіряти цьому",
"New login. Was this you?": "Новий вхід у вашу обліківку. Це були Ви?", "New login. Was this you?": "Новий вхід у вашу обліківку. Це були Ви?",
"Restart": "Перезапустити", "Restart": "Перезапустити",
@ -855,7 +855,7 @@
"Preferences": "Параметри", "Preferences": "Параметри",
"Room list": "Перелік кімнат", "Room list": "Перелік кімнат",
"Composer": "Редактор", "Composer": "Редактор",
"Security & Privacy": "Безпека та конфіденціальність", "Security & Privacy": "Безпека та конфіденційність",
"Where youre logged in": "Де ви ввійшли", "Where youre logged in": "Де ви ввійшли",
"Skip": "Пропустити", "Skip": "Пропустити",
"Notification settings": "Налаштування сповіщень", "Notification settings": "Налаштування сповіщень",
@ -882,7 +882,7 @@
"Enable 'Manage Integrations' in Settings to do this.": "Щоб зробити це увімкніть \"Керувати інтеграціями\" у налаштуваннях.", "Enable 'Manage Integrations' in Settings to do this.": "Щоб зробити це увімкніть \"Керувати інтеграціями\" у налаштуваннях.",
"Confirm by comparing the following with the User Settings in your other session:": "Підтвердьте шляхом порівняння наступного рядка з рядком у користувацьких налаштуваннях вашого іншого сеансу:", "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:": "Підтвердьте сеанс цього користувача шляхом порівняння наступного рядка з рядком з їхніх користувацьких налаштувань:", "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": "Поширити повідомлення", "Share Message": "Поширити повідомлення",
"Community Settings": "Налаштування спільноти", "Community Settings": "Налаштування спільноти",
"All settings": "Усі налаштування", "All settings": "Усі налаштування",
@ -968,7 +968,7 @@
"Cross-signing and secret storage are enabled.": "Кросс-підпис та секретне сховище дозволені.", "Cross-signing and secret storage are enabled.": "Кросс-підпис та секретне сховище дозволені.",
"well formed": "добре сформований", "well formed": "добре сформований",
"unexpected type": "несподіваний тип", "unexpected type": "несподіваний тип",
"Cross-signing public keys:": ублічні ключі для кросс-підпису:", "Cross-signing public keys:": ерехресно-підписувальні відкриті ключі:",
"in memory": "у пам'яті", "in memory": "у пам'яті",
"not found": "не знайдено", "not found": "не знайдено",
"Cross-signing private keys:": "Приватні ключі для кросс-підпису:", "Cross-signing private keys:": "Приватні ключі для кросс-підпису:",
@ -1004,5 +1004,101 @@
"Enter a new identity server": "Введіть новий сервер ідентифікації", "Enter a new identity server": "Введіть новий сервер ідентифікації",
"Change": "Змінити", "Change": "Змінити",
"Manage integrations": "Керування інтеграціями", "Manage integrations": "Керування інтеграціями",
"Size must be a number": "Розмір повинен бути числом" "Size must be a number": "Розмір повинен бути числом",
"Incoming voice call": "Входовий голосовий виклик",
"Incoming video call": "Входовий відеовиклик",
"<a>Upgrade</a> to your own domain": "<a>Поліпшити</a> до свого власного домену",
"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 <oldVersion /> to <newVersion />.": "Ви поліпшите цю кімнату з <oldVersion /> до <newVersion /> версії.",
"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 <button1>use your recovery key</button1> or <button2>set up new recovery options</button2>": "Якщо ви забули відновлювальну парольну фразу, ви можете <button1>скористатись вашим відновлювальним ключем</button1> або <button2>налаштувати нові параметри відновлювання</button2>",
"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 <button>set up new recovery options</button>": "Якщо ви забули ваш відновлювальний ключ, ви можете <button>наново налаштувати параметри відновлювання</button>",
"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 <b>copied to your clipboard</b>, paste it to:": "Ваш відновлювальний ключ було <b>скопійовано до буферу обміну</b>, вставте його у:",
"Your recovery key is in your <b>Downloads</b> folder.": "Ваш відновлювальний ключ у вашій теці <b>Завантаження</b>.",
"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. <a>Update now.</a>": "Доступна нова версія. <a>Оновити зараз</a>",
"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": "Поліпшити ваше шифрування"
} }

View file

@ -18,8 +18,9 @@ import PlatformPeg from "../PlatformPeg";
import {MatrixClientPeg} from "../MatrixClientPeg"; import {MatrixClientPeg} from "../MatrixClientPeg";
import {EventTimeline, RoomMember} from 'matrix-js-sdk'; import {EventTimeline, RoomMember} from 'matrix-js-sdk';
import {sleep} from "../utils/promise"; import {sleep} from "../utils/promise";
import SettingsStore, {SettingLevel} from "../settings/SettingsStore"; import SettingsStore from "../settings/SettingsStore";
import {EventEmitter} from "events"; import {EventEmitter} from "events";
import {SettingLevel} from "../settings/SettingLevel";
/* /*
* Event indexing class that wraps the platform specific event indexing. * Event indexing class that wraps the platform specific event indexing.

View file

@ -21,7 +21,8 @@ limitations under the License.
import PlatformPeg from "../PlatformPeg"; import PlatformPeg from "../PlatformPeg";
import EventIndex from "../indexing/EventIndex"; 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; const INDEX_VERSION = 1;

View file

@ -21,11 +21,12 @@ import request from 'browser-request';
import counterpart from 'counterpart'; import counterpart from 'counterpart';
import React from 'react'; import React from 'react';
import SettingsStore, {SettingLevel} from "./settings/SettingsStore"; import SettingsStore from "./settings/SettingsStore";
import PlatformPeg from "./PlatformPeg"; import PlatformPeg from "./PlatformPeg";
// @ts-ignore - $webapp is a webpack resolve alias pointing to the output directory, see webpack config // @ts-ignore - $webapp is a webpack resolve alias pointing to the output directory, see webpack config
import webpackLangJsonUrl from "$webapp/i18n/languages.json"; import webpackLangJsonUrl from "$webapp/i18n/languages.json";
import { SettingLevel } from "./settings/SettingLevel";
const i18nFolder = 'i18n/'; const i18nFolder = 'i18n/';

View file

@ -16,9 +16,10 @@ limitations under the License.
import {MatrixClientPeg} from "../MatrixClientPeg"; import {MatrixClientPeg} from "../MatrixClientPeg";
import {ALL_RULE_TYPES, BanList} from "./BanList"; import {ALL_RULE_TYPES, BanList} from "./BanList";
import SettingsStore, {SettingLevel} from "../settings/SettingsStore"; import SettingsStore from "../settings/SettingsStore";
import {_t} from "../languageHandler"; import {_t} from "../languageHandler";
import dis from "../dispatcher/dispatcher"; import dis from "../dispatcher/dispatcher";
import {SettingLevel} from "../settings/SettingLevel";
// TODO: Move this and related files to the js-sdk or something once finalized. // TODO: Move this and related files to the js-sdk or something once finalized.

View file

@ -141,7 +141,7 @@ export default async function sendBugReport(bugReportEndpoint: string, opts: IOp
} }
// add labs options // add labs options
const enabledLabs = SettingsStore.getLabsFeatures().filter(SettingsStore.isFeatureEnabled); const enabledLabs = SettingsStore.getLabsFeatures().filter(f => SettingsStore.isFeatureEnabled(f));
if (enabledLabs.length) { if (enabledLabs.length) {
body.append('enabled_labs', enabledLabs.join(', ')); body.append('enabled_labs', enabledLabs.join(', '));
} }

View file

@ -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",
}

View file

@ -1,7 +1,6 @@
/* /*
Copyright 2017 Travis Ralston Copyright 2017 Travis Ralston
Copyright 2018, 2019 New Vector Ltd. Copyright 2018, 2019, 2020 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -16,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License. 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 { import {
@ -28,75 +27,89 @@ import CustomStatusController from "./controllers/CustomStatusController";
import ThemeController from './controllers/ThemeController'; import ThemeController from './controllers/ThemeController';
import PushToMatrixClientController from './controllers/PushToMatrixClientController'; import PushToMatrixClientController from './controllers/PushToMatrixClientController';
import ReloadOnChangeController from "./controllers/ReloadOnChangeController"; import ReloadOnChangeController from "./controllers/ReloadOnChangeController";
import {RightPanelPhases} from "../stores/RightPanelStorePhases";
import FontSizeController from './controllers/FontSizeController'; import FontSizeController from './controllers/FontSizeController';
import SystemFontController from './controllers/SystemFontController'; import SystemFontController from './controllers/SystemFontController';
import UseSystemFontController from './controllers/UseSystemFontController'; 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 // 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_SETTINGS = [
const LEVELS_ROOM_OR_ACCOUNT = ['room-account', 'account']; SettingLevel.DEVICE,
const LEVELS_ROOM_SETTINGS_WITH_ROOM = ['device', 'room-device', 'room-account', 'account', 'config', 'room']; SettingLevel.ROOM_DEVICE,
const LEVELS_ACCOUNT_SETTINGS = ['device', 'account', 'config']; SettingLevel.ROOM_ACCOUNT,
const LEVELS_FEATURE = ['device', 'config']; SettingLevel.ACCOUNT,
const LEVELS_DEVICE_ONLY_SETTINGS = ['device']; SettingLevel.CONFIG,
const LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG = ['device', '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 = { export interface ISetting {
// EXAMPLE SETTING: // Must be set to true for features. Default is 'false'.
// "my-setting": { isFeature?: boolean;
// // Must be set to true for features. Default is 'false'.
// isFeature: false, // Display names are strongly recommended for clarity.
// // Display name can also be an object for different levels.
// // Display names are strongly recommended for clarity. displayName?: string | {
// displayName: _td("Cool Name"), // @ts-ignore - TS wants the key to be a string, but we know better
// [level: SettingLevel]: string;
// // Display name can also be an object for different levels. };
// //displayName: {
// // "device": _td("Name for when the setting is used at 'device'"), // The supported levels are required. Preferably, use the preset arrays
// // "room": _td("Name for when the setting is used at 'room'"), // at the top of this file to define this rather than a custom array.
// // "default": _td("The name for all other levels"), supportedLevels?: SettingLevel[];
// //}
// // Required. Can be any data type. The value specified here should match
// // The supported levels are required. Preferably, use the preset arrays // the data being stored (ie: if a boolean is used, the setting should
// // at the top of this file to define this rather than a custom array. // represent a boolean).
// supportedLevels: [ default: any;
// // The order does not matter.
// // Optional settings controller. See SettingsController for more information.
// "device", // Affects the current device only controller?: SettingController;
// "room-device", // Affects the current room on the current device
// "room-account", // Affects the current room for the current account // Optional flag to make supportedLevels be respected as the order to handle
// "account", // Affects the current account // settings. The first element is treated as "most preferred". The "default"
// "room", // Affects the current room (controlled by room admins) // level is always appended to the end.
// "config", // Affects the current application supportedLevelsAreOrdered?: boolean;
//
// // "default" is always supported and does not get listed here. // 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
// // Required. Can be any data type. The value specified here should match // be inverted, except for when the default value is returned. Inversion will
// // the data being stored (ie: if a boolean is used, the setting should // occur after the controller is asked for an override. This should be used by
// // represent a boolean). // historical settings which we don't want existing user's values be wiped. Do
// default: { // not use this for new settings.
// your: "value", invertedSettingName?: string;
// }, }
//
// // Optional settings controller. See SettingsController for more information. export const SETTINGS: {[setting: string]: ISetting} = {
// 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",
// },
"feature_new_spinner": { "feature_new_spinner": {
isFeature: true, isFeature: true,
displayName: _td("New spinner design"), displayName: _td("New spinner design"),
@ -153,11 +166,11 @@ export const SETTINGS = {
default: false, default: false,
}, },
"mjolnirRooms": { "mjolnirRooms": {
supportedLevels: ['account'], supportedLevels: [SettingLevel.ACCOUNT],
default: [], default: [],
}, },
"mjolnirPersonalRoom": { "mjolnirPersonalRoom": {
supportedLevels: ['account'], supportedLevels: [SettingLevel.ACCOUNT],
default: null, default: null,
}, },
"feature_bridge_state": { "feature_bridge_state": {
@ -354,24 +367,24 @@ export const SETTINGS = {
}, },
"breadcrumb_rooms": { "breadcrumb_rooms": {
// not really a setting // not really a setting
supportedLevels: ['account'], supportedLevels: [SettingLevel.ACCOUNT],
default: [], default: [],
}, },
"recent_emoji": { "recent_emoji": {
// not really a setting // not really a setting
supportedLevels: ['account'], supportedLevels: [SettingLevel.ACCOUNT],
default: [], default: [],
}, },
"room_directory_servers": { "room_directory_servers": {
supportedLevels: ['account'], supportedLevels: [SettingLevel.ACCOUNT],
default: [], default: [],
}, },
"integrationProvisioning": { "integrationProvisioning": {
supportedLevels: ['account'], supportedLevels: [SettingLevel.ACCOUNT],
default: true, default: true,
}, },
"allowedWidgets": { "allowedWidgets": {
supportedLevels: ['room-account'], supportedLevels: [SettingLevel.ROOM_ACCOUNT],
default: {}, // none allowed default: {}, // none allowed
}, },
"analyticsOptIn": { "analyticsOptIn": {
@ -398,7 +411,7 @@ export const SETTINGS = {
"blacklistUnverifiedDevices": { "blacklistUnverifiedDevices": {
// We specifically want to have room-device > device so that users may set a device default // We specifically want to have room-device > device so that users may set a device default
// with a per-room override. // with a per-room override.
supportedLevels: ['room-device', 'device'], supportedLevels: [SettingLevel.ROOM_DEVICE, SettingLevel.DEVICE],
supportedLevelsAreOrdered: true, supportedLevelsAreOrdered: true,
displayName: { displayName: {
"default": _td('Never send encrypted messages to unverified sessions from this session'), "default": _td('Never send encrypted messages to unverified sessions from this session'),
@ -416,7 +429,7 @@ export const SETTINGS = {
default: true, default: true,
}, },
"urlPreviewsEnabled_e2ee": { "urlPreviewsEnabled_e2ee": {
supportedLevels: ['room-device', 'room-account'], supportedLevels: [SettingLevel.ROOM_DEVICE, SettingLevel.ROOM_ACCOUNT],
displayName: { displayName: {
"room-account": _td("Enable URL previews for this room (only affects you)"), "room-account": _td("Enable URL previews for this room (only affects you)"),
}, },
@ -455,7 +468,7 @@ export const SETTINGS = {
default: false, default: false,
}, },
"PinnedEvents.isOpen": { "PinnedEvents.isOpen": {
supportedLevels: ['room-device'], supportedLevels: [SettingLevel.ROOM_DEVICE],
default: false, default: false,
}, },
"promptBeforeInviteUnknownUsers": { "promptBeforeInviteUnknownUsers": {
@ -565,7 +578,8 @@ export const SETTINGS = {
"ircDisplayNameWidth": { "ircDisplayNameWidth": {
// We specifically want to have room-device > device so that users may set a device default // We specifically want to have room-device > device so that users may set a device default
// with a per-room override. // with a per-room override.
supportedLevels: ['room-device', 'device'], supportedLevels: [SettingLevel.ROOM_DEVICE, SettingLevel.DEVICE],
supportedLevelsAreOrdered: true,
displayName: _td("IRC display name width"), displayName: _td("IRC display name width"),
default: 80, default: 80,
}, },

View file

@ -1,6 +1,6 @@
/* /*
Copyright 2017 Travis Ralston 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -25,24 +25,11 @@ import ConfigSettingsHandler from "./handlers/ConfigSettingsHandler";
import { _t } from '../languageHandler'; import { _t } from '../languageHandler';
import SdkConfig from "../SdkConfig"; import SdkConfig from "../SdkConfig";
import dis from '../dispatcher/dispatcher'; import dis from '../dispatcher/dispatcher';
import {SETTINGS} from "./Settings"; import { ISetting, SETTINGS } from "./Settings";
import LocalEchoWrapper from "./handlers/LocalEchoWrapper"; import LocalEchoWrapper from "./handlers/LocalEchoWrapper";
import { WatchManager } from "./WatchManager"; import { WatchManager } from "./WatchManager";
import { SettingLevel } from "./SettingLevel";
/** import SettingsHandler from "./handlers/SettingsHandler";
* 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",
};
const defaultWatchManager = new WatchManager(); const defaultWatchManager = new WatchManager();
@ -61,13 +48,13 @@ for (const key of Object.keys(SETTINGS)) {
} }
const LEVEL_HANDLERS = { const LEVEL_HANDLERS = {
"device": new DeviceSettingsHandler(featureNames, defaultWatchManager), [SettingLevel.DEVICE]: new DeviceSettingsHandler(featureNames, defaultWatchManager),
"room-device": new RoomDeviceSettingsHandler(defaultWatchManager), [SettingLevel.ROOM_DEVICE]: new RoomDeviceSettingsHandler(defaultWatchManager),
"room-account": new RoomAccountSettingsHandler(defaultWatchManager), [SettingLevel.ROOM_ACCOUNT]: new RoomAccountSettingsHandler(defaultWatchManager),
"account": new AccountSettingsHandler(defaultWatchManager), [SettingLevel.ACCOUNT]: new AccountSettingsHandler(defaultWatchManager),
"room": new RoomSettingsHandler(defaultWatchManager), [SettingLevel.ROOM]: new RoomSettingsHandler(defaultWatchManager),
"config": new ConfigSettingsHandler(), [SettingLevel.CONFIG]: new ConfigSettingsHandler(),
"default": new DefaultSettingsHandler(defaultSettings, invertedDefaultSettings), [SettingLevel.DEFAULT]: new DefaultSettingsHandler(defaultSettings, invertedDefaultSettings),
}; };
// Wrap all the handlers with local echo // Wrap all the handlers with local echo
@ -76,20 +63,41 @@ for (const key of Object.keys(LEVEL_HANDLERS)) {
} }
const LEVEL_ORDER = [ 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 * 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 * 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: * value should be given a set of circumstances. The levels, in priority order, are:
* - "device" - Values are determined by the current device * - SettingLevel.DEVICE - Values are determined by the current device
* - "room-device" - Values are determined by the current device for a particular room * - SettingLevel.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 * - SettingLevel.ROOM_ACCOUNT - Values are determined by the current account for a particular room
* - "account" - Values are determined by the current account * - SettingLevel.ACCOUNT - Values are determined by the current account
* - "room" - Values are determined by a particular room (by the room admins) * - SettingLevel.ROOM - Values are determined by a particular room (by the room admins)
* - "config" - Values are determined by the config.json * - SettingLevel.CONFIG - Values are determined by the config.json
* - "default" - Values are determined by the hardcoded defaults * - SettingLevel.DEFAULT - Values are determined by the hardcoded defaults
* *
* Each level has a different method to storing the setting value. For implementation * 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 * 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 // 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 // when the setting changes. We track which rooms we're monitoring though to ensure we
// don't duplicate updates on the bus. // don't duplicate updates on the bus.
static _watchers = {}; // { callbackRef => { callbackFn } } private static watchers = {}; // { callbackRef => { callbackFn } }
static _monitors = {}; // { settingName => { roomId => callbackRef } } private static monitors = {}; // { settingName => { roomId => callbackRef } }
// Counter used for generation of watcher IDs // 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 * 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. * if the change in value is worthwhile enough to react upon.
* @returns {string} A reference to the watcher that was employed. * @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 setting = SETTINGS[settingName];
const originalSettingName = settingName; const originalSettingName = settingName;
if (!setting) throw new Error(`${settingName} is not a setting`); if (!setting) throw new Error(`${settingName} is not a setting`);
@ -141,14 +149,14 @@ export default class SettingsStore {
settingName = setting.invertedSettingName; 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 localizedCallback = (changedInRoomId, atLevel, newValAtLevel) => {
const newValue = SettingsStore.getValue(originalSettingName); const newValue = SettingsStore.getValue(originalSettingName);
callbackFn(originalSettingName, changedInRoomId, atLevel, newValAtLevel, newValue); callbackFn(originalSettingName, changedInRoomId, atLevel, newValAtLevel, newValue);
}; };
SettingsStore._watchers[watcherId] = localizedCallback; SettingsStore.watchers[watcherId] = localizedCallback;
defaultWatchManager.watchSetting(settingName, roomId, localizedCallback); defaultWatchManager.watchSetting(settingName, roomId, localizedCallback);
return watcherId; return watcherId;
@ -160,14 +168,14 @@ export default class SettingsStore {
* @param {string} watcherReference The watcher reference (received from #watchSetting) * @param {string} watcherReference The watcher reference (received from #watchSetting)
* to cancel. * to cancel.
*/ */
static unwatchSetting(watcherReference) { public static unwatchSetting(watcherReference: string) {
if (!SettingsStore._watchers[watcherReference]) { if (!SettingsStore.watchers[watcherReference]) {
console.warn(`Ending non-existent watcher ID ${watcherReference}`); console.warn(`Ending non-existent watcher ID ${watcherReference}`);
return; return;
} }
defaultWatchManager.unwatchSetting(SettingsStore._watchers[watcherReference]); defaultWatchManager.unwatchSetting(SettingsStore.watchers[watcherReference]);
delete 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} settingName The setting name to monitor.
* @param {String} roomId The room ID to monitor for changes in. Use null for all rooms. * @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. 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 = () => { const registerWatcher = () => {
this._monitors[settingName][roomId] = SettingsStore.watchSetting( this.monitors[settingName][roomId] = SettingsStore.watchSetting(
settingName, roomId, (settingName, inRoomId, level, newValueAtLevel, newValue) => { settingName, roomId, (settingName, inRoomId, level, newValueAtLevel, newValue) => {
dis.dispatch({ dis.dispatch({
action: 'setting_updated', 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) { if (!hasRoom) {
registerWatcher(); registerWatcher();
} else { } else {
if (roomId === null) { if (roomId === null) {
// Unregister all existing watchers and register the new one // Unregister all existing watchers and register the new one
for (const roomId of Object.keys(this._monitors[settingName])) { for (const roomId of Object.keys(this.monitors[settingName])) {
SettingsStore.unwatchSetting(this._monitors[settingName][roomId]); SettingsStore.unwatchSetting(this.monitors[settingName][roomId]);
} }
this._monitors[settingName] = {}; this.monitors[settingName] = {};
registerWatcher(); registerWatcher();
} // else a watcher is already registered for the room, so don't bother registering it again } // 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 * Gets the translated display name for a given setting
* @param {string} settingName The setting to look up. * @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'. * The level to get the display name for; Defaults to 'default'.
* @return {String} The display name for the setting, or null if not found. * @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; if (!SETTINGS[settingName] || !SETTINGS[settingName].displayName) return null;
let displayName = SETTINGS[settingName].displayName; let displayName = SETTINGS[settingName].displayName;
@ -229,20 +237,20 @@ export default class SettingsStore {
else displayName = displayName["default"]; else displayName = displayName["default"];
} }
return _t(displayName); return _t(displayName as string);
} }
/** /**
* Returns a list of all available labs feature names * Returns a list of all available labs feature names
* @returns {string[]} The list of available 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 possibleFeatures = Object.keys(SETTINGS).filter((s) => SettingsStore.isFeature(s));
const enableLabs = SdkConfig.get()["enableLabs"]; const enableLabs = SdkConfig.get()["enableLabs"];
if (enableLabs) return possibleFeatures; 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. * @param {string} settingName The setting to look up.
* @return {boolean} True if the setting is a feature. * @return {boolean} True if the setting is a feature.
*/ */
static isFeature(settingName) { public static isFeature(settingName: string) {
if (!SETTINGS[settingName]) return false; if (!SETTINGS[settingName]) return false;
return SETTINGS[settingName].isFeature; 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. * @param {String} roomId The optional room ID to validate in, may be null.
* @return {boolean} True if the feature is enabled, false otherwise * @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)) { if (!SettingsStore.isFeature(settingName)) {
throw new Error("Setting " + settingName + " is not a feature"); 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. * @param {boolean} value True to enable the feature, false otherwise.
* @returns {Promise} Resolves when the setting has been set. * @returns {Promise} Resolves when the setting has been set.
*/ */
static setFeatureEnabled(settingName, value) { public static setFeatureEnabled(settingName: string, value: any): Promise<void> {
// Verify that the setting is actually a setting // Verify that the setting is actually a setting
if (!SETTINGS[settingName]) { if (!SETTINGS[settingName]) {
throw new Error("Setting '" + settingName + "' does not appear to be a setting."); 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"); 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. * @param {boolean} excludeDefault True to disable using the default value.
* @return {*} The value, or null if not found * @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 // Verify that the setting is actually a setting
if (!SETTINGS[settingName]) { if (!SETTINGS[settingName]) {
throw new Error("Setting '" + settingName + "' does not appear to be a setting."); 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. * 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. * level to look at.
* @param {string} settingName The name of the setting to read. * @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. * @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. * @param {boolean} excludeDefault True to disable using the default value.
* @return {*} The value, or null if not found. * @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 // Verify that the setting is actually a setting
const setting = SETTINGS[settingName]; const setting = SETTINGS[settingName];
if (!setting) { if (!setting) {
@ -327,19 +341,19 @@ export default class SettingsStore {
} }
const levelOrder = (setting.supportedLevelsAreOrdered ? setting.supportedLevels : LEVEL_ORDER); 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); const minIndex = levelOrder.indexOf(level);
if (minIndex === -1) throw new Error("Level " + level + " is not prioritized"); if (minIndex === -1) throw new Error("Level " + level + " is not prioritized");
if (SettingsStore.isFeature(settingName)) { if (SettingsStore.isFeature(settingName)) {
const configValue = SettingsStore._getFeatureState(settingName); const configValue = SettingsStore.getFeatureState(settingName);
if (configValue === "enable") return true; if (configValue === "enable") return true;
if (configValue === "disable") return false; if (configValue === "disable") return false;
// else let it fall through the default process // 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 // 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. // handlers though, otherwise we'll fail to read the value.
@ -351,10 +365,10 @@ export default class SettingsStore {
if (explicit) { if (explicit) {
const handler = handlers[level]; const handler = handlers[level];
if (!handler) { if (!handler) {
return SettingsStore._getFinalValue(setting, level, roomId, null, null); return SettingsStore.getFinalValue(setting, level, roomId, null, null);
} }
const value = handler.getValue(settingName, roomId); 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++) { for (let i = minIndex; i < levelOrder.length; i++) {
@ -364,10 +378,10 @@ export default class SettingsStore {
const value = handler.getValue(settingName, roomId); const value = handler.getValue(settingName, roomId);
if (value === null || value === undefined) continue; 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. * @param {String} roomId The room ID to read the setting value in, may be null.
* @return {*} The default value * @return {*} The default value
*/ */
static getDefaultValue(settingName) { public static getDefaultValue(settingName: string): any {
// Verify that the setting is actually a setting // Verify that the setting is actually a setting
if (!SETTINGS[settingName]) { if (!SETTINGS[settingName]) {
throw new Error("Setting '" + settingName + "' does not appear to be a setting."); throw new Error("Setting '" + settingName + "' does not appear to be a setting.");
@ -385,7 +399,13 @@ export default class SettingsStore {
return SETTINGS[settingName].default; 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; let resultingValue = calculatedValue;
if (setting.controller) { if (setting.controller) {
@ -404,20 +424,21 @@ export default class SettingsStore {
* to indicate that the level should no longer have an override. * to indicate that the level should no longer have an override.
* @param {string} settingName The name of the setting to change. * @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 {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. * to change the value at.
* @param {*} value The new value of the setting, may be null. * @param {*} value The new value of the setting, may be null.
* @return {Promise} Resolves when the setting has been changed. * @return {Promise} Resolves when the setting has been changed.
*/ */
/* eslint-enable valid-jsdoc */ /* eslint-enable valid-jsdoc */
static async setValue(settingName, roomId, level, value) { public static async setValue(settingName: string, roomId: string, level: SettingLevel, value: any): Promise<void> {
// Verify that the setting is actually a setting // Verify that the setting is actually a setting
const setting = SETTINGS[settingName]; const setting = SETTINGS[settingName];
if (!setting) { if (!setting) {
throw new Error("Setting '" + settingName + "' does not appear to be a 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) { if (!handler) {
throw new Error("Setting " + settingName + " does not have a handler for " + level); 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. * set for a particular room, otherwise it should be supplied.
* @param {string} settingName The name of the setting to check. * @param {string} settingName The name of the setting to check.
* @param {String} roomId The room ID to check in, may be null. * @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. * check at.
* @return {boolean} True if the user may set the setting, false otherwise. * @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 // Verify that the setting is actually a setting
if (!SETTINGS[settingName]) { if (!SETTINGS[settingName]) {
throw new Error("Setting '" + settingName + "' does not appear to be a 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) return false; if (!handler) return false;
return handler.canSetValue(settingName, roomId); return handler.canSetValue(settingName, roomId);
} }
/** /**
* Determines if the given level is supported on this device. * 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. * to check the feasibility of.
* @return {boolean} True if the level is supported, false otherwise. * @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; if (!LEVEL_HANDLERS[level]) return false;
return LEVEL_HANDLERS[level].isSupported(); 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} realSettingName The setting name to try and read.
* @param {string} roomId Optional room ID to test the setting in. * @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}`); console.log(`--- DEBUG ${realSettingName}`);
// Note: we intentionally use JSON.stringify here to avoid the console masking the // 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`); console.log(`--- END DEBUG`);
} }
static _getHandler(settingName, level) { private static getHandler(settingName: string, level: SettingLevel): SettingsHandler {
const handlers = SettingsStore._getHandlers(settingName); const handlers = SettingsStore.getHandlers(settingName);
if (!handlers[level]) return null; if (!handlers[level]) return null;
return handlers[level]; return handlers[level];
} }
static _getHandlers(settingName) { private static getHandlers(settingName: string): IHandlerMap {
if (!SETTINGS[settingName]) return {}; if (!SETTINGS[settingName]) return {};
const handlers = {}; const handlers = {};
@ -591,7 +612,7 @@ export default class SettingsStore {
return handlers; return handlers;
} }
static _getFeatureState(settingName) { private static getFeatureState(settingName: string): LabsFeatureState {
const featuresConfig = SdkConfig.get()['features']; const featuresConfig = SdkConfig.get()['features'];
const enableLabs = SdkConfig.get()['enableLabs']; // we'll honour the old flag const enableLabs = SdkConfig.get()['enableLabs']; // we'll honour the old flag
@ -611,4 +632,4 @@ export default class SettingsStore {
} }
// For debugging purposes // For debugging purposes
global.mxSettingsStore = SettingsStore; window.mxSettingsStore = SettingsStore;

View file

@ -14,41 +14,52 @@ See the License for the specific language governing permissions and
limitations under the License. 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) * 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 * basis without duplicating code. Handlers are expected to push updates through this
* class, which are then proxied outwards to any applicable watchers. * class, which are then proxied outwards to any applicable watchers.
*/ */
export class WatchManager { export class WatchManager {
_watchers = {}; // { settingName: { roomId: callbackFns[] } } private watchers: {[settingName: string]: RoomWatcherMap} = {};
// Proxy for handlers to delegate changes to this manager // Proxy for handlers to delegate changes to this manager
watchSetting(settingName, roomId, cb) { public watchSetting(settingName: string, roomId: string | null, cb: CallbackFn) {
if (!this._watchers[settingName]) this._watchers[settingName] = {}; if (!this.watchers[settingName]) this.watchers[settingName] = {};
if (!this._watchers[settingName][roomId]) this._watchers[settingName][roomId] = []; if (!this.watchers[settingName][roomId]) this.watchers[settingName][roomId] = [];
this._watchers[settingName][roomId].push(cb); this.watchers[settingName][roomId].push(cb);
} }
// Proxy for handlers to delegate changes to this manager // Proxy for handlers to delegate changes to this manager
unwatchSetting(cb) { public unwatchSetting(cb: CallbackFn) {
for (const settingName of Object.keys(this._watchers)) { for (const settingName of Object.keys(this.watchers)) {
for (const roomId of Object.keys(this._watchers[settingName])) { for (const roomId of Object.keys(this.watchers[settingName])) {
let idx; let idx;
while ((idx = this._watchers[settingName][roomId].indexOf(cb)) !== -1) { while ((idx = this.watchers[settingName][roomId].indexOf(cb)) !== -1) {
this._watchers[settingName][roomId].splice(idx, 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 // 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 // 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. // 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 = []; const callbacks = [];
if (inRoomId !== null && roomWatchers[inRoomId]) { if (inRoomId !== null && roomWatchers[inRoomId]) {
@ -59,8 +70,8 @@ export class WatchManager {
// Fire updates to all the individual room watchers too, as they probably // Fire updates to all the individual room watchers too, as they probably
// care about the change higher up. // care about the change higher up.
callbacks.push(...Object.values(roomWatchers).reduce((r, a) => [...r, ...a], [])); callbacks.push(...Object.values(roomWatchers).reduce((r, a) => [...r, ...a], []));
} else if (roomWatchers[null]) { } else if (roomWatchers[IRRELEVANT_ROOM]) {
callbacks.push(...roomWatchers[null]); callbacks.push(...roomWatchers[IRRELEVANT_ROOM]);
} }
for (const callback of callbacks) { for (const callback of callbacks) {

View file

@ -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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 SettingController from "./SettingController";
import dis from "../../dispatcher/dispatcher"; import dis from "../../dispatcher/dispatcher";
import { SettingLevel } from "../SettingLevel";
export default class CustomStatusController extends SettingController { 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 // Dispatch setting change so that some components that are still visible when the
// Settings page is open (such as RoomTiles) can reflect the change. // Settings page is open (such as RoomTiles) can reflect the change.
dis.dispatch({ dis.dispatch({

View file

@ -18,13 +18,14 @@ import SettingController from "./SettingController";
import dis from "../../dispatcher/dispatcher"; import dis from "../../dispatcher/dispatcher";
import { UpdateFontSizePayload } from "../../dispatcher/payloads/UpdateFontSizePayload"; import { UpdateFontSizePayload } from "../../dispatcher/payloads/UpdateFontSizePayload";
import { Action } from "../../dispatcher/actions"; import { Action } from "../../dispatcher/actions";
import { SettingLevel } from "../SettingLevel";
export default class FontSizeController extends SettingController { export default class FontSizeController extends SettingController {
constructor() { constructor() {
super(); 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. // Dispatch font size change so that everything open responds to the change.
dis.dispatch<UpdateFontSizePayload>({ dis.dispatch<UpdateFontSizePayload>({
action: Action.UpdateFontSize, action: Action.UpdateFontSize,

View file

@ -1,5 +1,6 @@
/* /*
Copyright 2017 Travis Ralston Copyright 2017 Travis Ralston
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 SettingController from "./SettingController";
import {MatrixClientPeg} from '../../MatrixClientPeg'; import {MatrixClientPeg} from '../../MatrixClientPeg';
import { SettingLevel } from "../SettingLevel";
// XXX: This feels wrong. // XXX: This feels wrong.
import {PushProcessor} from "matrix-js-sdk/src/pushprocessor"; import {PushProcessor} from "matrix-js-sdk/src/pushprocessor";
// .m.rule.master being enabled means all events match that push rule // .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 // 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 // Return the value of the master push rule as a default
const processor = new PushProcessor(MatrixClientPeg.get()); const processor = new PushProcessor(MatrixClientPeg.get());
const masterRule = processor.getPushRuleById(".m.rule.master"); const masterRule = processor.getPushRuleById(".m.rule.master");
@ -36,14 +38,20 @@ function isPushNotifyDisabled() {
return masterRule.enabled && !masterRule.actions.includes("notify"); 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 let Notifier = require('../../Notifier'); // avoids cyclical references
if (Notifier.default) Notifier = Notifier.default; // correct for webpack require() weirdness if (Notifier.default) Notifier = Notifier.default; // correct for webpack require() weirdness
return Notifier; return Notifier;
} }
export class NotificationsEnabledController extends SettingController { 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 (!getNotifier().isPossible()) return false;
if (calculatedValue === null || calculatedAtLevel === "default") { if (calculatedValue === null || calculatedAtLevel === "default") {
@ -53,7 +61,7 @@ export class NotificationsEnabledController extends SettingController {
return calculatedValue; return calculatedValue;
} }
onChange(level, roomId, newValue) { public onChange(level: SettingLevel, roomId: string, newValue: any) {
if (getNotifier().supportsDesktopNotifications()) { if (getNotifier().supportsDesktopNotifications()) {
getNotifier().setEnabled(newValue); getNotifier().setEnabled(newValue);
} }
@ -61,7 +69,7 @@ export class NotificationsEnabledController extends SettingController {
} }
export class NotificationBodyEnabledController 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 (!getNotifier().isPossible()) return false;
if (calculatedValue === null) { if (calculatedValue === null) {
@ -73,7 +81,7 @@ export class NotificationBodyEnabledController extends SettingController {
} }
export class AudioNotificationsEnabledController 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; if (!getNotifier().isPossible()) return false;
// Note: Audio notifications are *not* enabled by default. // Note: Audio notifications are *not* enabled by default.

View file

@ -15,23 +15,20 @@ limitations under the License.
*/ */
import { MatrixClientPeg } from '../../MatrixClientPeg'; 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 * When the value changes, call a setter function on the matrix client with the new value
*/ */
export default class PushToMatrixClientController { export default class PushToMatrixClientController extends SettingController {
constructor(setter, inverse) { constructor(private setter: Function, private inverse: boolean) {
this._setter = setter; super();
this._inverse = inverse;
} }
getValueOverride(level, roomId, calculatedValue, calculatedAtLevel) { public onChange(level: SettingLevel, roomId: string, newValue: any) {
return null; // no override
}
onChange(level, roomId, newValue) {
// XXX does this work? This surely isn't necessarily the effective value, // XXX does this work? This surely isn't necessarily the effective value,
// but it's what NotificationsEnabledController does... // but it's what NotificationsEnabledController does...
this._setter.call(MatrixClientPeg.get(), this._inverse ? !newValue : newValue); this.setter.call(MatrixClientPeg.get(), this.inverse ? !newValue : newValue);
} }
} }

View file

@ -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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 SettingController from "./SettingController";
import PlatformPeg from "../../PlatformPeg"; import PlatformPeg from "../../PlatformPeg";
import { SettingLevel } from "../SettingLevel";
export default class ReloadOnChangeController extends SettingController { export default class ReloadOnChangeController extends SettingController {
onChange(level, roomId, newValue) { public onChange(level: SettingLevel, roomId: string, newValue: any) {
PlatformPeg.get().reload(); PlatformPeg.get().reload();
} }
} }

View file

@ -1,5 +1,6 @@
/* /*
Copyright 2017 Travis Ralston Copyright 2017 Travis Ralston
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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. limitations under the License.
*/ */
import { SettingLevel } from "../SettingLevel";
/** /**
* Represents a controller for individual settings to alter the reading behaviour * Represents a controller for individual settings to alter the reading behaviour
* based upon environmental conditions, or to react to changes and therefore update * 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 * This is not intended to replace the functionality of a SettingsHandler, it is only
* intended to handle environmental factors for specific settings. * 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 * 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. * 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 {String} roomId The room ID, may be null.
* @param {*} calculatedValue The value that the handlers think the setting should be, * @param {*} calculatedValue The value that the handlers think the setting should be,
* may be null. * 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. * calculated at. May be null.
* @return {*} The value that should be used, or null if no override is applicable. * @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 return null; // no override
} }
@ -44,7 +52,7 @@ export default class SettingController {
* @param {String} roomId The room ID, may be null. * @param {String} roomId The room ID, may be null.
* @param {*} newValue The new value for the setting, 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 // do nothing by default
} }
} }

View file

@ -19,13 +19,14 @@ import SettingsStore from "../SettingsStore";
import dis from "../../dispatcher/dispatcher"; import dis from "../../dispatcher/dispatcher";
import { UpdateSystemFontPayload } from "../../dispatcher/payloads/UpdateSystemFontPayload"; import { UpdateSystemFontPayload } from "../../dispatcher/payloads/UpdateSystemFontPayload";
import { Action } from "../../dispatcher/actions"; import { Action } from "../../dispatcher/actions";
import { SettingLevel } from "../SettingLevel";
export default class SystemFontController extends SettingController { export default class SystemFontController extends SettingController {
constructor() { constructor() {
super(); 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. // Dispatch font size change so that everything open responds to the change.
dis.dispatch<UpdateSystemFontPayload>({ dis.dispatch<UpdateSystemFontPayload>({
action: Action.UpdateSystemFont, action: Action.UpdateSystemFont,

View file

@ -1,6 +1,6 @@
/* /*
Copyright 2019 New Vector Ltd
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 SettingController from "./SettingController";
import {DEFAULT_THEME, enumerateThemes} from "../../theme"; import {DEFAULT_THEME, enumerateThemes} from "../../theme";
import { SettingLevel } from "../SettingLevel";
export default class ThemeController extends SettingController { 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 (!calculatedValue) return null; // Don't override null themes
if (ThemeController.isLogin) return 'light'; if (ThemeController.isLogin) return 'light';

View file

@ -19,13 +19,14 @@ import SettingsStore from "../SettingsStore";
import dis from "../../dispatcher/dispatcher"; import dis from "../../dispatcher/dispatcher";
import { UpdateSystemFontPayload } from "../../dispatcher/payloads/UpdateSystemFontPayload"; import { UpdateSystemFontPayload } from "../../dispatcher/payloads/UpdateSystemFontPayload";
import { Action } from "../../dispatcher/actions"; import { Action } from "../../dispatcher/actions";
import { SettingLevel } from "../SettingLevel";
export default class UseSystemFontController extends SettingController { export default class UseSystemFontController extends SettingController {
constructor() { constructor() {
super(); 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. // Dispatch font size change so that everything open responds to the change.
dis.dispatch<UpdateSystemFontPayload>({ dis.dispatch<UpdateSystemFontPayload>({
action: Action.UpdateSystemFont, action: Action.UpdateSystemFont,

View file

@ -1,6 +1,6 @@
/* /*
Copyright 2017 Travis Ralston 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 {MatrixClientPeg} from '../../MatrixClientPeg';
import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler"; 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";
const BREADCRUMBS_LEGACY_EVENT_TYPE = "im.vector.riot.breadcrumb_rooms"; const BREADCRUMBS_LEGACY_EVENT_TYPE = "im.vector.riot.breadcrumb_rooms";
const BREADCRUMBS_EVENT_TYPE = "im.vector.setting.breadcrumbs"; const BREADCRUMBS_EVENT_TYPE = "im.vector.setting.breadcrumbs";
const BREADCRUMBS_EVENT_TYPES = [BREADCRUMBS_LEGACY_EVENT_TYPE, BREADCRUMBS_EVENT_TYPE]; const BREADCRUMBS_EVENT_TYPES = [BREADCRUMBS_LEGACY_EVENT_TYPE, BREADCRUMBS_EVENT_TYPE];
const RECENT_EMOJI_EVENT_TYPE = "io.element.recent_emoji"; const RECENT_EMOJI_EVENT_TYPE = "io.element.recent_emoji";
const INTEG_PROVISIONING_EVENT_TYPE = "im.vector.setting.integration_provisioning"; 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. * This handler does not make use of the roomId parameter.
*/ */
export default class AccountSettingsHandler extends MatrixClientBackedSettingsHandler { export default class AccountSettingsHandler extends MatrixClientBackedSettingsHandler {
constructor(watchManager) { constructor(private watchers: WatchManager) {
super(); super();
this._watchers = watchManager;
this._onAccountData = this._onAccountData.bind(this);
} }
initMatrixClient(oldClient, newClient) { public initMatrixClient(oldClient: MatrixClient, newClient: MatrixClient) {
if (oldClient) { 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") { if (event.getType() === "org.matrix.preview_urls") {
let val = event.getContent()['disable']; let val = event.getContent()['disable'];
if (typeof(val) !== "boolean") { if (typeof(val) !== "boolean") {
@ -56,30 +55,30 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
val = !val; 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") { } else if (event.getType() === "im.vector.web.settings") {
// Figure out what changed and fire those updates // Figure out what changed and fire those updates
const prevContent = prevEvent ? prevEvent.getContent() : {}; const prevContent = prevEvent ? prevEvent.getContent() : {};
const changedSettings = objectKeyChanges(prevContent, event.getContent()); const changedSettings = objectKeyChanges(prevContent, event.getContent());
for (const settingName of changedSettings) { for (const settingName of changedSettings) {
const val = event.getContent()[settingName]; 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())) { } else if (BREADCRUMBS_EVENT_TYPES.includes(event.getType())) {
this._notifyBreadcrumbsUpdate(event); this.notifyBreadcrumbsUpdate(event);
} else if (event.getType() === INTEG_PROVISIONING_EVENT_TYPE) { } else if (event.getType() === INTEG_PROVISIONING_EVENT_TYPE) {
const val = event.getContent()['enabled']; 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) { } else if (event.getType() === RECENT_EMOJI_EVENT_TYPE) {
const val = event.getContent()['enabled']; 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 // Special case URL previews
if (settingName === "urlPreviewsEnabled") { 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 // Check to make sure that we actually got a boolean
if (typeof(content['disable']) !== "boolean") return null; if (typeof(content['disable']) !== "boolean") return null;
@ -88,9 +87,9 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
// Special case for breadcrumbs // Special case for breadcrumbs
if (settingName === "breadcrumb_rooms") { if (settingName === "breadcrumb_rooms") {
let content = this._getSettings(BREADCRUMBS_EVENT_TYPE); let content = this.getSettings(BREADCRUMBS_EVENT_TYPE);
if (!content || !content['recent_rooms']) { 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 // This is a bit of a hack, but it makes things slightly easier
if (content) content['recent_rooms'] = content['rooms']; if (content) content['recent_rooms'] = content['rooms'];
@ -101,17 +100,17 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
// Special case recent emoji // Special case recent emoji
if (settingName === "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; return content ? content["recent_emoji"] : null;
} }
// Special case integration manager provisioning // Special case integration manager provisioning
if (settingName === "integrationProvisioning") { if (settingName === "integrationProvisioning") {
const content = this._getSettings(INTEG_PROVISIONING_EVENT_TYPE); const content = this.getSettings(INTEG_PROVISIONING_EVENT_TYPE);
return content ? content['enabled'] : null; return content ? content['enabled'] : null;
} }
const settings = this._getSettings() || {}; const settings = this.getSettings() || {};
let preferredValue = settings[settingName]; let preferredValue = settings[settingName];
if (preferredValue === null || preferredValue === undefined) { if (preferredValue === null || preferredValue === undefined) {
@ -124,10 +123,10 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
return preferredValue; return preferredValue;
} }
setValue(settingName, roomId, newValue) { public setValue(settingName: string, roomId: string, newValue: any): Promise<void> {
// Special case URL previews // Special case URL previews
if (settingName === "urlPreviewsEnabled") { if (settingName === "urlPreviewsEnabled") {
const content = this._getSettings("org.matrix.preview_urls") || {}; const content = this.getSettings("org.matrix.preview_urls") || {};
content['disable'] = !newValue; content['disable'] = !newValue;
return MatrixClientPeg.get().setAccountData("org.matrix.preview_urls", content); return MatrixClientPeg.get().setAccountData("org.matrix.preview_urls", content);
} }
@ -135,9 +134,9 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
// Special case for breadcrumbs // Special case for breadcrumbs
if (settingName === "breadcrumb_rooms") { if (settingName === "breadcrumb_rooms") {
// We read the value first just to make sure we preserve whatever random keys might be present. // 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']) { 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 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 // Special case recent emoji
if (settingName === "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; content["recent_emoji"] = newValue;
return MatrixClientPeg.get().setAccountData(RECENT_EMOJI_EVENT_TYPE, content); return MatrixClientPeg.get().setAccountData(RECENT_EMOJI_EVENT_TYPE, content);
} }
// Special case integration manager provisioning // Special case integration manager provisioning
if (settingName === "integrationProvisioning") { if (settingName === "integrationProvisioning") {
const content = this._getSettings(INTEG_PROVISIONING_EVENT_TYPE) || {}; const content = this.getSettings(INTEG_PROVISIONING_EVENT_TYPE) || {};
content['enabled'] = newValue; content['enabled'] = newValue;
return MatrixClientPeg.get().setAccountData(INTEG_PROVISIONING_EVENT_TYPE, content); return MatrixClientPeg.get().setAccountData(INTEG_PROVISIONING_EVENT_TYPE, content);
} }
const content = this._getSettings() || {}; const content = this.getSettings() || {};
content[settingName] = newValue; content[settingName] = newValue;
return MatrixClientPeg.get().setAccountData("im.vector.web.settings", content); 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 return true; // It's their account, so they should be able to
} }
isSupported() { public isSupported(): boolean {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
return cli !== undefined && cli !== null; 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(); const cli = MatrixClientPeg.get();
if (!cli) return null; if (!cli) return null;
@ -182,11 +181,11 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
return objectClone(event.getContent()); // clone to prevent mutation return objectClone(event.getContent()); // clone to prevent mutation
} }
_notifyBreadcrumbsUpdate(event) { private notifyBreadcrumbsUpdate(event: MatrixEvent) {
let val = []; let val = [];
if (event.getType() === BREADCRUMBS_LEGACY_EVENT_TYPE) { if (event.getType() === BREADCRUMBS_LEGACY_EVENT_TYPE) {
// This seems fishy - try and get the event for the new rooms // 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']; if (newType) val = newType['recent_rooms'];
else val = event.getContent()['rooms']; else val = event.getContent()['rooms'];
} else if (event.getType() === BREADCRUMBS_EVENT_TYPE) { } else if (event.getType() === BREADCRUMBS_EVENT_TYPE) {
@ -194,6 +193,6 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
} else { } else {
return; // for sanity, not because we expect to be here. 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 || []);
} }
} }

View file

@ -1,6 +1,6 @@
/* /*
Copyright 2017 Travis Ralston 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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. * roomId parameter.
*/ */
export default class ConfigSettingsHandler extends SettingsHandler { export default class ConfigSettingsHandler extends SettingsHandler {
getValue(settingName, roomId) { public getValue(settingName: string, roomId: string): any {
const config = SdkConfig.get() || {}; const config = SdkConfig.get() || {};
// Special case themes // Special case themes
@ -37,15 +37,15 @@ export default class ConfigSettingsHandler extends SettingsHandler {
return settingsConfig[settingName]; return settingsConfig[settingName];
} }
setValue(settingName, roomId, newValue) { public async setValue(settingName: string, roomId: string, newValue: any): Promise<void> {
throw new Error("Cannot change settings at the config level"); throw new Error("Cannot change settings at the config level");
} }
canSetValue(settingName, roomId) { public canSetValue(settingName: string, roomId: string): boolean {
return false; return false;
} }
isSupported() { public isSupported(): boolean {
return true; // SdkConfig is always there return true; // SdkConfig is always there
} }
} }

View file

@ -1,6 +1,6 @@
/* /*
Copyright 2017 Travis Ralston 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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} defaults The default setting values, keyed by setting name.
* @param {object} invertedDefaults The default inverted 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<string, any>, private invertedDefaults: Record<string, any>) {
super(); super();
this._defaults = defaults;
this._invertedDefaults = invertedDefaults;
} }
getValue(settingName, roomId) { public getValue(settingName: string, roomId: string): any {
let value = this._defaults[settingName]; let value = this.defaults[settingName];
if (value === undefined) { if (value === undefined) {
value = this._invertedDefaults[settingName]; value = this.invertedDefaults[settingName];
} }
return value; return value;
} }
setValue(settingName, roomId, newValue) { public async setValue(settingName: string, roomId: string, newValue: any): Promise<void> {
throw new Error("Cannot set values on the default level handler"); throw new Error("Cannot set values on the default level handler");
} }
canSetValue(settingName, roomId) { public canSetValue(settingName: string, roomId: string) {
return false; return false;
} }
isSupported() { public isSupported(): boolean {
return true; return true;
} }
} }

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