Merge branch 'develop' into travis/msc-send-widget-events

This commit is contained in:
Travis Ralston 2020-11-16 15:08:52 -07:00
commit 94550546eb
107 changed files with 3225 additions and 1480 deletions

View file

@ -1,3 +1,79 @@
Changes in [3.8.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.8.0) (2020-11-09)
===================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.8.0-rc.1...v3.8.0)
* Upgrade JS SDK to 9.1.0
Changes in [3.8.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.8.0-rc.1) (2020-11-04)
=============================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.7.1...v3.8.0-rc.1)
* Upgrade JS SDK to 9.1.0-rc.1
* Log when saving profile
[\#5394](https://github.com/matrix-org/matrix-react-sdk/pull/5394)
* Translations update from Weblate
[\#5395](https://github.com/matrix-org/matrix-react-sdk/pull/5395)
* Hide prompt to add email for notifications if 3pid ui feature is off
[\#5392](https://github.com/matrix-org/matrix-react-sdk/pull/5392)
* Fix room list message preview copy for hangup events
[\#5388](https://github.com/matrix-org/matrix-react-sdk/pull/5388)
* Track UISIs as Countly Events
[\#5382](https://github.com/matrix-org/matrix-react-sdk/pull/5382)
* Don't let users accidentally redact ACL events
[\#5384](https://github.com/matrix-org/matrix-react-sdk/pull/5384)
* Two more easy files to remove from eslintignore
[\#5378](https://github.com/matrix-org/matrix-react-sdk/pull/5378)
* Fix Widget OpenID Permissions for realsies
[\#5381](https://github.com/matrix-org/matrix-react-sdk/pull/5381)
* Fix regression with OpenID permissions on widgets
[\#5380](https://github.com/matrix-org/matrix-react-sdk/pull/5380)
* Fix room directory events happening in the wrong order for Funnels
[\#5379](https://github.com/matrix-org/matrix-react-sdk/pull/5379)
* Remove a couple more files from eslintignore
[\#5377](https://github.com/matrix-org/matrix-react-sdk/pull/5377)
* Fix countly method bindings and errors
[\#5376](https://github.com/matrix-org/matrix-react-sdk/pull/5376)
* Fix a bunch of silly lint errors
[\#5375](https://github.com/matrix-org/matrix-react-sdk/pull/5375)
* Typescript: ImageUtils
[\#5374](https://github.com/matrix-org/matrix-react-sdk/pull/5374)
* Convert AuxPanel to TypeScript
[\#5373](https://github.com/matrix-org/matrix-react-sdk/pull/5373)
* Only pass metrics if they exist otherwise Countly will be unhappy!
[\#5372](https://github.com/matrix-org/matrix-react-sdk/pull/5372)
* Fix CountlyAnalytics NPE on MatrixClientPeg
[\#5370](https://github.com/matrix-org/matrix-react-sdk/pull/5370)
* fix CountlyAnalytics canEnable on wrong target
[\#5369](https://github.com/matrix-org/matrix-react-sdk/pull/5369)
* Initial Countly work
[\#5365](https://github.com/matrix-org/matrix-react-sdk/pull/5365)
* Fix videos not playing in non-encrypted rooms
[\#5368](https://github.com/matrix-org/matrix-react-sdk/pull/5368)
* Fix custom tag layout which regressed in #5309
[\#5367](https://github.com/matrix-org/matrix-react-sdk/pull/5367)
* Watch replyToEvent at RoomView to prevent races
[\#5360](https://github.com/matrix-org/matrix-react-sdk/pull/5360)
* Add a UI Feature flag for room history settings
[\#5362](https://github.com/matrix-org/matrix-react-sdk/pull/5362)
* Hide inline images when preference disabled
[\#5361](https://github.com/matrix-org/matrix-react-sdk/pull/5361)
* Fix React warning by moving handler to each button
[\#5359](https://github.com/matrix-org/matrix-react-sdk/pull/5359)
* Do not preload encrypted videos|images unless autoplay or thumbnailing is on
[\#5352](https://github.com/matrix-org/matrix-react-sdk/pull/5352)
* Fix theme variable passed to Jitsi
[\#5357](https://github.com/matrix-org/matrix-react-sdk/pull/5357)
* docs: added comment explanation
[\#5349](https://github.com/matrix-org/matrix-react-sdk/pull/5349)
* Modal Widgets - MSC2790
[\#5252](https://github.com/matrix-org/matrix-react-sdk/pull/5252)
* Widgets fixes
[\#5350](https://github.com/matrix-org/matrix-react-sdk/pull/5350)
* Fix User Menu avatar colouring being based on wrong string
[\#5348](https://github.com/matrix-org/matrix-react-sdk/pull/5348)
* Support 'answered elsewhere'
[\#5345](https://github.com/matrix-org/matrix-react-sdk/pull/5345)
Changes in [3.7.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.7.1) (2020-10-28)
===================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.7.0...v3.7.1)

View file

@ -1,6 +1,6 @@
{
"name": "matrix-react-sdk",
"version": "3.7.1",
"version": "3.8.0",
"description": "SDK for matrix.org using React",
"author": "matrix.org",
"repository": {
@ -79,7 +79,7 @@
"linkifyjs": "^2.1.9",
"lodash": "^4.17.19",
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
"matrix-widget-api": "^0.1.0-beta.5",
"matrix-widget-api": "^0.1.0-beta.8",
"minimist": "^1.2.5",
"pako": "^1.0.11",
"parse5": "^5.1.1",

View file

@ -17,6 +17,7 @@ limitations under the License.
*/
@import "./_font-sizes.scss";
@import "./_font-weights.scss";
$hover-transition: 0.08s cubic-bezier(.46, .03, .52, .96); // quadratic
@ -323,6 +324,7 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus {
.mx_Dialog_title {
font-size: $font-22px;
font-weight: $font-semi-bold;
line-height: $font-36px;
color: $dialog-title-fg-color;
}
@ -348,8 +350,8 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus {
background-color: $dialog-close-fg-color;
cursor: pointer;
position: absolute;
top: 4px;
right: 0px;
top: 10px;
right: 0;
}
.mx_Dialog_content {

View file

@ -110,11 +110,11 @@
@import "./views/elements/_EventListSummary.scss";
@import "./views/elements/_Field.scss";
@import "./views/elements/_FormButton.scss";
@import "./views/elements/_IconButton.scss";
@import "./views/elements/_ImageView.scss";
@import "./views/elements/_InfoTooltip.scss";
@import "./views/elements/_InlineSpinner.scss";
@import "./views/elements/_ManageIntegsButton.scss";
@import "./views/elements/_MiniAvatarUploader.scss";
@import "./views/elements/_PowerSelector.scss";
@import "./views/elements/_ProgressBar.scss";
@import "./views/elements/_QRCode.scss";
@ -139,6 +139,7 @@
@import "./views/groups/_GroupUserSettings.scss";
@import "./views/messages/_CreateEvent.scss";
@import "./views/messages/_DateSeparator.scss";
@import "./views/messages/_EventTileBubble.scss";
@import "./views/messages/_MEmoteBody.scss";
@import "./views/messages/_MFileBody.scss";
@import "./views/messages/_MImageBody.scss";
@ -182,6 +183,7 @@
@import "./views/rooms/_MemberList.scss";
@import "./views/rooms/_MessageComposer.scss";
@import "./views/rooms/_MessageComposerFormatBar.scss";
@import "./views/rooms/_NewRoomIntro.scss";
@import "./views/rooms/_NotificationBadge.scss";
@import "./views/rooms/_PinnedEventTile.scss";
@import "./views/rooms/_PinnedEventsPanel.scss";
@ -229,4 +231,4 @@
@import "./views/verification/_VerificationShowSas.scss";
@import "./views/voip/_CallContainer.scss";
@import "./views/voip/_CallView.scss";
@import "./views/voip/_VideoView.scss";
@import "./views/voip/_VideoFeed.scss";

View file

@ -26,9 +26,10 @@ limitations under the License.
.mx_HomePage_default {
text-align: center;
display: flex;
.mx_HomePage_default_wrapper {
padding: 25vh 0 12px;
margin: auto;
}
img {
@ -50,56 +51,54 @@ limitations under the License.
color: $muted-fg-color;
}
.mx_MiniAvatarUploader {
margin: 0 auto;
}
.mx_HomePage_default_buttons {
margin: 80px auto 0;
margin: 60px auto 0;
width: fit-content;
.mx_AccessibleButton {
padding: 73px 8px 15px; // top: 20px top padding + 40px icon + 13px margin
width: 104px; // 120px - 2* 8px
margin: 0 39px; // 55px - 2* 8px
width: 160px;
height: 132px;
margin: 20px;
position: relative;
display: inline-block;
border-radius: 8px;
vertical-align: top;
word-break: break-word;
box-sizing: border-box;
font-weight: 600;
font-size: $font-15px;
line-height: $font-20px;
color: $muted-fg-color;
&:hover {
color: $accent-color;
background: rgba($accent-color, 0.06);
&::before {
background-color: $accent-color;
}
}
color: #fff; // on all themes
background-color: $accent-color;
&::before {
top: 20px;
left: 40px; // (120px-40px)/2
left: 60px; // (160px-40px)/2
width: 40px;
height: 40px;
content: '';
position: absolute;
background-color: $muted-fg-color;
background-color: #fff; // on all themes
mask-repeat: no-repeat;
mask-size: contain;
}
&.mx_HomePage_button_sendDm::before {
mask-image: url('$(res)/img/feather-customised/message-circle.svg');
mask-image: url('$(res)/img/element-icons/feedback.svg');
}
&.mx_HomePage_button_explore::before {
mask-image: url('$(res)/img/feather-customised/explore.svg');
mask-image: url('$(res)/img/element-icons/roomlist/explore.svg');
}
&.mx_HomePage_button_createGroup::before {
mask-image: url('$(res)/img/feather-customised/group.svg');
mask-image: url('$(res)/img/element-icons/community-members.svg');
}
}
}

View file

@ -153,16 +153,6 @@ limitations under the License.
display: block;
}
.mx_RoomStatusBar_isAlone {
height: 50px;
line-height: $font-50px;
color: $primary-fg-color;
opacity: 0.5;
overflow-y: hidden;
display: block;
}
.mx_MatrixChat_useCompactLayout {
.mx_RoomStatusBar {
min-height: 40px;

View file

@ -27,37 +27,29 @@ limitations under the License.
padding-left: 8px;
overflow-x: hidden;
overflow-y: auto;
display: flex;
flex-wrap: wrap;
.mx_InviteDialog_userTile {
margin: 6px 6px 0 0;
display: inline-block;
float: left;
position: relative;
top: 7px;
min-width: max-content; // prevent manipulation by flexbox
}
// Using a textarea for this element, to circumvent autofill
// Mostly copied from AddressPickerDialog
textarea,
textarea:focus {
height: 34px;
line-height: $font-34px;
// Mostly copied from AddressPickerDialog; overrides bunch of our default text input styles
> input[type="text"] {
margin: 6px 0 !important;
height: 24px;
line-height: $font-24px;
font-size: $font-14px;
padding-left: 12px;
margin: 0 !important;
border: 0 !important;
outline: 0 !important;
resize: none;
overflow: hidden;
box-sizing: border-box;
word-wrap: nowrap;
// Roughly fill about 2/5ths of the available space. This is to try and 'fill' the
// remaining space after a bunch of pills, but is a bit hacky. Ideally we'd have
// support for "fill remaining width", but traditional tricks don't work with what
// we're pushing into this "field". Flexbox just makes things worse. The theory is
// that users won't need more than about 2/5ths of the input to find the person
// they're looking for.
width: 40%;
min-width: 40%;
flex: 1 !important;
color: $primary-fg-color !important;
}
}
@ -148,6 +140,10 @@ limitations under the License.
}
}
.mx_InviteDialog_roomTile_nameStack {
display: inline-block;
}
.mx_InviteDialog_roomTile_name {
font-weight: 600;
font-size: $font-14px;

View file

@ -1,55 +0,0 @@
/*
Copyright 2019 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_IconButton {
width: 32px;
height: 32px;
border-radius: 100%;
background-color: $accent-bg-color;
// don't shrink or grow if in a flex container
flex: 0 0 auto;
&.mx_AccessibleButton_disabled {
background-color: none;
&::before {
background-color: lightgrey;
}
}
&:hover {
opacity: 90%;
}
&::before {
content: "";
display: block;
width: 100%;
height: 100%;
mask-repeat: no-repeat;
mask-position: center;
mask-size: 55%;
background-color: $accent-color;
}
&.mx_IconButton_icon_check::before {
mask-image: url('$(res)/img/feather-customised/check.svg');
}
&.mx_IconButton_icon_edit::before {
mask-image: url('$(res)/img/feather-customised/edit.svg');
}
}

View file

@ -0,0 +1,56 @@
/*
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_MiniAvatarUploader {
position: relative;
width: min-content;
&::before, &::after {
content: '';
position: absolute;
height: 26px;
width: 26px;
right: -6px;
bottom: -6px;
}
&::before {
background-color: $primary-bg-color;
border-radius: 50%;
z-index: 1;
}
&::after {
background-color: $secondary-fg-color;
mask-position: center;
mask-repeat: no-repeat;
mask-image: url('$(res)/img/element-icons/camera.svg');
mask-size: 16px;
z-index: 2;
}
&.mx_MiniAvatarUploader_busy::after {
background: url("$(res)/img/spinner.gif") no-repeat center;
background-size: 80%;
mask: unset;
}
}
.mx_MiniAvatarUploader_input {
display: none;
}

View file

@ -1,5 +1,5 @@
/*
Copyright 2018 New Vector Ltd
Copyright 2018, 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -15,25 +15,8 @@ limitations under the License.
*/
.mx_CreateEvent {
background-color: $info-plinth-bg-color;
padding-left: 20px;
padding-right: 20px;
padding-top: 10px;
padding-bottom: 10px;
}
.mx_CreateEvent_image {
float: left;
margin-right: 20px;
width: 72px;
height: 34px;
background-color: $primary-fg-color;
mask: url('$(res)/img/room-continuation.svg');
mask-repeat: no-repeat;
mask-position: center;
}
.mx_CreateEvent_header {
font-weight: bold;
&::before {
background-color: $composer-e2e-icon-color;
mask-image: url('$(res)/img/element-icons/chat-bubbles.svg');
}
}

View file

@ -0,0 +1,60 @@
/*
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
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_EventTileBubble {
background-color: $dark-panel-bg-color;
padding: 10px;
border-radius: 8px;
margin: 10px auto;
max-width: 75%;
box-sizing: border-box;
display: grid;
grid-template-columns: 24px minmax(0, 1fr) min-content;
&::before, &::after {
position: relative;
grid-column: 1;
grid-row: 1 / 3;
width: 16px;
height: 16px;
content: "";
top: 0;
bottom: 0;
left: 0;
right: 0;
mask-repeat: no-repeat;
mask-position: center;
mask-size: contain;
margin-top: 4px;
}
.mx_EventTileBubble_title, .mx_EventTileBubble_subtitle {
overflow-wrap: break-word;
}
.mx_EventTileBubble_title {
font-weight: 600;
font-size: $font-15px;
grid-column: 2;
grid-row: 1;
}
.mx_EventTileBubble_subtitle {
font-size: $font-12px;
grid-column: 2;
grid-row: 2;
}
}

View file

@ -15,41 +15,8 @@ limitations under the License.
*/
.mx_MJitsiWidgetEvent {
display: grid;
grid-template-columns: 24px minmax(0, 1fr) min-content;
&::before {
grid-column: 1;
grid-row: 1 / 3;
width: 16px;
height: 16px;
content: "";
top: 0;
bottom: 0;
left: 0;
right: 0;
mask-repeat: no-repeat;
mask-position: center;
mask-size: contain;
background-color: $composer-e2e-icon-color; // XXX: Variable abuse
margin-top: 4px;
mask-image: url('$(res)/img/element-icons/call/video-call.svg');
}
.mx_MJitsiWidgetEvent_title {
font-weight: 600;
font-size: $font-15px;
grid-column: 2;
grid-row: 1;
}
.mx_MJitsiWidgetEvent_subtitle {
grid-column: 2;
grid-row: 2;
}
.mx_MJitsiWidgetEvent_title,
.mx_MJitsiWidgetEvent_subtitle {
overflow-wrap: break-word;
}
}

View file

@ -15,28 +15,6 @@ limitations under the License.
*/
.mx_cryptoEvent {
display: grid;
grid-template-columns: 24px minmax(0, 1fr) min-content;
&.mx_cryptoEvent_icon::before,
&.mx_cryptoEvent_icon::after {
grid-column: 1;
grid-row: 1 / 3;
width: 16px;
height: 16px;
content: "";
top: 0;
bottom: 0;
left: 0;
right: 0;
mask-repeat: no-repeat;
mask-position: center;
mask-size: contain;
mask-image: url('$(res)/img/e2e/normal.svg');
background-color: $composer-e2e-icon-color;
margin-top: 4px;
}
// white infill for the transparency
&.mx_cryptoEvent_icon::before {
background-color: #ffffff;
@ -46,6 +24,11 @@ limitations under the License.
mask-size: 90%;
}
&.mx_cryptoEvent_icon::after {
mask-image: url('$(res)/img/e2e/normal.svg');
background-color: $composer-e2e-icon-color;
}
&.mx_cryptoEvent_icon_verified::after {
mask-image: url("$(res)/img/e2e/verified.svg");
background-color: $accent-color;
@ -56,25 +39,6 @@ limitations under the License.
background-color: $notice-primary-color;
}
.mx_cryptoEvent_title, .mx_cryptoEvent_subtitle, .mx_cryptoEvent_state {
overflow-wrap: break-word;
}
.mx_cryptoEvent_title {
font-weight: 600;
font-size: $font-15px;
grid-column: 2;
grid-row: 1;
}
.mx_cryptoEvent_subtitle {
grid-column: 2;
grid-row: 2;
}
.mx_cryptoEvent_state, .mx_cryptoEvent_subtitle {
font-size: $font-12px;
}
.mx_cryptoEvent_state, .mx_cryptoEvent_buttons {
grid-column: 3;
@ -92,5 +56,7 @@ limitations under the License.
margin: auto 0;
text-align: center;
color: $notice-secondary-color;
overflow-wrap: break-word;
font-size: $font-12px;
}
}

View file

@ -173,26 +173,12 @@ limitations under the License.
margin: 6px 0;
.mx_IconButton, .mx_Spinner {
margin-left: 20px;
width: 16px;
height: 16px;
&::before {
mask-size: 80%;
}
}
.mx_UserInfo_roleDescription {
display: flex;
justify-content: center;
align-items: center;
// try to make it the same height as the dropdown
margin: 11px 0 12px 0;
.mx_IconButton {
margin-left: 6px;
}
}
.mx_Field {

View file

@ -25,15 +25,6 @@ $left-gutter: 64px;
position: relative;
}
.mx_EventTile_bubble {
background-color: $dark-panel-bg-color;
padding: 10px;
border-radius: 5px;
margin: 10px auto;
max-width: 75%;
box-sizing: border-box;
}
.mx_EventTile.mx_EventTile_info {
padding-top: 0px;
}
@ -131,9 +122,10 @@ $left-gutter: 64px;
grid-template-columns: 1fr 100px;
.mx_EventTile_line {
margin-right: 0px;
margin-right: 0;
grid-column: 1 / 3;
padding: 0;
// override default padding of mx_EventTile_line so that we can be centered
padding: 0 !important;
}
.mx_EventTile_msgOption {

View file

@ -0,0 +1,67 @@
/*
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_NewRoomIntro {
margin: 80px 0 48px 64px;
.mx_MiniAvatarUploader_hasAvatar:not(.mx_MiniAvatarUploader_busy):not(:hover) {
&::before, &::after {
content: unset;
}
}
.mx_AccessibleButton_kind_link {
padding: 0;
font-size: inherit;
}
.mx_NewRoomIntro_buttons {
margin-top: 28px;
.mx_AccessibleButton {
line-height: $font-24px;
&::before {
content: '';
display: inline-block;
background-color: $button-fg-color;
mask-position: center;
mask-repeat: no-repeat;
mask-size: 20px;
width: 20px;
height: 20px;
margin-right: 5px;
vertical-align: text-bottom;
}
}
.mx_NewRoomIntro_inviteButton::before {
mask-image: url('$(res)/img/element-icons/room/invite.svg');
}
}
> h2 {
margin-top: 24px;
font-size: $font-24px;
font-weight: 600;
}
> p {
margin: 0;
font-size: $font-15px;
color: $secondary-fg-color;
}
}

View file

@ -383,3 +383,22 @@ limitations under the License.
.mx_RoomSublist_addRoomTooltip {
margin-top: -3px;
}
.mx_RoomSublist_skeletonUI {
position: relative;
margin-left: 4px;
height: 288px;
&::before {
background: $roomsublist-skeleton-ui-bg;
width: 100%;
height: 100%;
content: '';
position: absolute;
mask-repeat: repeat-y;
mask-size: auto 48px;
mask-image: url('$(res)/img/element-icons/roomlist/skeleton-ui.svg');
}
}

View file

@ -33,11 +33,11 @@ limitations under the License.
pointer-events: initial; // restore pointer events so the user can leave/interact
cursor: pointer;
.mx_VideoView {
.mx_CallView_video {
width: 350px;
}
.mx_VideoView_localVideoFeed {
.mx_VideoFeed_local {
border-radius: 8px;
overflow: hidden;
}

View file

@ -92,3 +92,10 @@ limitations under the License.
background-color: $primary-fg-color;
}
}
.mx_CallView_video {
width: 100%;
position: relative;
z-index: 30;
}

View file

@ -1,5 +1,5 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2015, 2016, 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,23 +14,17 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_VideoView {
width: 100%;
position: relative;
z-index: 30;
}
.mx_VideoView video {
.mx_VideoFeed video {
width: 100%;
}
.mx_VideoView_remoteVideoFeed {
.mx_VideoFeed_remote {
width: 100%;
background-color: #000;
z-index: 50;
}
.mx_VideoView_localVideoFeed {
.mx_VideoFeed_local {
width: 25%;
height: 25%;
position: absolute;
@ -39,11 +33,11 @@ limitations under the License.
z-index: 100;
}
.mx_VideoView_localVideoFeed video {
.mx_VideoFeed_local video {
width: auto;
height: 100%;
}
.mx_VideoView_localVideoFeed.mx_VideoView_localVideoFeed_flipped video {
.mx_VideoFeed_mirror video {
transform: scale(-1, 1);
}

View file

@ -0,0 +1,10 @@
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.4896 2.5C9.04778 2.5 7.827 3.52171 7.54879 4.90624C7.50711 5.11367 7.42679 5.31408 7.28726 5.47312L6.6851 6.15949C6.49523 6.37591 6.22129 6.5 5.93338 6.5H2.75C1.64543 6.5 0.75 7.39543 0.75 8.5V19.5C0.75 20.6046 1.64543 21.5 2.75 21.5H22.75C23.8546 21.5 24.75 20.6046 24.75 19.5V8.5C24.75 7.39543 23.8546 6.5 22.75 6.5H19.5666C19.2787 6.5 19.0048 6.37591 18.8149 6.15949L18.2127 5.47312C18.0732 5.31408 17.9929 5.11366 17.9512 4.90623C17.673 3.5217 16.4522 2.5 15.0104 2.5H10.4896ZM16.75 13.5C16.75 15.7091 14.9591 17.5 12.75 17.5C10.5409 17.5 8.75 15.7091 8.75 13.5C8.75 11.2909 10.5409 9.5 12.75 9.5C14.9591 9.5 16.75 11.2909 16.75 13.5ZM3.25 5C2.97386 5 2.75 5.22386 2.75 5.5C2.75 5.77614 2.97386 6 3.25 6H5.25C5.52614 6 5.75 5.77614 5.75 5.5C5.75 5.22386 5.52614 5 5.25 5H3.25Z" fill="black"/>
</g>
<defs>
<clipPath id="clip0">
<rect width="24" height="24" fill="white" transform="translate(0.75)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,11 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.90964 11.5297C9.09231 11.5297 11.6724 8.94865 11.6724 5.76483C11.6724 2.581 9.09231 0 5.90964 0C2.72697 0 0.146904 2.581 0.146904 5.76483C0.146904 6.65678 0.3494 7.50142 0.710912 8.25525L0.0648767 10.3556C-0.171716 11.1248 0.550948 11.8442 1.31906 11.6041L3.39724 10.9544C4.15657 11.323 5.00898 11.5297 5.90964 11.5297Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.03851 12.8449C5.70399 13.1151 6.4314 13.2638 7.19345 13.2638C10.3676 13.2638 13.5 10.6832 13.5 7.49979C13.5 6.63255 13.2676 5.81005 12.8651 5.07227C14.6487 6.05071 15.8583 7.94999 15.8583 10.1326C15.8583 11.0243 15.6564 11.8688 15.2959 12.6224L15.9404 14.7232C16.1765 15.4926 15.4533 16.2114 14.6854 15.9708L12.6155 15.322C11.8585 15.6902 11.0088 15.8966 10.111 15.8966C7.91459 15.8966 6.00594 14.661 5.03851 12.8449Z" fill="black"/>
</g>
<defs>
<clipPath id="clip0">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,5 @@
<svg width="228" height="48" viewBox="0 0 228 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="16" cy="16" r="16" fill="#D4D4D4"/>
<rect x="39" width="189" height="12" rx="6" fill="#D4D4D4"/>
<rect x="39" y="20" width="143" height="12" rx="6" fill="#D4D4D4"/>
</svg>

After

Width:  |  Height:  |  Size: 282 B

View file

@ -1,6 +0,0 @@
<svg width="72" height="34" viewBox="0 0 72 34" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 7.26087V1H28.7889V7.26087M1 7.26087V33H28.7889V7.26087M1 7.26087H28.7889M4.16583 4.13043H16.8291" stroke="#454545" stroke-width="2" stroke-linejoin="round"/>
<path d="M43.2109 7.26087V1H70.9999V7.26087M43.2109 7.26087V33H70.9999V7.26087M43.2109 7.26087H70.9999M46.3768 4.13043H59.0401" stroke="#454545" stroke-width="2" stroke-linejoin="round"/>
<path d="M27.03 28.8262C34.2226 28.8262 36.0207 26.343 36.0207 25.1014V16.0996C36.0207 12.1264 43.6283 11.3401 47.432 11.4436" stroke="black" stroke-width="2"/>
</svg>

Before

Width:  |  Height:  |  Size: 623 B

View file

@ -117,6 +117,7 @@ $roomlist-filter-active-bg-color: $bg-color;
$roomlist-bg-color: rgba(33, 38, 44, 0.90);
$roomlist-header-color: $tertiary-fg-color;
$roomsublist-divider-color: $primary-fg-color;
$roomsublist-skeleton-ui-bg: linear-gradient(180deg, #3e444c 0%, #3e444c00 100%);
$groupFilterPanel-divider-color: $roomlist-header-color;

View file

@ -114,6 +114,7 @@ $roomlist-filter-active-bg-color: $roomlist-button-bg-color;
$roomlist-bg-color: $header-panel-bg-color;
$roomsublist-divider-color: $primary-fg-color;
$roomsublist-skeleton-ui-bg: linear-gradient(180deg, #3e444c 0%, #3e444c00 100%);
$groupFilterPanel-divider-color: $roomlist-header-color;

View file

@ -181,6 +181,7 @@ $roomlist-filter-active-bg-color: $roomlist-button-bg-color;
$roomlist-bg-color: $header-panel-bg-color;
$roomlist-header-color: $primary-fg-color;
$roomsublist-divider-color: $primary-fg-color;
$roomsublist-skeleton-ui-bg: linear-gradient(180deg, #ffffff 0%, #ffffff00 100%);
$groupFilterPanel-divider-color: $roomlist-header-color;

View file

@ -175,6 +175,7 @@ $roomlist-filter-active-bg-color: #ffffff;
$roomlist-bg-color: rgba(245, 245, 245, 0.90);
$roomlist-header-color: $tertiary-fg-color;
$roomsublist-divider-color: $primary-fg-color;
$roomsublist-skeleton-ui-bg: linear-gradient(180deg, #ffffff 0%, #ffffff00 100%);
$groupFilterPanel-divider-color: $roomlist-header-color;

View file

@ -69,6 +69,13 @@ declare global {
interface Document {
// https://developer.mozilla.org/en-US/docs/Web/API/Document/hasStorageAccess
hasStorageAccess?: () => Promise<boolean>;
// Safari & IE11 only have this prefixed: we used prefixed versions
// previously so let's continue to support them for now
webkitExitFullscreen(): Promise<void>;
msExitFullscreen(): Promise<void>;
readonly webkitFullscreenElement: Element | null;
readonly msFullscreenElement: Element | null;
}
interface Navigator {
@ -99,6 +106,13 @@ declare global {
type?: string;
}
interface Element {
// Safari & IE11 only have this prefixed: we used prefixed versions
// previously so let's continue to support them for now
webkitRequestFullScreen(options?: FullscreenOptions): Promise<void>;
msRequestFullscreen(options?: FullscreenOptions): Promise<void>;
}
interface Error {
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/fileName
fileName?: string;

View file

@ -24,6 +24,7 @@ import {ActionPayload} from "./dispatcher/payloads";
import {CheckUpdatesPayload} from "./dispatcher/payloads/CheckUpdatesPayload";
import {Action} from "./dispatcher/actions";
import {hideToast as hideUpdateToast} from "./toasts/UpdateToast";
import {MatrixClientPeg} from "./MatrixClientPeg";
export const SSO_HOMESERVER_URL_KEY = "mx_sso_hs_url";
export const SSO_ID_SERVER_URL_KEY = "mx_sso_is_url";
@ -105,6 +106,9 @@ export default abstract class BasePlatform {
* @param newVersion the version string to check
*/
protected shouldShowUpdate(newVersion: string): boolean {
// If the user registered on this client in the last 24 hours then do not show them the update toast
if (MatrixClientPeg.userRegisteredWithinLastHours(24)) return false;
try {
const [version, deferUntil] = JSON.parse(localStorage.getItem(UPDATE_DEFER_KEY));
return newVersion !== version || Date.now() > deferUntil;

View file

@ -59,8 +59,7 @@ import {MatrixClientPeg} from './MatrixClientPeg';
import PlatformPeg from './PlatformPeg';
import Modal from './Modal';
import { _t } from './languageHandler';
// @ts-ignore - XXX: tsc doesn't like this: our js-sdk imports are complex so this isn't surprising
import Matrix from 'matrix-js-sdk';
import { createNewMatrixCall } from 'matrix-js-sdk/src/webrtc/call';
import dis from './dispatcher/dispatcher';
import WidgetUtils from './utils/WidgetUtils';
import WidgetEchoStore from './stores/WidgetEchoStore';
@ -77,7 +76,7 @@ import ErrorDialog from "./components/views/dialogs/ErrorDialog";
import WidgetStore from "./stores/WidgetStore";
import { WidgetMessagingStore } from "./stores/widgets/WidgetMessagingStore";
import { ElementWidgetActions } from "./stores/widgets/ElementWidgetActions";
import { MatrixCall, CallErrorCode, CallState, CallEvent, CallParty, CallType } from "matrix-js-sdk/lib/webrtc/call";
import { MatrixCall, CallErrorCode, CallState, CallEvent, CallParty, CallType } from "matrix-js-sdk/src/webrtc/call";
import Analytics from './Analytics';
import CountlyAnalytics from "./CountlyAnalytics";
@ -98,6 +97,21 @@ export enum PlaceCallType {
ScreenSharing = 'screensharing',
}
function getRemoteAudioElement(): HTMLAudioElement {
// this needs to be somewhere at the top of the DOM which
// always exists to avoid audio interruptions.
// Might as well just use DOM.
const remoteAudioElement = document.getElementById("remoteAudio") as HTMLAudioElement;
if (!remoteAudioElement) {
console.error(
"Failed to find remoteAudio element - cannot play audio!" +
"You need to add an <audio/> to the DOM.",
);
return null;
}
return remoteAudioElement;
}
export default class CallHandler {
private calls = new Map<string, MatrixCall>();
private audioPromises = new Map<AudioID, Promise<void>>();
@ -291,6 +305,11 @@ export default class CallHandler {
});
}
private setCallAudioElement(call: MatrixCall) {
const audioElement = getRemoteAudioElement();
if (audioElement) call.setRemoteAudioElement(audioElement);
}
private setCallState(call: MatrixCall, status: CallState) {
console.log(
`Call state in ${call.roomId} changed to ${status}`,
@ -343,9 +362,11 @@ export default class CallHandler {
) {
Analytics.trackEvent('voip', 'placeCall', 'type', type);
CountlyAnalytics.instance.trackStartCall(roomId, type === PlaceCallType.Video, false);
const call = Matrix.createNewMatrixCall(MatrixClientPeg.get(), roomId);
const call = createNewMatrixCall(MatrixClientPeg.get(), roomId);
this.calls.set(roomId, call);
this.setCallListeners(call);
this.setCallAudioElement(call);
if (type === PlaceCallType.Voice) {
call.placeVoiceCall();
} else if (type === 'video') {
@ -451,6 +472,7 @@ export default class CallHandler {
Analytics.trackEvent('voip', 'receiveCall', 'type', call.type);
this.calls.set(call.roomId, call)
this.setCallListeners(call);
this.setCallAudioElement(call);
}
break;
case 'hangup':

View file

@ -34,6 +34,7 @@ import * as StorageManager from './utils/StorageManager';
import IdentityAuthClient from './IdentityAuthClient';
import { crossSigningCallbacks, tryToUnlockSecretStorageWithDehydrationKey } from './SecurityManager';
import {SHOW_QR_CODE_METHOD} from "matrix-js-sdk/src/crypto/verification/QRCode";
import SecurityCustomisations from "./customisations/Security";
export interface IMatrixClientCreds {
homeserverUrl: string;
@ -100,6 +101,12 @@ export interface IMatrixClientPeg {
*/
currentUserIsJustRegistered(): boolean;
/**
* If the current user has been registered by this device then this
* returns a boolean of whether it was within the last N hours given.
*/
userRegisteredWithinLastHours(hours: number): boolean;
/**
* Replace this MatrixClientPeg's client with a client instance that has
* homeserver / identity server URLs and active credentials
@ -150,6 +157,9 @@ class _MatrixClientPeg implements IMatrixClientPeg {
public setJustRegisteredUserId(uid: string): void {
this.justRegisteredUserId = uid;
if (uid) {
window.localStorage.setItem("mx_registration_time", String(new Date().getTime()));
}
}
public currentUserIsJustRegistered(): boolean {
@ -159,6 +169,15 @@ class _MatrixClientPeg implements IMatrixClientPeg {
);
}
public userRegisteredWithinLastHours(hours: number): boolean {
try {
const date = new Date(window.localStorage.getItem("mx_registration_time"));
return ((new Date().getTime() - date.getTime()) / 36e5) <= hours;
} catch (e) {
return false;
}
}
public replaceUsingCreds(creds: IMatrixClientCreds): void {
this.currentClientCreds = creds;
this.createClient(creds);
@ -273,7 +292,10 @@ class _MatrixClientPeg implements IMatrixClientPeg {
// These are always installed regardless of the labs flag so that
// cross-signing features can toggle on without reloading and also be
// accessed immediately after login.
Object.assign(opts.cryptoCallbacks, crossSigningCallbacks);
const customisedCallbacks = {
getDehydrationKey: SecurityCustomisations.getDehydrationKey,
};
Object.assign(opts.cryptoCallbacks, crossSigningCallbacks, customisedCallbacks);
this.matrixClient = createMatrixClient(opts);

View file

@ -50,8 +50,8 @@ class Skinner {
return null;
}
// components have to be functions.
const validType = typeof comp === 'function';
// components have to be functions or forwardRef objects with a render function.
const validType = typeof comp === 'function' || comp.render;
if (!validType) {
throw new Error(`Not a valid component: ${name} (type = ${typeof(comp)}).`);
}

View file

@ -46,6 +46,7 @@ import { EffectiveMembership, getEffectiveMembership, leaveRoomBehaviour } from
import SdkConfig from "./SdkConfig";
import SettingsStore from "./settings/SettingsStore";
import {UIFeature} from "./settings/UIFeature";
import CallHandler from "./CallHandler";
// XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816
interface HTMLInputEvent extends Event {
@ -1001,14 +1002,29 @@ export const Commands = [
description: _td("Opens chat with the given user"),
args: "<user-id>",
runFn: function(roomId, userId) {
if (!userId || !userId.startsWith("@") || !userId.includes(":")) {
// easter-egg for now: look up phone numbers through the thirdparty API
// (very dumb phone number detection...)
const isPhoneNumber = userId && /^\+?[0123456789]+$/.test(userId);
if (!userId || (!userId.startsWith("@") || !userId.includes(":")) && !isPhoneNumber) {
return reject(this.getUsage());
}
return success((async () => {
if (isPhoneNumber) {
const results = await MatrixClientPeg.get().getThirdpartyUser('im.vector.protocol.pstn', {
'm.id.phone': userId,
});
if (!results || results.length === 0 || !results[0].userid) {
throw new Error("Unable to find Matrix ID for phone number");
}
userId = results[0].userid;
}
const roomId = await ensureDMExists(MatrixClientPeg.get(), userId);
dis.dispatch({
action: 'view_room',
room_id: await ensureDMExists(MatrixClientPeg.get(), userId),
room_id: roomId,
});
})());
},
@ -1042,6 +1058,32 @@ export const Commands = [
},
category: CommandCategories.actions,
}),
new Command({
command: "holdcall",
description: _td("Places the call in the current room on hold"),
category: CommandCategories.other,
runFn: function(roomId, args) {
const call = CallHandler.sharedInstance().getCallForRoom(roomId);
if (!call) {
return reject("No active call in this room");
}
call.setRemoteOnHold(true);
return success();
},
}),
new Command({
command: "unholdcall",
description: _td("Takes the call in the current room off hold"),
category: CommandCategories.other,
runFn: function(roomId, args) {
const call = CallHandler.sharedInstance().getCallForRoom(roomId);
if (!call) {
return reject("No active call in this room");
}
call.setRemoteOnHold(false);
return success();
},
}),
// Command definitions for autocompletion ONLY:
// /me is special because its not handled by SlashCommands.js and is instead done inside the Composer classes

View file

@ -15,20 +15,67 @@ limitations under the License.
*/
import * as React from "react";
import {useContext, useState} from "react";
import AutoHideScrollbar from './AutoHideScrollbar';
import { getHomePageUrl } from "../../utils/pages";
import { _t } from "../../languageHandler";
import {getHomePageUrl} from "../../utils/pages";
import {_t} from "../../languageHandler";
import SdkConfig from "../../SdkConfig";
import * as sdk from "../../index";
import dis from "../../dispatcher/dispatcher";
import { Action } from "../../dispatcher/actions";
import {Action} from "../../dispatcher/actions";
import BaseAvatar from "../views/avatars/BaseAvatar";
import {OwnProfileStore} from "../../stores/OwnProfileStore";
import AccessibleButton from "../views/elements/AccessibleButton";
import {UPDATE_EVENT} from "../../stores/AsyncStore";
import {useEventEmitter} from "../../hooks/useEventEmitter";
import MatrixClientContext from "../../contexts/MatrixClientContext";
import MiniAvatarUploader, {AVATAR_SIZE} from "../views/elements/MiniAvatarUploader";
const onClickSendDm = () => dis.dispatch({action: 'view_create_chat'});
const onClickExplore = () => dis.fire(Action.ViewRoomDirectory);
const onClickNewRoom = () => dis.dispatch({action: 'view_create_room'});
const HomePage = () => {
interface IProps {
justRegistered?: boolean;
}
const getOwnProfile = (userId: string) => ({
displayName: OwnProfileStore.instance.displayName || userId,
avatarUrl: OwnProfileStore.instance.getHttpAvatarUrl(AVATAR_SIZE),
});
const UserWelcomeTop = () => {
const cli = useContext(MatrixClientContext);
const userId = cli.getUserId();
const [ownProfile, setOwnProfile] = useState(getOwnProfile(userId));
useEventEmitter(OwnProfileStore.instance, UPDATE_EVENT, () => {
setOwnProfile(getOwnProfile(userId));
});
return <div>
<MiniAvatarUploader
hasAvatar={!!ownProfile.avatarUrl}
hasAvatarLabel={_t("Great, that'll help people know it's you")}
noAvatarLabel={_t("Add a photo so people know it's you.")}
setAvatarUrl={url => cli.setAvatarUrl(url)}
>
<BaseAvatar
idName={userId}
name={ownProfile.displayName}
url={ownProfile.avatarUrl}
width={AVATAR_SIZE}
height={AVATAR_SIZE}
resizeMethod="crop"
/>
</MiniAvatarUploader>
<h1>{ _t("Welcome %(name)s", { name: ownProfile.displayName }) }</h1>
<h4>{ _t("Now, let's help you get started") }</h4>
</div>;
};
const HomePage: React.FC<IProps> = ({ justRegistered = false }) => {
const config = SdkConfig.get();
const pageUrl = getHomePageUrl(config);
@ -37,18 +84,27 @@ const HomePage = () => {
return <EmbeddedPage className="mx_HomePage" url={pageUrl} scrollbar={true} />;
}
const brandingConfig = config.branding;
let logoUrl = "themes/element/img/logos/element-logo.svg";
if (brandingConfig && brandingConfig.authHeaderLogoUrl) {
logoUrl = brandingConfig.authHeaderLogoUrl;
let introSection;
if (justRegistered) {
introSection = <UserWelcomeTop />;
} else {
const brandingConfig = config.branding;
let logoUrl = "themes/element/img/logos/element-logo.svg";
if (brandingConfig && brandingConfig.authHeaderLogoUrl) {
logoUrl = brandingConfig.authHeaderLogoUrl;
}
introSection = <React.Fragment>
<img src={logoUrl} alt={config.brand} />
<h1>{ _t("Welcome to %(appName)s", { appName: config.brand }) }</h1>
<h4>{ _t("Liberate your communication") }</h4>
</React.Fragment>;
}
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
return <AutoHideScrollbar className="mx_HomePage mx_HomePage_default">
<div className="mx_HomePage_default_wrapper">
<img src={logoUrl} alt={config.brand || "Element"} />
<h1>{ _t("Welcome to %(appName)s", { appName: config.brand || "Element" }) }</h1>
<h4>{ _t("Liberate your communication") }</h4>
{ introSection }
<div className="mx_HomePage_default_buttons">
<AccessibleButton onClick={onClickSendDm} className="mx_HomePage_button_sendDm">
{ _t("Send a Direct Message") }

View file

@ -88,6 +88,7 @@ interface IProps {
currentUserId?: string;
currentGroupId?: string;
currentGroupIsNew?: boolean;
justRegistered?: boolean;
}
interface IUsageLimit {
@ -573,7 +574,7 @@ class LoggedInView extends React.Component<IProps, IState> {
break;
case PageTypes.HomePage:
pageElement = <HomePage />;
pageElement = <HomePage justRegistered={this.props.justRegistered} />;
break;
case PageTypes.UserView:

View file

@ -62,7 +62,7 @@ import DMRoomMap from '../../utils/DMRoomMap';
import ThemeWatcher from "../../settings/watchers/ThemeWatcher";
import { FontWatcher } from '../../settings/watchers/FontWatcher';
import { storeRoomAliasInCache } from '../../RoomAliasCache';
import { defer, IDeferred } from "../../utils/promise";
import { defer, IDeferred, sleep } from "../../utils/promise";
import ToastStore from "../../stores/ToastStore";
import * as StorageManager from "../../utils/StorageManager";
import type LoggedInViewType from "./LoggedInView";
@ -201,6 +201,7 @@ interface IState {
roomOobData?: object;
viaServers?: string[];
pendingInitialSync?: boolean;
justRegistered?: boolean;
}
export default class MatrixChat extends React.PureComponent<IProps, IState> {
@ -479,6 +480,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
}
const newState = {
currentUserId: null,
justRegistered: false,
};
Object.assign(newState, state);
this.setState(newState);
@ -669,7 +671,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
this.viewWelcome();
break;
case 'view_home_page':
this.viewHome();
this.viewHome(payload.justRegistered);
break;
case 'view_start_chat_or_reuse':
this.chatCreateOrReuse(payload.user_id);
@ -953,10 +955,11 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
this.themeWatcher.recheck();
}
private viewHome() {
private viewHome(justRegistered = false) {
// The home page requires the "logged in" view, so we'll set that.
this.setStateForNewView({
view: Views.LOGGED_IN,
justRegistered,
});
this.setPage(PageTypes.HomePage);
this.notifyNewScreen('home');
@ -1190,7 +1193,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
if (welcomeUserRoom === null) {
// We didn't redirect to the welcome user room, so show
// the homepage.
dis.dispatch({action: 'view_home_page'});
dis.dispatch({action: 'view_home_page', justRegistered: true});
}
} else if (ThreepidInviteStore.instance.pickBestInvite()) {
// The user has a 3pid invite pending - show them that
@ -1203,7 +1206,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
} else {
// The user has just logged in after registering,
// so show the homepage.
dis.dispatch({action: 'view_home_page'});
dis.dispatch({action: 'view_home_page', justRegistered: true});
}
} else {
this.showScreenAfterLogin();
@ -1211,6 +1214,8 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
StorageManager.tryPersistStorage();
// defer the following actions by 30 seconds to not throw them at the user immediately
await sleep(30);
if (SettingsStore.getValue("showCookieBar") &&
(Analytics.canEnable() || CountlyAnalytics.instance.canEnable())
) {
@ -1343,8 +1348,8 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
this.firstSyncComplete = true;
this.firstSyncPromise.resolve();
if (Notifier.shouldShowPrompt()) {
showNotificationsToast();
if (Notifier.shouldShowPrompt() && !MatrixClientPeg.userRegisteredWithinLastHours(24)) {
showNotificationsToast(false);
}
dis.fire(Action.FocusComposer);
@ -1407,6 +1412,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
const dft = new DecryptionFailureTracker((total, errorCode) => {
Analytics.trackEvent('E2E', 'Decryption failure', errorCode, total);
CountlyAnalytics.instance.track("decryption_failure", { errorCode }, null, { sum: total });
}, (errorCode) => {
// Map JS-SDK error codes to tracker codes for aggregation
switch (errorCode) {

View file

@ -30,6 +30,8 @@ import {_t} from "../../languageHandler";
import {haveTileForEvent} from "../views/rooms/EventTile";
import {textForEvent} from "../../TextForEvent";
import IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResizer";
import DMRoomMap from "../../utils/DMRoomMap";
import NewRoomIntro from "../views/rooms/NewRoomIntro";
const CONTINUATION_MAX_INTERVAL = 5 * 60 * 1000; // 5 minutes
const continuedTypes = ['m.sticker', 'm.room.message'];
@ -952,15 +954,25 @@ class CreationGrouper {
}).reduce((a, b) => a.concat(b), []);
// Get sender profile from the latest event in the summary as the m.room.create doesn't contain one
const ev = this.events[this.events.length - 1];
let summaryText;
const roomId = ev.getRoomId();
const creator = ev.sender ? ev.sender.name : ev.getSender();
if (DMRoomMap.shared().getUserIdForRoomId(roomId)) {
summaryText = _t("%(creator)s created this DM.", { creator });
} else {
summaryText = _t("%(creator)s created and configured the room.", { creator });
}
ret.push(<NewRoomIntro key="newroomintro" />);
ret.push(
<EventListSummary
key="roomcreationsummary"
events={this.events}
onToggle={panel._onHeightChanged} // Update scroll state
summaryMembers={[ev.sender]}
summaryText={_t("%(creator)s created and configured the room.", {
creator: ev.sender ? ev.sender.name : ev.getSender(),
})}
summaryText={summaryText}
>
{ eventTiles }
</EventListSummary>,

View file

@ -41,9 +41,6 @@ export default class RoomStatusBar extends React.Component {
static propTypes = {
// the room this statusbar is representing.
room: PropTypes.object.isRequired,
// This is true when the user is alone in the room, but has also sent a message.
// Used to suggest to the user to invite someone
sentMessageAndIsAlone: PropTypes.bool,
// The active call in the room, if any (means we show the call bar
// along with the status of the call)
@ -68,10 +65,6 @@ export default class RoomStatusBar extends React.Component {
// 'you are alone' bar
onInviteClick: PropTypes.func,
// callback for when the user clicks on the 'stop warning me' button in the
// 'you are alone' bar
onStopWarningClick: PropTypes.func,
// callback for when we do something that changes the size of the
// status bar. This is used to trigger a re-layout in the parent
// component.
@ -159,10 +152,7 @@ export default class RoomStatusBar extends React.Component {
// changed - so we use '0' to indicate normal size, and other values to
// indicate other sizes.
_getSize() {
if (this._shouldShowConnectionError() ||
this._showCallBar() ||
this.props.sentMessageAndIsAlone
) {
if (this._shouldShowConnectionError() || this._showCallBar()) {
return STATUS_BAR_EXPANDED;
} else if (this.state.unsentMessages.length > 0) {
return STATUS_BAR_EXPANDED_LARGE;
@ -325,24 +315,6 @@ export default class RoomStatusBar extends React.Component {
);
}
// If you're alone in the room, and have sent a message, suggest to invite someone
if (this.props.sentMessageAndIsAlone && !this.props.isPeeking) {
return (
<div className="mx_RoomStatusBar_isAlone">
{ _t("There's no one else here! Would you like to <inviteText>invite others</inviteText> " +
"or <nowarnText>stop warning about the empty room</nowarnText>?",
{},
{
'inviteText': (sub) =>
<a className="mx_RoomStatusBar_resend_link" key="invite" onClick={this.props.onInviteClick}>{ sub }</a>,
'nowarnText': (sub) =>
<a className="mx_RoomStatusBar_resend_link" key="nowarn" onClick={this.props.onStopWarningClick}>{ sub }</a>,
},
) }
</div>
);
}
return null;
}

View file

@ -71,9 +71,11 @@ import RoomHeader from "../views/rooms/RoomHeader";
import TintableSvg from "../views/elements/TintableSvg";
import {XOR} from "../../@types/common";
import { IThreepidInvite } from "../../stores/ThreepidInviteStore";
import { CallState, CallType, MatrixCall } from "matrix-js-sdk/lib/webrtc/call";
import { CallState, CallType, MatrixCall } from "matrix-js-sdk/src/webrtc/call";
import WidgetStore from "../../stores/WidgetStore";
import {UPDATE_EVENT} from "../../stores/AsyncStore";
import Notifier from "../../Notifier";
import {showToast as showNotificationsToast} from "../../toasts/DesktopNotificationsToast";
const DEBUG = false;
let debuglog = function(msg: string) {};
@ -148,7 +150,6 @@ export interface IState {
guestsCanJoin: boolean;
canPeek: boolean;
showApps: boolean;
isAlone: boolean;
isPeeking: boolean;
showingPinned: boolean;
showReadReceipts: boolean;
@ -221,7 +222,6 @@ export default class RoomView extends React.Component<IProps, IState> {
guestsCanJoin: false,
canPeek: false,
showApps: false,
isAlone: false,
isPeeking: false,
showingPinned: false,
showReadReceipts: true,
@ -703,9 +703,8 @@ export default class RoomView extends React.Component<IProps, IState> {
private onAction = payload => {
switch (payload.action) {
case 'message_send_failed':
case 'message_sent':
this.checkIfAlone(this.state.room);
this.checkDesktopNotifications();
break;
case 'post_sticker_message':
this.injectSticker(
@ -1023,33 +1022,17 @@ export default class RoomView extends React.Component<IProps, IState> {
}
// rate limited because a power level change will emit an event for every member in the room.
private updateRoomMembers = rateLimitedFunc((dueToMember) => {
private updateRoomMembers = rateLimitedFunc(() => {
this.updateDMState();
let memberCountInfluence = 0;
if (dueToMember && dueToMember.membership === "invite" && this.state.room.getInvitedMemberCount() === 0) {
// A member got invited, but the room hasn't detected that change yet. Influence the member
// count by 1 to counteract this.
memberCountInfluence = 1;
}
this.checkIfAlone(this.state.room, memberCountInfluence);
this.updateE2EStatus(this.state.room);
}, 500);
private checkIfAlone(room: Room, countInfluence?: number) {
let warnedAboutLonelyRoom = false;
if (localStorage) {
warnedAboutLonelyRoom = Boolean(localStorage.getItem('mx_user_alone_warned_' + this.state.room.roomId));
private checkDesktopNotifications() {
const memberCount = this.state.room.getJoinedMemberCount() + this.state.room.getInvitedMemberCount();
// if they are not alone prompt the user about notifications so they don't miss replies
if (memberCount > 1 && Notifier.shouldShowPrompt()) {
showNotificationsToast(true);
}
if (warnedAboutLonelyRoom) {
if (this.state.isAlone) this.setState({isAlone: false});
return;
}
let joinedOrInvitedMemberCount = room.getJoinedMemberCount() + room.getInvitedMemberCount();
if (countInfluence) joinedOrInvitedMemberCount += countInfluence;
this.setState({isAlone: joinedOrInvitedMemberCount === 1});
}
private updateDMState() {
@ -1084,14 +1067,6 @@ export default class RoomView extends React.Component<IProps, IState> {
action: 'view_invite',
roomId: this.state.room.roomId,
});
this.setState({isAlone: false}); // there's a good chance they'll invite someone
};
private onStopAloneWarningClick = () => {
if (localStorage) {
localStorage.setItem('mx_user_alone_warned_' + this.state.room.roomId, String(true));
}
this.setState({isAlone: false});
};
private onJoinButtonClicked = () => {
@ -1140,16 +1115,9 @@ export default class RoomView extends React.Component<IProps, IState> {
ev.dataTransfer.dropEffect = 'none';
const items = [...ev.dataTransfer.items];
if (items.length >= 1) {
const isDraggingFiles = items.every(function(item) {
return item.kind == 'file';
});
if (isDraggingFiles) {
this.setState({ draggingFile: true });
ev.dataTransfer.dropEffect = 'copy';
}
if (ev.dataTransfer.types.includes("Files") || ev.dataTransfer.types.includes("application/x-moz-file")) {
this.setState({ draggingFile: true });
ev.dataTransfer.dropEffect = 'copy';
}
};
@ -1790,12 +1758,10 @@ export default class RoomView extends React.Component<IProps, IState> {
isStatusAreaExpanded = this.state.statusBarVisible;
statusBar = <RoomStatusBar
room={this.state.room}
sentMessageAndIsAlone={this.state.isAlone}
callState={this.state.callState}
callType={activeCall ? activeCall.type : null}
isPeeking={myMembership !== "join"}
onInviteClick={this.onInviteButtonClick}
onStopWarningClick={this.onStopAloneWarningClick}
onVisible={this.onStatusBarVisible}
onHidden={this.onStatusBarHidden}
/>;

View file

@ -190,11 +190,18 @@ export default class UserMenu extends React.Component<IProps, IState> {
this.setState({contextMenuPosition: null}); // also close the menu
};
private onSignOutClick = (ev: ButtonEvent) => {
private onSignOutClick = async (ev: ButtonEvent) => {
ev.preventDefault();
ev.stopPropagation();
Modal.createTrackedDialog('Logout from LeftPanel', '', LogoutDialog);
const cli = MatrixClientPeg.get();
if (!cli || !cli.isCryptoEnabled() || !(await cli.exportRoomKeys())?.length) {
// log out without user prompt if they have no local megolm sessions
dis.dispatch({action: 'logout'});
} else {
Modal.createTrackedDialog('Logout from LeftPanel', '', LogoutDialog);
}
this.setState({contextMenuPosition: null}); // also close the menu
};
@ -203,6 +210,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
ev.stopPropagation();
defaultDispatcher.dispatch({action: 'view_home_page'});
this.setState({contextMenuPosition: null}); // also close the menu
};
private onCommunitySettingsClick = (ev: ButtonEvent) => {

View file

@ -314,7 +314,7 @@ export default class ForgotPassword extends React.Component {
<Field
name="reset_password"
type="password"
label={_t('Password')}
label={_t('New Password')}
value={this.state.password}
onChange={this.onInputChanged.bind(this, "password")}
onFocus={() => CountlyAnalytics.instance.track("onboarding_forgot_password_newPassword_focus")}

View file

@ -123,7 +123,7 @@ export default class CountryDropdown extends React.Component {
const options = displayedCountries.map((country) => {
return <div className="mx_CountryDropdown_option" key={country.iso2}>
{ this._flagImgForIso2(country.iso2) }
{ country.name } (+{ country.prefix })
{ _t(country.name) } (+{ country.prefix })
</div>;
});

View file

@ -250,6 +250,7 @@ export default class RegistrationForm extends React.Component {
validateEmailRules = withValidation({
description: () => _t("Use an email address to recover your account"),
hideDescriptionIfValid: true,
rules: [
{
key: "required",
@ -326,6 +327,7 @@ export default class RegistrationForm extends React.Component {
validatePhoneNumberRules = withValidation({
description: () => _t("Other users can invite you to rooms using your contact details"),
hideDescriptionIfValid: true,
rules: [
{
key: "required",
@ -356,6 +358,7 @@ export default class RegistrationForm extends React.Component {
validateUsernameRules = withValidation({
description: () => _t("Use lowercase letters, numbers, dashes and underscores only"),
hideDescriptionIfValid: true,
rules: [
{
key: "required",

View file

@ -51,7 +51,8 @@ const calculateUrls = (url, urls) => {
_urls = urls || [];
if (url) {
_urls.unshift(url); // put in urls[0]
// copy urls and put url first
_urls = [url, ..._urls];
}
}

View file

@ -35,6 +35,7 @@ interface IProps {
height?: number;
resizeMethod?: ResizeMethod;
viewAvatarOnClick?: boolean;
onClick?(): void;
}
interface IState {
@ -130,7 +131,7 @@ export default class RoomAvatar extends React.Component<IProps, IState> {
};
public render() {
const {room, oobData, viewAvatarOnClick, ...otherProps} = this.props;
const {room, oobData, viewAvatarOnClick, onClick, ...otherProps} = this.props;
const roomName = room ? room.name : oobData.name;
@ -139,7 +140,7 @@ export default class RoomAvatar extends React.Component<IProps, IState> {
name={roomName}
idName={room ? room.roomId : null}
urls={this.state.urls}
onClick={viewAvatarOnClick && this.state.urls[0] ? this.onRoomAvatarClick : null}
onClick={viewAvatarOnClick && this.state.urls[0] ? this.onRoomAvatarClick : onClick}
/>
);
}

View file

@ -31,6 +31,7 @@ import SettingsStore from '../../../settings/SettingsStore';
import { isUrlPermitted } from '../../../HtmlUtils';
import { isContentActionable } from '../../../utils/EventUtils';
import {MenuItem} from "../../structures/ContextMenu";
import {EventType} from "matrix-js-sdk/src/@types/event";
function canCancel(eventStatus) {
return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT;
@ -72,7 +73,10 @@ export default class MessageContextMenu extends React.Component {
const cli = MatrixClientPeg.get();
const room = cli.getRoom(this.props.mxEvent.getRoomId());
const canRedact = room.currentState.maySendRedactionForEvent(this.props.mxEvent, cli.credentials.userId);
// We explicitly decline to show the redact option on ACL events as it has a potential
// to obliterate the room - https://github.com/matrix-org/synapse/issues/4042
const canRedact = room.currentState.maySendRedactionForEvent(this.props.mxEvent, cli.credentials.userId)
&& this.props.mxEvent.getType() !== EventType.RoomServerAcl;
let canPin = room.currentState.mayClientSendStateEvent('m.room.pinned_events', cli);
// HACK: Intentionally say we can't pin if the user doesn't want to use the functionality

View file

@ -48,8 +48,8 @@ export default (props) => {
title: _t('Feedback sent'),
description: _t('Thank you!'),
});
props.onFinished();
}
props.onFinished();
};
const brand = SdkConfig.get().brand;

View file

@ -280,11 +280,17 @@ class DMRoomTile extends React.PureComponent {
</span>
);
const caption = this.props.member.isEmail
? _t("Invite by email")
: this._highlightName(this.props.member.userId);
return (
<div className='mx_InviteDialog_roomTile' onClick={this._onClick}>
{stackedAvatar}
<span className='mx_InviteDialog_roomTile_name'>{this._highlightName(this.props.member.name)}</span>
<span className='mx_InviteDialog_roomTile_userId'>{this._highlightName(this.props.member.userId)}</span>
<span className="mx_InviteDialog_roomTile_nameStack">
<div className='mx_InviteDialog_roomTile_name'>{this._highlightName(this.props.member.name)}</div>
<div className='mx_InviteDialog_roomTile_userId'>{caption}</div>
</span>
{timestamp}
</div>
);
@ -663,12 +669,21 @@ export default class InviteDialog extends React.PureComponent {
};
_onKeyDown = (e) => {
// when the field is empty and the user hits backspace remove the right-most target
if (!e.target.value && !this.state.busy && this.state.targets.length > 0 && e.key === Key.BACKSPACE &&
!e.ctrlKey && !e.shiftKey && !e.metaKey
) {
if (this.state.busy) return;
const value = e.target.value.trim();
const hasModifiers = e.ctrlKey || e.shiftKey || e.metaKey;
if (!value && this.state.targets.length > 0 && e.key === Key.BACKSPACE && !hasModifiers) {
// when the field is empty and the user hits backspace remove the right-most target
e.preventDefault();
this._removeMember(this.state.targets[this.state.targets.length - 1]);
} else if (value && e.key === Key.ENTER && !hasModifiers) {
// when the user hits enter with something in their field try to convert it
e.preventDefault();
this._convertFilter();
} else if (value && e.key === Key.SPACE && !hasModifiers && value.includes("@") && !value.includes(" ")) {
// when the user hits space and their input looks like an e-mail/MXID then try to convert it
e.preventDefault();
this._convertFilter();
}
};
@ -811,6 +826,10 @@ export default class InviteDialog extends React.PureComponent {
filterText = ""; // clear the filter when the user accepts a suggestion
}
this.setState({targets, filterText});
if (this._editorRef && this._editorRef.current) {
this._editorRef.current.focus();
}
};
_removeMember = (member: Member) => {
@ -820,6 +839,10 @@ export default class InviteDialog extends React.PureComponent {
targets.splice(idx, 1);
this.setState({targets});
}
if (this._editorRef && this._editorRef.current) {
this._editorRef.current.focus();
}
};
_onPaste = async (e) => {
@ -829,7 +852,7 @@ export default class InviteDialog extends React.PureComponent {
return;
}
// Prevent the text being pasted into the textarea
// Prevent the text being pasted into the input
e.preventDefault();
// Process it as a list of addresses to add instead
@ -1024,8 +1047,8 @@ export default class InviteDialog extends React.PureComponent {
<DMUserTile member={t} onRemove={!this.state.busy && this._removeMember} key={t.userId} />
));
const input = (
<textarea
rows={1}
<input
type="text"
onKeyDown={this._onKeyDown}
onChange={this._updateFilter}
value={this.state.filterText}
@ -1033,6 +1056,7 @@ export default class InviteDialog extends React.PureComponent {
onPaste={this._onPaste}
autoFocus={true}
disabled={this.state.busy}
autoComplete="off"
/>
);
return (
@ -1103,7 +1127,7 @@ export default class InviteDialog extends React.PureComponent {
if (identityServersEnabled) {
helpText = _t(
"Start a conversation with someone using their name, username (like <userId/>) or email address.",
"Start a conversation with someone using their name, email address or username (like <userId/>).",
{},
{userId: () => {
return (
@ -1158,7 +1182,7 @@ export default class InviteDialog extends React.PureComponent {
if (identityServersEnabled) {
helpText = _t(
"Invite someone using their name, username (like <userId/>), email address or " +
"Invite someone using their name, email address, username (like <userId/>) or " +
"<a>share this room</a>.",
{},
{

View file

@ -23,6 +23,11 @@ import {
IModalWidgetCloseRequest,
IModalWidgetOpenRequestData,
IModalWidgetReturnData,
ISetModalButtonEnabledActionRequest,
IWidgetApiAcknowledgeResponseData,
IWidgetApiErrorResponseData,
BuiltInModalButtonID,
ModalButtonID,
ModalButtonKind,
Widget,
WidgetApiFromWidgetAction,
@ -31,6 +36,7 @@ import {StopGapWidgetDriver} from "../../../stores/widgets/StopGapWidgetDriver";
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import RoomViewStore from "../../../stores/RoomViewStore";
import {OwnProfileStore} from "../../../stores/OwnProfileStore";
import { arrayFastClone } from "../../../utils/arrays";
interface IProps {
widgetDefinition: IModalWidgetOpenRequestData;
@ -40,15 +46,19 @@ interface IProps {
interface IState {
messaging?: ClientWidgetApi;
disabledButtonIds: ModalButtonID[];
}
const MAX_BUTTONS = 3;
export default class ModalWidgetDialog extends React.PureComponent<IProps, IState> {
private readonly widget: Widget;
private readonly possibleButtons: ModalButtonID[];
private appFrame: React.RefObject<HTMLIFrameElement> = React.createRef();
state: IState = {};
state: IState = {
disabledButtonIds: [],
};
constructor(props) {
super(props);
@ -58,6 +68,7 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
creatorUserId: MatrixClientPeg.get().getUserId(),
id: `modal_${this.props.sourceWidgetId}`,
});
this.possibleButtons = (this.props.widgetDefinition.buttons || []).map(b => b.id);
}
public componentDidMount() {
@ -79,12 +90,35 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
private onLoad = () => {
this.state.messaging.once("ready", this.onReady);
this.state.messaging.on(`action:${WidgetApiFromWidgetAction.CloseModalWidget}`, this.onWidgetClose);
this.state.messaging.on(`action:${WidgetApiFromWidgetAction.SetModalButtonEnabled}`, this.onButtonEnableToggle);
};
private onWidgetClose = (ev: CustomEvent<IModalWidgetCloseRequest>) => {
this.props.onFinished(true, ev.detail.data);
}
private onButtonEnableToggle = (ev: CustomEvent<ISetModalButtonEnabledActionRequest>) => {
ev.preventDefault();
const isClose = ev.detail.data.button === BuiltInModalButtonID.Close;
if (isClose || !this.possibleButtons.includes(ev.detail.data.button)) {
return this.state.messaging.transport.reply(ev.detail, {
error: {message: "Invalid button"},
} as IWidgetApiErrorResponseData);
}
let buttonIds: ModalButtonID[];
if (ev.detail.data.enabled) {
buttonIds = arrayFastClone(this.state.disabledButtonIds).filter(i => i !== ev.detail.data.button);
} else {
// use a set to swap the operation to avoid memory leaky arrays.
const tempSet = new Set(this.state.disabledButtonIds);
tempSet.add(ev.detail.data.button);
buttonIds = Array.from(tempSet);
}
this.setState({disabledButtonIds: buttonIds});
this.state.messaging.transport.reply(ev.detail, {} as IWidgetApiAcknowledgeResponseData);
};
public render() {
const templated = this.widget.getCompleteUrl({
currentRoomId: RoomViewStore.getRoomId(),

View file

@ -39,7 +39,7 @@ interface IProps extends React.InputHTMLAttributes<Element> {
tabIndex?: number;
disabled?: boolean;
className?: string;
onClick?(e?: ButtonEvent): void;
onClick(e?: ButtonEvent): void;
}
interface IAccessibleButtonProps extends React.InputHTMLAttributes<Element> {

View file

@ -1,34 +0,0 @@
/*
Copyright 2019 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 PropTypes from 'prop-types';
import AccessibleButton from "./AccessibleButton";
export default function IconButton(props) {
const {icon, className, ...restProps} = props;
let newClassName = (className || "") + " mx_IconButton";
newClassName = newClassName + " mx_IconButton_icon_" + icon;
const allProps = Object.assign({}, restProps, {className: newClassName});
return React.createElement(AccessibleButton, allProps);
}
IconButton.propTypes = Object.assign({
icon: PropTypes.string,
}, AccessibleButton.propTypes);

View file

@ -0,0 +1,90 @@
/*
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, {useContext, useRef, useState} from 'react';
import classNames from 'classnames';
import AccessibleButton from "./AccessibleButton";
import Tooltip from './Tooltip';
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {useTimeout} from "../../../hooks/useTimeout";
export const AVATAR_SIZE = 52;
interface IProps {
hasAvatar: boolean;
noAvatarLabel?: string;
hasAvatarLabel?: string;
setAvatarUrl(url: string): Promise<void>;
}
const MiniAvatarUploader: React.FC<IProps> = ({ hasAvatar, hasAvatarLabel, noAvatarLabel, setAvatarUrl, children }) => {
const cli = useContext(MatrixClientContext);
const [busy, setBusy] = useState(false);
const [hover, setHover] = useState(false);
const [show, setShow] = useState(false);
useTimeout(() => {
setShow(true);
}, 3000); // show after 3 seconds
useTimeout(() => {
setShow(false);
}, 13000); // hide after being shown for 10 seconds
const uploadRef = useRef<HTMLInputElement>();
const label = (hasAvatar || busy) ? hasAvatarLabel : noAvatarLabel;
return <React.Fragment>
<input
type="file"
ref={uploadRef}
className="mx_MiniAvatarUploader_input"
onChange={async (ev) => {
if (!ev.target.files?.length) return;
setBusy(true);
const file = ev.target.files[0];
const uri = await cli.uploadContent(file);
await setAvatarUrl(uri);
setBusy(false);
}}
accept="image/*"
/>
<AccessibleButton
className={classNames("mx_MiniAvatarUploader", {
mx_MiniAvatarUploader_busy: busy,
mx_MiniAvatarUploader_hasAvatar: hasAvatar,
})}
disabled={busy}
onClick={() => {
uploadRef.current.click();
}}
onMouseOver={() => setHover(true)}
onMouseLeave={() => setHover(false)}
>
{ children }
<Tooltip
label={label}
visible={!!label && (hover || show)}
forceOnRight
/>
</AccessibleButton>
</React.Fragment>;
};
export default MiniAvatarUploader;

View file

@ -33,6 +33,7 @@ interface IRule<T, D = void> {
interface IArgs<T, D = void> {
rules: IRule<T, D>[];
description(this: T, derivedData: D): React.ReactChild;
hideDescriptionIfValid?: boolean;
deriveData?(data: Data): Promise<D>;
}
@ -54,6 +55,8 @@ export interface IValidationResult {
* @param {Function} description
* Function that returns a string summary of the kind of value that will
* meet the validation rules. Shown at the top of the validation feedback.
* @param {Boolean} hideDescriptionIfValid
* If true, don't show the description if the validation passes validation.
* @param {Function} deriveData
* Optional function that returns a Promise to an object of generic type D.
* The result of this Promise is passed to rule methods `skip`, `test`, `valid`, and `invalid`.
@ -71,7 +74,9 @@ export interface IValidationResult {
* A validation function that takes in the current input value and returns
* the overall validity and a feedback UI that can be rendered for more detail.
*/
export default function withValidation<T = undefined, D = void>({ description, deriveData, rules }: IArgs<T, D>) {
export default function withValidation<T = undefined, D = void>({
description, hideDescriptionIfValid, deriveData, rules,
}: IArgs<T, D>) {
return async function onValidate({ value, focused, allowEmpty = true }: IFieldState): Promise<IValidationResult> {
if (!value && allowEmpty) {
return {
@ -156,7 +161,7 @@ export default function withValidation<T = undefined, D = void>({ description, d
}
let summary;
if (description) {
if (description && (details || !hideDescriptionIfValid)) {
// We're setting `this` to whichever component holds the validation
// function. That allows rules to access the state of the component.
const content = description.call(this, derivedData);

View file

@ -1,63 +0,0 @@
/*
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 PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
export default class EncryptionEvent extends React.Component {
render() {
const {mxEvent} = this.props;
let body;
let classes = "mx_EventTile_bubble mx_cryptoEvent mx_cryptoEvent_icon";
const isRoomEncrypted = MatrixClientPeg.get().isRoomEncrypted(mxEvent.getRoomId());
if (mxEvent.getContent().algorithm === 'm.megolm.v1.aes-sha2' && isRoomEncrypted) {
body = <div>
<div className="mx_cryptoEvent_title">{_t("Encryption enabled")}</div>
<div className="mx_cryptoEvent_subtitle">
{_t(
"Messages in this room are end-to-end encrypted. " +
"Learn more & verify this user in their user profile.",
)}
</div>
</div>;
} else if (isRoomEncrypted) {
body = <div>
<div className="mx_cryptoEvent_title">{_t("Encryption enabled")}</div>
<div className="mx_cryptoEvent_subtitle">
{_t("Ignored attempt to disable encryption")}
</div>
</div>;
} else {
body = <div>
<div className="mx_cryptoEvent_title">{_t("Encryption not enabled")}</div>
<div className="mx_cryptoEvent_subtitle">{_t("The encryption used by this room isn't supported.")}</div>
</div>;
classes += " mx_cryptoEvent_icon_warning";
}
return (<div className={classes}>
{body}
</div>);
}
}
EncryptionEvent.propTypes = {
/* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired,
};

View file

@ -0,0 +1,68 @@
/*
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, {forwardRef, useContext} from 'react';
import {MatrixEvent} from "matrix-js-sdk/src/models/event";
import { _t } from '../../../languageHandler';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import EventTileBubble from "./EventTileBubble";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import DMRoomMap from "../../../utils/DMRoomMap";
interface IProps {
mxEvent: MatrixEvent;
}
const EncryptionEvent = forwardRef<HTMLDivElement, IProps>(({mxEvent}, ref) => {
const cli = useContext(MatrixClientContext);
const roomId = mxEvent.getRoomId();
const isRoomEncrypted = MatrixClientPeg.get().isRoomEncrypted(roomId);
if (mxEvent.getContent().algorithm === 'm.megolm.v1.aes-sha2' && isRoomEncrypted) {
let subtitle: string;
const dmPartner = DMRoomMap.shared().getUserIdForRoomId(roomId);
if (dmPartner) {
const displayName = cli?.getRoom(roomId)?.getMember(dmPartner)?.rawDisplayName || dmPartner;
subtitle = _t("Messages here are end-to-end encrypted. " +
"Verify %(displayName)s in their profile - tap on their avatar.", { displayName });
} else {
subtitle = _t("Messages in this room are end-to-end encrypted. " +
"When people join, you can verify them in their profile, just tap on their avatar.");
}
return <EventTileBubble
className="mx_cryptoEvent mx_cryptoEvent_icon"
title={_t("Encryption enabled")}
subtitle={subtitle}
/>;
} else if (isRoomEncrypted) {
return <EventTileBubble
className="mx_cryptoEvent mx_cryptoEvent_icon"
title={_t("Encryption enabled")}
subtitle={_t("Ignored attempt to disable encryption")}
/>;
}
return <EventTileBubble
className="mx_cryptoEvent mx_cryptoEvent_icon mx_cryptoEvent_icon_warning"
title={_t("Encryption not enabled")}
subtitle={_t("The encryption used by this room isn't supported.")}
ref={ref}
/>;
});
export default EncryptionEvent;

View file

@ -0,0 +1,34 @@
/*
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, {forwardRef, ReactNode} from "react";
import classNames from "classnames";
interface IProps {
className: string;
title: string;
subtitle?: ReactNode;
}
const EventTileBubble = forwardRef<HTMLDivElement, IProps>(({ className, title, subtitle, children }, ref) => {
return <div className={classNames("mx_EventTileBubble", className)} ref={ref}>
<div className="mx_EventTileBubble_title">{ title }</div>
{ subtitle && <div className="mx_EventTileBubble_subtitle">{ subtitle }</div> }
{ children }
</div>;
});
export default EventTileBubble;

View file

@ -18,6 +18,7 @@ import React from 'react';
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { _t } from "../../../languageHandler";
import WidgetStore from "../../../stores/WidgetStore";
import EventTileBubble from "./EventTileBubble";
interface IProps {
mxEvent: MatrixEvent;
@ -40,37 +41,24 @@ export default class MJitsiWidgetEvent extends React.PureComponent<IProps> {
if (!url) {
// removed
return (
<div className='mx_EventTile_bubble mx_MJitsiWidgetEvent'>
<div className='mx_MJitsiWidgetEvent_title'>
{_t('Video conference ended by %(senderName)s', {senderName})}
</div>
</div>
);
return <EventTileBubble
className="mx_MJitsiWidgetEvent"
title={_t('Video conference ended by %(senderName)s', {senderName})}
/>;
} else if (prevUrl) {
// modified
return (
<div className='mx_EventTile_bubble mx_MJitsiWidgetEvent'>
<div className='mx_MJitsiWidgetEvent_title'>
{_t('Video conference updated by %(senderName)s', {senderName})}
</div>
<div className='mx_MJitsiWidgetEvent_subtitle'>
{joinCopy}
</div>
</div>
);
return <EventTileBubble
className="mx_MJitsiWidgetEvent"
title={_t('Video conference updated by %(senderName)s', {senderName})}
subtitle={joinCopy}
/>;
} else {
// assume added
return (
<div className='mx_EventTile_bubble mx_MJitsiWidgetEvent'>
<div className='mx_MJitsiWidgetEvent_title'>
{_t("Video conference started by %(senderName)s", {senderName})}
</div>
<div className='mx_MJitsiWidgetEvent_subtitle'>
{joinCopy}
</div>
</div>
);
return <EventTileBubble
className="mx_MJitsiWidgetEvent"
title={_t("Video conference started by %(senderName)s", {senderName})}
subtitle={joinCopy}
/>;
}
}
}

View file

@ -21,6 +21,7 @@ import {MatrixClientPeg} from '../../../MatrixClientPeg';
import { _t } from '../../../languageHandler';
import {getNameForEventRoom, userLabelForEventRoom}
from '../../../utils/KeyVerificationStateObserver';
import EventTileBubble from "./EventTileBubble";
export default class MKeyVerificationConclusion extends React.Component {
constructor(props) {
@ -115,14 +116,14 @@ export default class MKeyVerificationConclusion extends React.Component {
}
if (title) {
const subtitle = userLabelForEventRoom(request.otherUserId, mxEvent.getRoomId());
const classes = classNames("mx_EventTile_bubble", "mx_cryptoEvent", "mx_cryptoEvent_icon", {
const classes = classNames("mx_cryptoEvent mx_cryptoEvent_icon", {
mx_cryptoEvent_icon_verified: request.done,
});
return (<div className={classes}>
<div className="mx_cryptoEvent_title">{title}</div>
<div className="mx_cryptoEvent_subtitle">{subtitle}</div>
</div>);
return <EventTileBubble
className={classes}
title={title}
subtitle={userLabelForEventRoom(request.otherUserId, mxEvent.getRoomId())}
/>;
}
return null;

View file

@ -24,6 +24,7 @@ import {getNameForEventRoom, userLabelForEventRoom}
import dis from "../../../dispatcher/dispatcher";
import {RightPanelPhases} from "../../../stores/RightPanelStorePhases";
import {Action} from "../../../dispatcher/actions";
import EventTileBubble from "./EventTileBubble";
export default class MKeyVerificationRequest extends React.Component {
constructor(props) {
@ -146,10 +147,8 @@ export default class MKeyVerificationRequest extends React.Component {
if (!request.initiatedByMe) {
const name = getNameForEventRoom(request.requestingUserId, mxEvent.getRoomId());
title = (<div className="mx_cryptoEvent_title">{
_t("%(name)s wants to verify", {name})}</div>);
subtitle = (<div className="mx_cryptoEvent_subtitle">{
userLabelForEventRoom(request.requestingUserId, mxEvent.getRoomId())}</div>);
title = _t("%(name)s wants to verify", {name});
subtitle = userLabelForEventRoom(request.requestingUserId, mxEvent.getRoomId());
if (request.canAccept) {
stateNode = (<div className="mx_cryptoEvent_buttons">
<FormButton kind="danger" onClick={this._onRejectClicked} label={_t("Decline")} />
@ -157,18 +156,18 @@ export default class MKeyVerificationRequest extends React.Component {
</div>);
}
} else { // request sent by us
title = (<div className="mx_cryptoEvent_title">{
_t("You sent a verification request")}</div>);
subtitle = (<div className="mx_cryptoEvent_subtitle">{
userLabelForEventRoom(request.receivingUserId, mxEvent.getRoomId())}</div>);
title = _t("You sent a verification request");
subtitle = userLabelForEventRoom(request.receivingUserId, mxEvent.getRoomId());
}
if (title) {
return (<div className="mx_EventTile_bubble mx_cryptoEvent mx_cryptoEvent_icon">
{title}
{subtitle}
{stateNode}
</div>);
return <EventTileBubble
className="mx_cryptoEvent mx_cryptoEvent_icon"
title={title}
subtitle={subtitle}
>
{ stateNode }
</EventTileBubble>;
}
return null;
}

View file

@ -22,6 +22,7 @@ import dis from '../../../dispatcher/dispatcher';
import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
import { _t } from '../../../languageHandler';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import EventTileBubble from "./EventTileBubble";
export default class RoomCreate extends React.Component {
static propTypes = {
@ -51,17 +52,16 @@ export default class RoomCreate extends React.Component {
const permalinkCreator = new RoomPermalinkCreator(prevRoom, predecessor['room_id']);
permalinkCreator.load();
const predecessorPermalink = permalinkCreator.forEvent(predecessor['event_id']);
return <div className="mx_CreateEvent">
<div className="mx_CreateEvent_image" />
<div className="mx_CreateEvent_header">
{_t("This room is a continuation of another conversation.")}
</div>
<a className="mx_CreateEvent_link"
href={predecessorPermalink}
onClick={this._onLinkClicked}
>
const link = (
<a href={predecessorPermalink} onClick={this._onLinkClicked}>
{_t("Click here to see older messages.")}
</a>
</div>;
);
return <EventTileBubble
className="mx_CreateEvent"
title={_t("This room is a continuation of another conversation.")}
subtitle={link}
/>;
}
}

View file

@ -51,7 +51,6 @@ import BaseCard from "./BaseCard";
import {E2EStatus} from "../../../utils/ShieldUtils";
import ImageView from "../elements/ImageView";
import Spinner from "../elements/Spinner";
import IconButton from "../elements/IconButton";
import PowerSelector from "../elements/PowerSelector";
import MemberAvatar from "../avatars/MemberAvatar";
import PresenceLabel from "../rooms/PresenceLabel";
@ -60,6 +59,7 @@ import ErrorDialog from "../dialogs/ErrorDialog";
import QuestionDialog from "../dialogs/QuestionDialog";
import ConfirmUserActionDialog from "../dialogs/ConfirmUserActionDialog";
import InfoDialog from "../dialogs/InfoDialog";
import { EventType } from "matrix-js-sdk/src/@types/event";
interface IDevice {
deviceId: string;
@ -586,7 +586,10 @@ const RedactMessagesButton: React.FC<IBaseProps> = ({member}) => {
while (timeline) {
eventsToRedact = timeline.getEvents().reduce((events, event) => {
if (event.getSender() === userId && !event.isRedacted() && !event.isRedaction() &&
event.getType() !== "m.room.create"
event.getType() !== EventType.RoomCreate &&
// Don't redact ACLs because that'll obliterate the room
// See https://github.com/matrix-org/synapse/issues/4042 for details.
event.getType() !== EventType.RoomServerAcl
) {
return events.concat(event);
} else {
@ -1024,24 +1027,15 @@ const PowerLevelSection: React.FC<{
roomPermissions: IRoomPermissions;
powerLevels: IPowerLevelsContent;
}> = ({user, room, roomPermissions, powerLevels}) => {
const [isEditing, setEditing] = useState(false);
if (isEditing) {
return (<PowerLevelEditor
user={user} room={room} roomPermissions={roomPermissions}
onFinished={() => setEditing(false)} />);
if (roomPermissions.canEdit) {
return (<PowerLevelEditor user={user} room={room} roomPermissions={roomPermissions} />);
} else {
const powerLevelUsersDefault = powerLevels.users_default || 0;
const powerLevel = parseInt(user.powerLevel, 10);
const modifyButton = roomPermissions.canEdit ?
(<IconButton icon="edit" onClick={() => setEditing(true)} />) : null;
const role = textualPowerLevel(powerLevel, powerLevelUsersDefault);
const label = _t("<strong>%(role)s</strong> in %(roomName)s",
{role, roomName: room.name},
{strong: label => <strong>{label}</strong>},
);
return (
<div className="mx_UserInfo_profileField">
<div className="mx_UserInfo_roleDescription">{label}{modifyButton}</div>
<div className="mx_UserInfo_roleDescription">{role}</div>
</div>
);
}
@ -1051,20 +1045,15 @@ const PowerLevelEditor: React.FC<{
user: User;
room: Room;
roomPermissions: IRoomPermissions;
onFinished(): void;
}> = ({user, room, roomPermissions, onFinished}) => {
}> = ({user, room, roomPermissions}) => {
const cli = useContext(MatrixClientContext);
const [isUpdating, setIsUpdating] = useState(false);
const [selectedPowerLevel, setSelectedPowerLevel] = useState(parseInt(user.powerLevel, 10));
const [isDirty, setIsDirty] = useState(false);
const onPowerChange = useCallback((powerLevel) => {
setIsDirty(true);
setSelectedPowerLevel(parseInt(powerLevel, 10));
}, [setSelectedPowerLevel, setIsDirty]);
const onPowerChange = useCallback(async (powerLevelStr: string) => {
const powerLevel = parseInt(powerLevelStr, 10);
setSelectedPowerLevel(powerLevel);
const changePowerLevel = useCallback(async () => {
const _applyPowerChange = (roomId, target, powerLevel, powerLevelEvent) => {
const applyPowerChange = (roomId, target, powerLevel, powerLevelEvent) => {
return cli.setPowerLevel(roomId, target, parseInt(powerLevel), powerLevelEvent).then(
function() {
// NO-OP; rely on the m.room.member event coming down else we could
@ -1080,64 +1069,42 @@ const PowerLevelEditor: React.FC<{
);
};
try {
if (!isDirty) {
return;
}
const roomId = user.roomId;
const target = user.userId;
setIsUpdating(true);
const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
if (!powerLevelEvent) return;
const powerLevel = selectedPowerLevel;
const myUserId = cli.getUserId();
const myPower = powerLevelEvent.getContent().users[myUserId];
if (myPower && parseInt(myPower) === powerLevel) {
const {finished} = Modal.createTrackedDialog('Promote to PL100 Warning', '', QuestionDialog, {
title: _t("Warning!"),
description:
<div>
{ _t("You will not be able to undo this change as you are promoting the user " +
"to have the same power level as yourself.") }<br />
{ _t("Are you sure?") }
</div>,
button: _t("Continue"),
});
const roomId = user.roomId;
const target = user.userId;
const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
if (!powerLevelEvent) return;
if (!powerLevelEvent.getContent().users) {
_applyPowerChange(roomId, target, powerLevel, powerLevelEvent);
return;
}
const myUserId = cli.getUserId();
const [confirmed] = await finished;
if (!confirmed) return;
} else if (myUserId === target) {
// If we are changing our own PL it can only ever be decreasing, which we cannot reverse.
if (myUserId === target) {
try {
if (!(await warnSelfDemote())) return;
} catch (e) {
console.error("Failed to warn about self demotion: ", e);
}
await _applyPowerChange(roomId, target, powerLevel, powerLevelEvent);
return;
try {
if (!(await warnSelfDemote())) return;
} catch (e) {
console.error("Failed to warn about self demotion: ", e);
}
const myPower = powerLevelEvent.getContent().users[myUserId];
if (parseInt(myPower) === powerLevel) {
const {finished} = Modal.createTrackedDialog('Promote to PL100 Warning', '', QuestionDialog, {
title: _t("Warning!"),
description:
<div>
{ _t("You will not be able to undo this change as you are promoting the user " +
"to have the same power level as yourself.") }<br />
{ _t("Are you sure?") }
</div>,
button: _t("Continue"),
});
const [confirmed] = await finished;
if (!confirmed) return;
}
await _applyPowerChange(roomId, target, powerLevel, powerLevelEvent);
} finally {
onFinished();
}
}, [user.roomId, user.userId, cli, selectedPowerLevel, isDirty, setIsUpdating, onFinished, room]);
await applyPowerChange(roomId, target, powerLevel, powerLevelEvent);
}, [user.roomId, user.userId, cli, room]);
const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
const powerLevelUsersDefault = powerLevelEvent ? powerLevelEvent.getContent().users_default : 0;
const buttonOrSpinner = isUpdating ? <Spinner w={16} h={16} /> :
<IconButton icon="check" onClick={changePowerLevel} />;
return (
<div className="mx_UserInfo_profileField">
@ -1147,9 +1114,7 @@ const PowerLevelEditor: React.FC<{
maxValue={roomPermissions.modifyLevelMax}
usersDefault={powerLevelUsersDefault}
onChange={onPowerChange}
disabled={isUpdating}
/>
{buttonOrSpinner}
</div>
);
};
@ -1339,13 +1304,17 @@ const BasicUserInfo: React.FC<{
}
let memberDetails;
if (room && member.roomId) {
memberDetails = <PowerLevelSection
powerLevels={powerLevels}
user={member}
room={room}
roomPermissions={roomPermissions}
/>;
// hide the Roles section for DMs as it doesn't make sense there
if (room && member.roomId && !DMRoomMap.shared().getUserIdForRoomId(member.roomId)) {
memberDetails = <div className="mx_UserInfo_container">
<h3>{ _t("Role") }</h3>
<PowerLevelSection
powerLevels={powerLevels}
user={member}
room={room}
roomPermissions={roomPermissions}
/>
</div>;
}
// only display the devices list if our client supports E2E
@ -1415,12 +1384,7 @@ const BasicUserInfo: React.FC<{
);
return <React.Fragment>
{ memberDetails &&
<div className="mx_UserInfo_container mx_UserInfo_separator mx_UserInfo_memberDetailsContainer">
<div className="mx_UserInfo_memberDetails">
{ memberDetails }
</div>
</div> }
{ memberDetails }
{ securitySection }
<UserOptionsSection

View file

@ -21,6 +21,7 @@ import ReplyThread from "../elements/ReplyThread";
import React, {createRef} from 'react';
import PropTypes from 'prop-types';
import classNames from "classnames";
import {EventType} from "matrix-js-sdk/src/@types/event";
import { _t, _td } from '../../../languageHandler';
import * as TextForEvent from "../../../TextForEvent";
import * as sdk from "../../../index";
@ -646,12 +647,13 @@ export default class EventTile extends React.Component {
// Info messages are basically information about commands processed on a room
const isBubbleMessage = eventType.startsWith("m.key.verification") ||
(eventType === "m.room.message" && msgtype && msgtype.startsWith("m.key.verification")) ||
(eventType === "m.room.encryption") ||
(eventType === EventType.RoomMessage && msgtype && msgtype.startsWith("m.key.verification")) ||
(eventType === EventType.RoomCreate) ||
(eventType === EventType.RoomEncryption) ||
(tileHandler === "messages.MJitsiWidgetEvent");
let isInfoMessage = (
!isBubbleMessage && eventType !== 'm.room.message' &&
eventType !== 'm.sticker' && eventType !== 'm.room.create'
!isBubbleMessage && eventType !== EventType.RoomMessage &&
eventType !== EventType.Sticker && eventType !== EventType.RoomCreate
);
// If we're showing hidden events in the timeline, we should use the

View file

@ -0,0 +1,135 @@
/*
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, {useContext} from "react";
import {EventType} from "matrix-js-sdk/src/@types/event";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import RoomContext from "../../../contexts/RoomContext";
import DMRoomMap from "../../../utils/DMRoomMap";
import {_t} from "../../../languageHandler";
import AccessibleButton from "../elements/AccessibleButton";
import MiniAvatarUploader, {AVATAR_SIZE} from "../elements/MiniAvatarUploader";
import RoomAvatar from "../avatars/RoomAvatar";
import defaultDispatcher from "../../../dispatcher/dispatcher";
import {ViewUserPayload} from "../../../dispatcher/payloads/ViewUserPayload";
import {Action} from "../../../dispatcher/actions";
import dis from "../../../dispatcher/dispatcher";
const NewRoomIntro = () => {
const cli = useContext(MatrixClientContext);
const {room, roomId} = useContext(RoomContext);
const dmPartner = DMRoomMap.shared().getUserIdForRoomId(roomId);
let body;
if (dmPartner) {
let caption;
if ((room.getJoinedMemberCount() + room.getInvitedMemberCount()) === 2) {
caption = _t("Only the two of you are in this conversation, unless either of you invites anyone to join.");
}
const member = room?.getMember(dmPartner);
const displayName = member?.rawDisplayName || dmPartner;
body = <React.Fragment>
<RoomAvatar room={room} width={AVATAR_SIZE} height={AVATAR_SIZE} onClick={() => {
defaultDispatcher.dispatch<ViewUserPayload>({
action: Action.ViewUser,
// XXX: We should be using a real member object and not assuming what the receiver wants.
member: member || {userId: dmPartner},
});
}} />
<h2>{ room.name }</h2>
<p>{_t("This is the beginning of your direct message history with <displayName/>.", {}, {
displayName: () => <b>{ displayName }</b>,
})}</p>
{ caption && <p>{ caption }</p> }
</React.Fragment>;
} else {
const topic = room.currentState.getStateEvents(EventType.RoomTopic, "")?.getContent()?.topic;
const canAddTopic = room.currentState.maySendStateEvent(EventType.RoomTopic, cli.getUserId());
const onTopicClick = () => {
dis.dispatch({
action: "open_room_settings",
room_id: roomId,
}, true);
// focus the topic field to help the user find it as it'll gain an outline
setImmediate(() => {
window.document.getElementById("profileTopic").focus();
});
};
let topicText;
if (canAddTopic && topic) {
topicText = _t("Topic: %(topic)s (<a>edit</a>)", { topic }, {
a: sub => <AccessibleButton kind="link" onClick={onTopicClick}>{ sub }</AccessibleButton>,
});
} else if (topic) {
topicText = _t("Topic: %(topic)s ", { topic });
} else if (canAddTopic) {
topicText = _t("<a>Add a topic</a> to help people know what it is about.", {}, {
a: sub => <AccessibleButton kind="link" onClick={onTopicClick}>{ sub }</AccessibleButton>,
});
}
const creator = room.currentState.getStateEvents(EventType.RoomCreate, "")?.getSender();
const creatorName = room?.getMember(creator)?.rawDisplayName || creator;
let createdText;
if (creator === cli.getUserId()) {
createdText = _t("You created this room.");
} else {
createdText = _t("%(displayName)s created this room.", {
displayName: creatorName,
});
}
const onInviteClick = () => {
dis.dispatch({ action: "view_invite", roomId });
};
const avatarUrl = room.currentState.getStateEvents(EventType.RoomAvatar, "")?.getContent()?.url;
body = <React.Fragment>
<MiniAvatarUploader
hasAvatar={!!avatarUrl}
noAvatarLabel={_t("Add a photo, so people can easily spot your room.")}
setAvatarUrl={url => cli.sendStateEvent(roomId, EventType.RoomAvatar, { url }, '')}
>
<RoomAvatar room={room} width={AVATAR_SIZE} height={AVATAR_SIZE} />
</MiniAvatarUploader>
<h2>{ room.name }</h2>
<p>{createdText} {_t("This is the start of <roomName/>.", {}, {
roomName: () => <b>{ room.name }</b>,
})}</p>
<p>{topicText}</p>
<div className="mx_NewRoomIntro_buttons">
<AccessibleButton className="mx_NewRoomIntro_inviteButton" kind="primary" onClick={onInviteClick}>
{_t("Invite to this room")}
</AccessibleButton>
</div>
</React.Fragment>;
}
return <div className="mx_NewRoomIntro">
{ body }
</div>;
};
export default NewRoomIntro;

View file

@ -332,6 +332,9 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
return p;
}, [] as TagID[]);
// show a skeleton UI if the user is in no rooms
const showSkeleton = Object.values(RoomListStore.instance.unfilteredLists).every(list => !list?.length);
for (const orderedTagId of tagOrder) {
const orderedRooms = this.state.sublists[orderedTagId] || [];
const extraTiles = orderedTagId === DefaultTagID.Invite ? this.renderCommunityInvites() : null;
@ -356,6 +359,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
addRoomContextMenu={aesthetics.addRoomContextMenu}
isMinimized={this.props.isMinimized}
onResize={this.props.onResize}
showSkeleton={showSkeleton}
extraBadTilesThatShouldntExist={extraTiles}
/>);
}
@ -365,13 +369,28 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
public render() {
let explorePrompt: JSX.Element;
if (!this.props.isMinimized && RoomListStore.instance.getFirstNameFilterCondition()) {
explorePrompt = <div className="mx_RoomList_explorePrompt">
<div>{_t("Can't see what youre looking for?")}</div>
<AccessibleButton kind="link" onClick={this.onExplore}>
{_t("Explore all public rooms")}
</AccessibleButton>
</div>;
if (!this.props.isMinimized) {
if (RoomListStore.instance.getFirstNameFilterCondition()) {
explorePrompt = <div className="mx_RoomList_explorePrompt">
<div>{_t("Can't see what youre looking for?")}</div>
<AccessibleButton kind="link" onClick={this.onExplore}>
{_t("Explore all public rooms")}
</AccessibleButton>
</div>;
} else if (Object.values(this.state.sublists).some(list => list.length > 0)) {
const unfilteredLists = RoomListStore.instance.unfilteredLists
const unfilteredRooms = unfilteredLists[DefaultTagID.Untagged] || [];
const unfilteredHistorical = unfilteredLists[DefaultTagID.Archived] || [];
// show a prompt to join/create rooms if the user is in 0 rooms and no historical
if (unfilteredRooms.length < 1 && unfilteredHistorical < 1) {
explorePrompt = <div className="mx_RoomList_explorePrompt">
<div>{_t("Use the + to make a new room or explore existing ones below")}</div>
<AccessibleButton kind="link" onClick={this.onExplore}>
{_t("Explore all public rooms")}
</AccessibleButton>
</div>;
}
}
}
const sublists = this.renderSublists();

View file

@ -71,6 +71,7 @@ interface IProps {
isMinimized: boolean;
tagId: TagID;
onResize: () => void;
showSkeleton?: boolean;
// TODO: Don't use this. It's for community invites, and community invites shouldn't be here.
// You should feel bad if you use this.
@ -877,6 +878,8 @@ export default class RoomSublist extends React.Component<IProps, IState> {
</Resizable>
</React.Fragment>
);
} else if (this.props.showSkeleton && this.state.isExpanded) {
content = <div className="mx_RoomSublist_skeletonUI" />;
}
return (

View file

@ -308,6 +308,9 @@ export default class SendMessageComposer extends React.Component {
const startTime = CountlyAnalytics.getTimestamp();
const {roomId} = this.props.room;
const content = createMessageContent(this.model, this.props.permalinkCreator, replyToEvent);
// don't bother sending an empty message
if (!content.body.trim()) return;
const prom = this.context.sendMessage(roomId, content);
if (replyToEvent) {
// Clear reply_to_event as we put the message into the queue

View file

@ -129,11 +129,13 @@ export default class EventIndexPanel extends React.Component {
eventIndexingSettings = (
<div>
<div className='mx_SettingsTab_subsectionText'>
{_t( "Securely cache encrypted messages locally for them " +
"to appear in search results, using ")
} {formatBytes(this.state.eventIndexSize, 0)}
{_t( " to store messages from ")}
{formatCountLong(this.state.roomCount)} {_t("rooms.")}
{_t("Securely cache encrypted messages locally for them " +
"to appear in search results, using %(size)s to store messages from %(count)s rooms.",
{
size: formatBytes(this.state.eventIndexSize, 0),
count: formatCountLong(this.state.roomCount),
},
)}
</div>
<div>
<AccessibleButton kind="primary" onClick={this._onManage}>

View file

@ -31,6 +31,7 @@ import SdkConfig from "../../../SdkConfig";
import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
import AccessibleButton from "../elements/AccessibleButton";
import {SettingLevel} from "../../../settings/SettingLevel";
import {UIFeature} from "../../../settings/UIFeature";
// TODO: this "view" component still has far too much application logic in it,
// which should be factored out to other files.
@ -783,14 +784,14 @@ export default class Notifications extends React.Component {
const emailThreepids = this.state.threepids.filter((tp) => tp.medium === "email");
let emailNotificationsRows;
if (emailThreepids.length === 0) {
emailNotificationsRows = <div>
{ _t('Add an email address to configure email notifications') }
</div>;
} else {
if (emailThreepids.length > 0) {
emailNotificationsRows = emailThreepids.map((threePid) => this.emailNotificationsRow(
threePid.address, `${_t('Enable email notifications')} (${threePid.address})`,
));
} else if (SettingsStore.getValue(UIFeature.ThirdPartyID)) {
emailNotificationsRows = <div>
{ _t('Add an email address to configure email notifications') }
</div>;
}
// Build external push rules

View file

@ -84,6 +84,9 @@ export default class ProfileSettings extends React.Component {
}
if (this.state.avatarFile) {
console.log(
`Uploading new avatar, ${this.state.avatarFile.name} of type ${this.state.avatarFile.type},` +
` (${this.state.avatarFile.size}) bytes`);
const uri = await client.uploadContent(this.state.avatarFile);
await client.setAvatarUrl(uri);
newState.avatarUrl = client.mxcUrlToHttp(uri, 96, 96, 'crop', false);
@ -93,6 +96,7 @@ export default class ProfileSettings extends React.Component {
await client.setAvatarUrl(""); // use empty string as Synapse 500s on undefined
}
} catch (err) {
console.log("Failed to save profile", err);
Modal.createTrackedDialog('Failed to save profile', '', ErrorDialog, {
title: _t("Failed to save your profile"),
description: ((err && err.message) ? err.message : _t("The operation could not be completed")),

View file

@ -24,7 +24,7 @@ import dis from '../../../dispatcher/dispatcher';
import { ActionPayload } from '../../../dispatcher/payloads';
import PersistentApp from "../elements/PersistentApp";
import SettingsStore from "../../../settings/SettingsStore";
import { CallState, MatrixCall } from 'matrix-js-sdk/lib/webrtc/call';
import { CallState, MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
interface IProps {
}

View file

@ -15,17 +15,18 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, {createRef} from 'react';
import React, { createRef } from 'react';
import Room from 'matrix-js-sdk/src/models/room';
import dis from '../../../dispatcher/dispatcher';
import CallHandler from '../../../CallHandler';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import { _t } from '../../../languageHandler';
import AccessibleButton from '../elements/AccessibleButton';
import VideoView from "./VideoView";
import VideoFeed, { VideoFeedType } from "./VideoFeed";
import RoomAvatar from "../avatars/RoomAvatar";
import PulsedAvatar from '../avatars/PulsedAvatar';
import { CallState, MatrixCall } from 'matrix-js-sdk/lib/webrtc/call';
import { CallState, CallType, MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
import { CallEvent } from 'matrix-js-sdk/src/webrtc/call';
interface IProps {
// js-sdk room object. If set, we will only show calls for the given
@ -50,53 +51,104 @@ interface IProps {
}
interface IState {
call: any;
call: MatrixCall;
isLocalOnHold: boolean,
}
function getFullScreenElement() {
return (
document.fullscreenElement ||
// moz omitted because firefox supports this unprefixed now (webkit here for safari)
document.webkitFullscreenElement ||
document.msFullscreenElement
);
}
function requestFullscreen(element: Element) {
const method = (
element.requestFullscreen ||
// moz omitted since firefox supports unprefixed now
element.webkitRequestFullScreen ||
element.msRequestFullscreen
);
if (method) method.call(element);
}
function exitFullscreen() {
const exitMethod = (
document.exitFullscreen ||
document.webkitExitFullscreen ||
document.msExitFullscreen
);
if (exitMethod) exitMethod.call(document);
}
export default class CallView extends React.Component<IProps, IState> {
private videoref: React.RefObject<any>;
private dispatcherRef: string;
public call: any;
private container = createRef<HTMLDivElement>();
constructor(props: IProps) {
super(props);
const call = this.getCall();
this.state = {
// the call this view is displaying (if any)
call: null,
};
call,
isLocalOnHold: call ? call.isLocalOnHold() : null,
}
this.videoref = createRef();
this.updateCallListeners(null, call);
}
public componentDidMount() {
this.dispatcherRef = dis.register(this.onAction);
this.showCall();
}
public componentWillUnmount() {
this.updateCallListeners(this.state.call, null);
dis.unregister(this.dispatcherRef);
}
private onAction = (payload) => {
// don't filter out payloads for room IDs other than props.room because
// we may be interested in the conf 1:1 room
if (payload.action !== 'call_state') {
return;
switch (payload.action) {
case 'video_fullscreen': {
if (!this.container.current) {
return;
}
if (payload.fullscreen) {
requestFullscreen(this.container.current);
} else if (getFullScreenElement()) {
exitFullscreen();
}
break;
}
case 'call_state': {
const newCall = this.getCall();
if (newCall !== this.state.call) {
this.updateCallListeners(this.state.call, newCall);
this.setState({
call: newCall,
isLocalOnHold: newCall ? newCall.isLocalOnHold() : null,
});
}
if (!newCall && getFullScreenElement()) {
exitFullscreen();
}
break;
}
}
this.showCall();
};
private showCall() {
private getCall(): MatrixCall {
let call: MatrixCall;
if (this.props.room) {
const roomId = this.props.room.roomId;
call = CallHandler.sharedInstance().getCallForRoom(roomId);
if (this.call) {
this.setState({ call: call });
}
// We don't currently show voice calls in this view when in the room:
// they're represented in the room status bar at the bottom instead
// (but this will all change with the new designs)
if (call && call.type == CallType.Voice) call = null;
} else {
call = CallHandler.sharedInstance().getAnyActiveCall();
// Ignore calls if we can't get the room associated with them.
@ -106,65 +158,68 @@ export default class CallView extends React.Component<IProps, IState> {
if (MatrixClientPeg.get().getRoom(call.roomId) === null) {
call = null;
}
this.setState({ call: call });
}
if (call) {
if (this.getVideoView()) {
call.setLocalVideoElement(this.getVideoView().getLocalVideoElement());
call.setRemoteVideoElement(this.getVideoView().getRemoteVideoElement());
// always use a separate element for audio stream playback.
// this is to let us move CallView around the DOM without interrupting remote audio
// during playback, by having the audio rendered by a top-level <audio/> element.
// rather than being rendered by the main remoteVideo <video/> element.
call.setRemoteAudioElement(this.getVideoView().getRemoteAudioElement());
}
}
if (call && call.type === "video" && call.state !== CallState.Ended && call.state !== CallState.Ringing) {
this.getVideoView().getLocalVideoElement().style.display = "block";
this.getVideoView().getRemoteVideoElement().style.display = "block";
} else {
this.getVideoView().getLocalVideoElement().style.display = "none";
this.getVideoView().getRemoteVideoElement().style.display = "none";
dis.dispatch({action: 'video_fullscreen', fullscreen: false});
}
if (this.props.onResize) {
this.props.onResize();
}
if (call && call.state == CallState.Ended) return null;
return call;
}
private getVideoView() {
return this.videoref.current;
private updateCallListeners(oldCall: MatrixCall, newCall: MatrixCall) {
if (oldCall === newCall) return;
if (oldCall) oldCall.removeListener(CallEvent.HoldUnhold, this.onCallHoldUnhold);
if (newCall) newCall.on(CallEvent.HoldUnhold, this.onCallHoldUnhold);
}
private onCallHoldUnhold = () => {
this.setState({
isLocalOnHold: this.state.call ? this.state.call.isLocalOnHold() : null,
});
};
public render() {
let view: React.ReactNode;
if (this.state.call && this.state.call.type === "voice") {
const client = MatrixClientPeg.get();
const callRoom = client.getRoom(this.state.call.roomId);
view = <AccessibleButton className="mx_CallView_voice" onClick={this.props.onClick}>
<PulsedAvatar>
<RoomAvatar
room={callRoom}
height={35}
width={35}
if (this.state.call) {
if (this.state.call.type === "voice") {
const client = MatrixClientPeg.get();
const callRoom = client.getRoom(this.state.call.roomId);
let caption = _t("Active call");
if (this.state.isLocalOnHold) {
// we currently have no UI for holding / unholding a call (apart from slash
// commands) so we don't disintguish between when we've put the call on hold
// (ie. we'd show an unhold button) and when the other side has put us on hold
// (where obviously we would not show such a button).
caption = _t("Call Paused");
}
view = <AccessibleButton className="mx_CallView_voice" onClick={this.props.onClick}>
<PulsedAvatar>
<RoomAvatar
room={callRoom}
height={35}
width={35}
/>
</PulsedAvatar>
<div>
<h1>{callRoom.name}</h1>
<p>{ caption }</p>
</div>
</AccessibleButton>;
} else {
// For video calls, we currently ignore the call hold state altogether
// (the video will just go black)
// if we're fullscreen, we don't want to set a maxHeight on the video element.
const maxVideoHeight = getFullScreenElement() ? null : this.props.maxVideoHeight;
view = <div className="mx_CallView_video" onClick={this.props.onClick}>
<VideoFeed type={VideoFeedType.Remote} call={this.state.call} onResize={this.props.onResize}
maxHeight={maxVideoHeight}
/>
</PulsedAvatar>
<div>
<h1>{callRoom.name}</h1>
<p>{ _t("Active call") }</p>
</div>
</AccessibleButton>;
} else {
view = <VideoView
ref={this.videoref}
onClick={this.props.onClick}
onResize={this.props.onResize}
maxHeight={this.props.maxVideoHeight}
/>;
<VideoFeed type={VideoFeedType.Local} call={this.state.call} />
</div>;
}
}
let hangup: React.ReactNode;
@ -180,10 +235,9 @@ export default class CallView extends React.Component<IProps, IState> {
/>;
}
return <div className={this.props.className}>
return <div className={this.props.className} ref={this.container}>
{view}
{hangup}
</div>;
}
}

View file

@ -1,58 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2019 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, {createRef} from 'react';
import PropTypes from 'prop-types';
export default class VideoFeed extends React.Component {
static propTypes = {
// maxHeight style attribute for the video element
maxHeight: PropTypes.number,
// a callback which is called when the video element is resized
// due to a change in video metadata
onResize: PropTypes.func,
};
constructor(props) {
super(props);
this._vid = createRef();
}
componentDidMount() {
this._vid.current.addEventListener('resize', this.onResize);
}
componentWillUnmount() {
this._vid.current.removeEventListener('resize', this.onResize);
}
onResize = (e) => {
if (this.props.onResize) {
this.props.onResize(e);
}
};
render() {
return (
<video ref={this._vid} style={{maxHeight: this.props.maxHeight}}>
</video>
);
}
}

View file

@ -0,0 +1,80 @@
/*
Copyright 2015, 2016, 2019 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 classnames from 'classnames';
import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
import React, {createRef} from 'react';
import SettingsStore from "../../../settings/SettingsStore";
export enum VideoFeedType {
Local,
Remote,
}
interface IProps {
call: MatrixCall,
type: VideoFeedType,
// maxHeight style attribute for the video element
maxHeight?: number,
// a callback which is called when the video element is resized
// due to a change in video metadata
onResize?: (e: Event) => void,
}
export default class VideoFeed extends React.Component<IProps> {
private vid = createRef<HTMLVideoElement>();
componentDidMount() {
this.vid.current.addEventListener('resize', this.onResize);
if (this.props.type === VideoFeedType.Local) {
this.props.call.setLocalVideoElement(this.vid.current);
} else {
this.props.call.setRemoteVideoElement(this.vid.current);
}
}
componentWillUnmount() {
this.vid.current.removeEventListener('resize', this.onResize);
}
onResize = (e) => {
if (this.props.onResize) {
this.props.onResize(e);
}
};
render() {
const videoClasses = {
mx_VideoFeed: true,
mx_VideoFeed_local: this.props.type === VideoFeedType.Local,
mx_VideoFeed_remote: this.props.type === VideoFeedType.Remote,
mx_VideoFeed_mirror: (
this.props.type === VideoFeedType.Local &&
SettingsStore.getValue('VideoView.flipVideoHorizontally')
),
};
let videoStyle = {};
if (this.props.maxHeight) videoStyle = { maxHeight: this.props.maxHeight };
return <div className={classnames(videoClasses)}>
<video ref={this.vid} style={videoStyle}></video>
</div>;
}
}

View file

@ -1,142 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2019 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, {createRef} from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher';
import SettingsStore from "../../../settings/SettingsStore";
function getFullScreenElement() {
return (
document.fullscreenElement ||
document.mozFullScreenElement ||
document.webkitFullscreenElement ||
document.msFullscreenElement
);
}
export default class VideoView extends React.Component {
static propTypes = {
// maxHeight style attribute for the video element
maxHeight: PropTypes.number,
// a callback which is called when the user clicks on the video div
onClick: PropTypes.func,
// a callback which is called when the video element is resized due to
// a change in video metadata
onResize: PropTypes.func,
};
constructor(props) {
super(props);
this._local = createRef();
this._remote = createRef();
}
componentDidMount() {
this.dispatcherRef = dis.register(this.onAction);
}
componentWillUnmount() {
dis.unregister(this.dispatcherRef);
}
getRemoteVideoElement = () => {
return ReactDOM.findDOMNode(this._remote.current);
};
getRemoteAudioElement = () => {
// this needs to be somewhere at the top of the DOM which
// always exists to avoid audio interruptions.
// Might as well just use DOM.
const remoteAudioElement = document.getElementById("remoteAudio");
if (!remoteAudioElement) {
console.error("Failed to find remoteAudio element - cannot play audio!"
+ "You need to add an <audio/> to the DOM.");
}
return remoteAudioElement;
};
getLocalVideoElement = () => {
return ReactDOM.findDOMNode(this._local.current);
};
setContainer = (c) => {
this.container = c;
};
onAction = (payload) => {
switch (payload.action) {
case 'video_fullscreen': {
if (!this.container) {
return;
}
const element = this.container;
if (payload.fullscreen) {
const requestMethod = (
element.requestFullScreen ||
element.webkitRequestFullScreen ||
element.mozRequestFullScreen ||
element.msRequestFullscreen
);
requestMethod.call(element);
} else if (getFullScreenElement()) {
const exitMethod = (
document.exitFullscreen ||
document.mozCancelFullScreen ||
document.webkitExitFullscreen ||
document.msExitFullscreen
);
if (exitMethod) {
exitMethod.call(document);
}
}
break;
}
}
};
render() {
const VideoFeed = sdk.getComponent('voip.VideoFeed');
// if we're fullscreen, we don't want to set a maxHeight on the video element.
const maxVideoHeight = getFullScreenElement() ? null : this.props.maxHeight;
const localVideoFeedClasses = classNames("mx_VideoView_localVideoFeed",
{ "mx_VideoView_localVideoFeed_flipped":
SettingsStore.getValue('VideoView.flipVideoHorizontally'),
},
);
return (
<div className="mx_VideoView" ref={this.setContainer} onClick={this.props.onClick}>
<div className="mx_VideoView_remoteVideoFeed">
<VideoFeed ref={this._remote} onResize={this.props.onResize}
maxHeight={maxVideoHeight} />
</div>
<div className={localVideoFeedClasses}>
<VideoFeed ref={this._local} />
</div>
</div>
);
}
}

View file

@ -29,7 +29,6 @@ const RoomContext = createContext<IState>({
guestsCanJoin: false,
canPeek: false,
showApps: false,
isAlone: false,
isPeeking: false,
showingPinned: false,
showReadReceipts: true,

View file

@ -16,6 +16,7 @@ limitations under the License.
import { IMatrixClientCreds } from "../MatrixClientPeg";
import { Kind as SetupEncryptionKind } from "../toasts/SetupEncryptionToast";
import { ISecretStorageKeyInfo } from 'matrix-js-sdk/src/matrix';
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
function examineLoginResponse(
@ -44,6 +45,13 @@ function getSecretStorageKey(): Uint8Array {
return null;
}
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
function getDehydrationKey(
keyInfo: ISecretStorageKeyInfo,
): Promise<Uint8Array> {
return Promise.resolve(null);
}
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
function catchAccessSecretStorageError(e: Error): void {
// E.g. notify the user in some way
@ -74,6 +82,9 @@ export interface ISecurityCustomisations {
setupEncryptionNeeded?: (
kind: SetupEncryptionKind,
) => boolean,
getDehydrationKey?: (
keyInfo: ISecretStorageKeyInfo,
) => Promise<Uint8Array>,
}
// A real customisation module will define and export one or more of the

View file

@ -2322,5 +2322,30 @@
"End conference": "Прекрати конфетентният разговор",
"Call Declined": "Обаждането е отказано",
"The call could not be established": "Обаждането не може да бъде осъществено",
"The other party declined the call.": "Другата страна отказа обаждането."
"The other party declined the call.": "Другата страна отказа обаждането.",
"Answered Elsewhere": "Отговорено на друго място",
"%(senderDisplayName)s set the server ACLs for this room.": "%(senderDisplayName)s зададе ACLs на сървър за тази стая.",
"Incoming call": "Входящ разговор",
"Incoming video call": "Входящ видео разговор",
"Incoming voice call": "Входящ гласов разговор",
"Unknown caller": "Непознат абонат",
"Downloading logs": "Изтегляне на логове",
"Uploading logs": "Качване на логове",
"Enable experimental, compact IRC style layout": "Включи експериментално, компактно IRC оформление",
"Use a more compact Modern layout": "Използвай по-компактно 'Модерно' оформление",
"Enable advanced debugging for the room list": "Включи разрешен debugging за списъка със стаите",
"Offline encrypted messaging using dehydrated devices": "Офлайн шифровани съобщения чрез използването на дехидратирани устройства",
"Show message previews for reactions in all rooms": "Показвай преглед на реакциите за всички стаи",
"Show message previews for reactions in DMs": "Показвай преглед на реакциите за директни съобщения",
"New spinner design": "Нов дизайн на индикатора за активност",
"Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Протипи за Общности v2. Изисква сървър съвместим с това. Много експериментално - използвайте с повишено внимание.",
"Change notification settings": "Промяна на настройките за уведомление",
"%(senderName)s: %(stickerName)s": "%(senderName)s: %(stickerName)s",
"%(senderName)s: %(reaction)s": "%(senderName)s: %(reaction)s",
"Unexpected server error trying to leave the room": "Възникна неочаквана сървърна грешка при опит за напускане на стаята",
"(an error occurred)": "(възникна грешка)",
"🎉 All servers are banned from participating! This room can no longer be used.": "🎉 Всички сървъри за възбранени от участие! Тази стая вече не може да бъде използвана.",
"%(senderDisplayName)s changed the server ACLs for this room.": "%(senderDisplayName)s промени сървърните разрешения за контрол на достъпа до тази стая.",
"Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Добавя ( ͡° ͜ʖ ͡°) в началото на текстовото съобщение",
"The call was answered on another device.": "На обаждането беше отговорено от друго устройство."
}

View file

@ -809,13 +809,13 @@
"Terms and Conditions": "Geschäftsbedingungen",
"To continue using the %(homeserverDomain)s homeserver you must review and agree to our terms and conditions.": "Um den %(homeserverDomain)s -Heimserver weiter zu verwenden, musst du die Geschäftsbedingungen sichten und ihnen zustimmen.",
"Review terms and conditions": "Geschäftsbedingungen anzeigen",
"Share Link to User": "Sende Link an Benutzer",
"Share room": "Teile Raum",
"Share Room": "Teile Raum",
"Share Link to User": "Link zum Benutzer teilen",
"Share room": "Raum teilen",
"Share Room": "Raum teilen",
"Link to most recent message": "Link zur aktuellsten Nachricht",
"Share User": "Teile Benutzer",
"Share Community": "Teile Community",
"Share Room Message": "Teile Raumnachricht",
"Share Room Message": "Raumnachricht teilen",
"Link to selected message": "Link zur ausgewählten Nachricht",
"COPY": "KOPIEREN",
"Share Message": "Nachricht teilen",
@ -1218,7 +1218,7 @@
"Send %(eventType)s events": "Sende %(eventType)s-Ereignisse",
"Select the roles required to change various parts of the room": "Wähle Rollen die benötigt werden um einige Teile des Raumes zu ändern",
"Enable encryption?": "Verschlüsselung aktivieren?",
"Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly. <a>Learn more about encryption.</a>": "Sobald aktiviert, kann die Verschlüsselung für einen Raum nicht mehr deaktiviert werden. Nachrichten in einem verschlüsselten Raum können nur noch von Teilnehmern aber nicht mehr vom Server gelesen werden. Einige Bots und Brücken werden vielleicht nicht mehr funktionieren. <a>Lerne mehr über Verschlüsselung</a>",
"Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly. <a>Learn more about encryption.</a>": "Sobald aktiviert, kann die Verschlüsselung für einen Raum nicht mehr deaktiviert werden. Nachrichten in einem verschlüsselten Raum können nur noch von Teilnehmern aber nicht mehr vom Server gelesen werden. Einige Bots und Brücken werden vielleicht nicht mehr funktionieren. <a>Erfahre mehr über Verschlüsselung.</a>",
"Error updating main address": "Fehler beim Aktualisieren der Hauptadresse",
"There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.": "Es gab ein Problem beim Aktualisieren der Raum-Hauptadresse. Es kann sein, dass es vom Server verboten ist oder ein temporäres Problem auftrat.",
"Error updating flair": "Konnte Abzeichen nicht aktualisieren",
@ -1247,7 +1247,7 @@
"Sends the given message coloured as a rainbow": "Sendet die Nachricht in Regenbogenfarben",
"Adds a custom widget by URL to the room": "Fügt ein Benutzer-Widget über eine URL zum Raum hinzu",
"Please supply a https:// or http:// widget URL": "Bitte gib eine https:// oder http:// Widget-URL an",
"Sends the given emote coloured as a rainbow": "Sendet das Emoji in Regenbogenfarben",
"Sends the given emote coloured as a rainbow": "Zeigt Aktionen in Regenbogenfarben",
"%(senderName)s made no change.": "%(senderName)s hat keine Änderung vorgenommen.",
"%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.": "%(senderName)s hat die Einladung zum Raumbeitritt für %(targetDisplayName)s zurückgezogen.",
"Cannot reach homeserver": "Der Heimserver ist nicht erreichbar",
@ -1325,8 +1325,8 @@
"Deactivate account": "Benutzerkonto deaktivieren",
"Show previews/thumbnails for images": "Zeige Vorschauen/Thumbnails für Bilder",
"View": "Vorschau",
"Find a room…": "Suche einen Raum…",
"Find a room… (e.g. %(exampleRoom)s)": "Suche einen Raum… (z.B. %(exampleRoom)s)",
"Find a room…": "Einen Raum suchen…",
"Find a room… (e.g. %(exampleRoom)s)": "Einen Raum suchen… (z.B. %(exampleRoom)s)",
"If you can't find the room you're looking for, ask for an invite or <a>Create a new room</a>.": "Wenn du den gesuchten Raum nicht finden kannst, frage nach einer Einladung für den Raum oder <a>Erstelle einen neuen Raum</a>.",
"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.": "Alternativ kannst du versuchen, den öffentlichen Server unter <code>turn.matrix.org</code> zu verwenden. Allerdings wird dieser nicht so zuverlässig sein, und deine IP-Adresse mit diesem Server teilen. Du kannst dies auch in den Einstellungen konfigurieren.",
"This action requires accessing the default identity server <server /> to validate an email address or phone number, but the server does not have any terms of service.": "Diese Handlung erfordert es, auf den Standard-Identitätsserver <server /> zuzugreifen, um eine E-Mail Adresse oder Telefonnummer zu validieren, aber der Server hat keine Nutzungsbedingungen.",
@ -1528,7 +1528,7 @@
"Summary": "Zusammenfassung",
"Document": "Dokument",
"Explore": "Erkunde",
"Explore rooms": "Erkunde Räume",
"Explore rooms": "Räume erkunden",
"Maximize apps": "Apps maximieren",
"The signing key you provided matches the signing key you received from %(userId)s's session %(deviceId)s. Session marked as verified.": "Dein bereitgestellter Signaturschlüssel passt zu dem Schlüssel, der von %(userId)s's Sitzung %(deviceId)s empfangen wurde. Sitzung wird als verifiziert markiert.",
"Match system theme": "An System-Design anpassen",
@ -1756,7 +1756,7 @@
"Delete sessions|one": "Lösche Sitzung",
"Individually verify each session used by a user to mark it as trusted, not trusting cross-signed devices.": "Alle Sitzungen einzeln verifizieren, anstatt auch Sitzungen zu vertrauen, die durch Cross-Signing verifiziert sind.",
"Securely cache encrypted messages locally for them to appear in search results, using ": "Der Zwischenspeicher für die lokale Suche in verschlüsselten Nachrichten benötigt ",
" to store messages from ": " um Nachrichten zu speichern von ",
" to store messages from ": " um Nachrichten von ",
"%(brand)s is missing some components required for securely caching encrypted messages locally. If you'd like to experiment with this feature, build a custom %(brand)s Desktop with <nativeLink>search components added</nativeLink>.": "%(brand)s benötigt weitere Komponenten um verschlüsselte Nachrichten lokal zu durchsuchen. Wenn du diese Funktion testen möchtest kannst du dir deine eigene Version von %(brand)s Desktop mit der <nativeLink>integrierten Suchfunktion bauen</nativeLink>.",
"Backup has a <validity>valid</validity> signature from this user": "Die Sicherung hat eine <validity>gültige</validity> Signatur dieses Benutzers",
"Backup has a <validity>invalid</validity> signature from this user": "Die Sicherung hat eine <validity>ungültige</validity> Signatur dieses Benutzers",
@ -2098,7 +2098,7 @@
"Nice, strong password!": "Super, ein starkes Passwort!",
"Other users can invite you to rooms using your contact details": "Andere Benutzer können dich mit deinen Kontaktdaten in Räume einladen",
"Set an email for account recovery. Use email or phone to optionally be discoverable by existing contacts.": "Lege eine E-Mail für die Kontowiederherstellung fest. Verwende optional E-Mail oder Telefon, um von Anderen gefunden zu werden.",
"Explore Public Rooms": "Erkunde öffentliche Räume",
"Explore Public Rooms": "Öffentliche Räume erkunden",
"If you've joined lots of rooms, this might take a while": "Du bist einer Menge Räumen beigetreten, das kann eine Weile dauern",
"%(brand)s failed to get the protocol list from the homeserver. The homeserver may be too old to support third party networks.": "%(brand)s konnte die Protokollliste nicht vom Heimserver abrufen. Der Heimserver ist möglicherweise zu alt, um Netzwerke von Drittanbietern zu unterstützen.",
"No identity server is configured: add one in server settings to reset your password.": "Kein Identitätsserver konfiguriert: Füge einen in den Servereinstellungen hinzu, um dein Kennwort zurückzusetzen.",
@ -2413,16 +2413,16 @@
"Show message previews for reactions in all rooms": "Zeige eine Nachrichtenvorschau für Reaktionen in allen Räumen an",
"Uploading logs": "Protokolle werden hochgeladen",
"Downloading logs": "Protokolle werden heruntergeladen",
"Explore public rooms": "Erkunde öffentliche Räume",
"Explore public rooms": "Öffentliche Räume erkunden",
"Can't see what youre looking for?": "Kannst du nicht finden wonach du suchst?",
"Explore all public rooms": "Erkunde alle öffentlichen Räume",
"Explore all public rooms": "Alle öffentlichen Räume erkunden",
"%(count)s results|other": "%(count)s Ergebnisse",
"Preparing to download logs": "Bereite das Herunterladen der Protokolle vor",
"Download logs": "Protokolle herunterladen",
"Unexpected server error trying to leave the room": "Unerwarteter Server-Fehler beim Versuch den Raum zu verlassen",
"Error leaving room": "Fehler beim Verlassen des Raums",
"Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Communities v2 Prototyp. Benötigt einen kompatiblen Heimserver. Höchst experimentell - mit Vorsicht verwenden.",
"Explore rooms in %(communityName)s": "Erkunde Räume in %(communityName)s",
"Explore rooms in %(communityName)s": "Räume in %(communityName)s erkunden",
"Set up Secure Backup": "Schlüsselsicherung einrichten",
"Information": "Information",
"Add another email": "Weitere E-Mail-Adresse hinzufügen",
@ -2525,5 +2525,32 @@
"Starting camera...": "Starte Kamera...",
"Call connecting...": "Verbinde den Anruf...",
"Calling...": "Rufe an...",
"Starting microphone...": "Starte Mikrofon..."
"Starting microphone...": "Starte Mikrofon...",
"Move right": "Nach rechts schieben",
"Move left": "Nach links schieben",
"Revoke permissions": "Berechtigungen widerrufen",
"Unpin a widget to view it in this panel": "Widget nicht mehr anheften, um es in diesem Bereich anzuzeigen",
"You can only pin up to %(count)s widgets|other": "Du kannst nur bis zu %(count)s Widgets anheften",
"Show Widgets": "Widgets anzeigen",
"Hide Widgets": "Widgets verstecken",
"%(senderName)s declined the call.": "%(senderName)s hat den Anruf abgelehnt.",
"(an error occurred)": "(ein Fehler ist aufgetreten)",
"(their device couldn't start the camera / microphone)": "(ihr/sein Gerät konnte Kamera / Mikrophon nicht starten)",
"(connection failed)": "(Verbindung fehlgeschlagen)",
"🎉 All servers are banned from participating! This room can no longer be used.": "🎉 Alle Server sind von der Teilnahme ausgeschlossen! Dieser Raum kann nicht mehr genutzt werden.",
"%(senderDisplayName)s changed the server ACLs for this room.": "%(senderDisplayName)s hat die Server-ACLs für diesen Raum geändert.",
"%(senderDisplayName)s set the server ACLs for this room.": "%(senderDisplayName)s hat die Server-ACLs für diesen Raum gesetzt.",
"The call was answered on another device.": "Der Anruf wurde an einem anderen Gerät angenommen.",
"Answered Elsewhere": "Anderswo beantwortet",
"The call could not be established": "Der Anruf konnte nicht hergestellt werden",
"The other party declined the call.": "Die andere Seite hat den Anruf abgelehnt.",
"Call Declined": "Anruf abgelehnt",
"Data on this screen is shared with %(widgetDomain)s": "Daten auf diesem Bildschirm werden mit %(widgetDomain)s geteilt",
"Modal Widget": "Modales Widget",
"Offline encrypted messaging using dehydrated devices": "Offline verschlüsselte Kommunikation mit Hilfe von dehydrierten Geräten",
"Send feedback": "Feedback senden",
"Report a bug": "Einen Fehler melden",
"Add comment": "Kommentar hinzufügen",
"Rate %(brand)s": "%(brand)s bewerten",
"Feedback sent": "Feedback gesendet"
}

View file

@ -117,6 +117,255 @@
"Unable to enable Notifications": "Unable to enable Notifications",
"This email address was not found": "This email address was not found",
"Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Your email address does not appear to be associated with a Matrix ID on this Homeserver.",
"United Kingdom": "United Kingdom",
"United States": "United States",
"Afghanistan": "Afghanistan",
"Åland Islands": "Åland Islands",
"Albania": "Albania",
"Algeria": "Algeria",
"American Samoa": "American Samoa",
"Andorra": "Andorra",
"Angola": "Angola",
"Anguilla": "Anguilla",
"Antarctica": "Antarctica",
"Antigua & Barbuda": "Antigua & Barbuda",
"Argentina": "Argentina",
"Armenia": "Armenia",
"Aruba": "Aruba",
"Australia": "Australia",
"Austria": "Austria",
"Azerbaijan": "Azerbaijan",
"Bahamas": "Bahamas",
"Bahrain": "Bahrain",
"Bangladesh": "Bangladesh",
"Barbados": "Barbados",
"Belarus": "Belarus",
"Belgium": "Belgium",
"Belize": "Belize",
"Benin": "Benin",
"Bermuda": "Bermuda",
"Bhutan": "Bhutan",
"Bolivia": "Bolivia",
"Bosnia": "Bosnia",
"Botswana": "Botswana",
"Bouvet Island": "Bouvet Island",
"Brazil": "Brazil",
"British Indian Ocean Territory": "British Indian Ocean Territory",
"British Virgin Islands": "British Virgin Islands",
"Brunei": "Brunei",
"Bulgaria": "Bulgaria",
"Burkina Faso": "Burkina Faso",
"Burundi": "Burundi",
"Cambodia": "Cambodia",
"Cameroon": "Cameroon",
"Canada": "Canada",
"Cape Verde": "Cape Verde",
"Caribbean Netherlands": "Caribbean Netherlands",
"Cayman Islands": "Cayman Islands",
"Central African Republic": "Central African Republic",
"Chad": "Chad",
"Chile": "Chile",
"China": "China",
"Christmas Island": "Christmas Island",
"Cocos (Keeling) Islands": "Cocos (Keeling) Islands",
"Colombia": "Colombia",
"Comoros": "Comoros",
"Congo - Brazzaville": "Congo - Brazzaville",
"Congo - Kinshasa": "Congo - Kinshasa",
"Cook Islands": "Cook Islands",
"Costa Rica": "Costa Rica",
"Croatia": "Croatia",
"Cuba": "Cuba",
"Curaçao": "Curaçao",
"Cyprus": "Cyprus",
"Czech Republic": "Czech Republic",
"Côte dIvoire": "Côte dIvoire",
"Denmark": "Denmark",
"Djibouti": "Djibouti",
"Dominica": "Dominica",
"Dominican Republic": "Dominican Republic",
"Ecuador": "Ecuador",
"Egypt": "Egypt",
"El Salvador": "El Salvador",
"Equatorial Guinea": "Equatorial Guinea",
"Eritrea": "Eritrea",
"Estonia": "Estonia",
"Ethiopia": "Ethiopia",
"Falkland Islands": "Falkland Islands",
"Faroe Islands": "Faroe Islands",
"Fiji": "Fiji",
"Finland": "Finland",
"France": "France",
"French Guiana": "French Guiana",
"French Polynesia": "French Polynesia",
"French Southern Territories": "French Southern Territories",
"Gabon": "Gabon",
"Gambia": "Gambia",
"Georgia": "Georgia",
"Germany": "Germany",
"Ghana": "Ghana",
"Gibraltar": "Gibraltar",
"Greece": "Greece",
"Greenland": "Greenland",
"Grenada": "Grenada",
"Guadeloupe": "Guadeloupe",
"Guam": "Guam",
"Guatemala": "Guatemala",
"Guernsey": "Guernsey",
"Guinea": "Guinea",
"Guinea-Bissau": "Guinea-Bissau",
"Guyana": "Guyana",
"Haiti": "Haiti",
"Heard & McDonald Islands": "Heard & McDonald Islands",
"Honduras": "Honduras",
"Hong Kong": "Hong Kong",
"Hungary": "Hungary",
"Iceland": "Iceland",
"India": "India",
"Indonesia": "Indonesia",
"Iran": "Iran",
"Iraq": "Iraq",
"Ireland": "Ireland",
"Isle of Man": "Isle of Man",
"Israel": "Israel",
"Italy": "Italy",
"Jamaica": "Jamaica",
"Japan": "Japan",
"Jersey": "Jersey",
"Jordan": "Jordan",
"Kazakhstan": "Kazakhstan",
"Kenya": "Kenya",
"Kiribati": "Kiribati",
"Kosovo": "Kosovo",
"Kuwait": "Kuwait",
"Kyrgyzstan": "Kyrgyzstan",
"Laos": "Laos",
"Latvia": "Latvia",
"Lebanon": "Lebanon",
"Lesotho": "Lesotho",
"Liberia": "Liberia",
"Libya": "Libya",
"Liechtenstein": "Liechtenstein",
"Lithuania": "Lithuania",
"Luxembourg": "Luxembourg",
"Macau": "Macau",
"Macedonia": "Macedonia",
"Madagascar": "Madagascar",
"Malawi": "Malawi",
"Malaysia": "Malaysia",
"Maldives": "Maldives",
"Mali": "Mali",
"Malta": "Malta",
"Marshall Islands": "Marshall Islands",
"Martinique": "Martinique",
"Mauritania": "Mauritania",
"Mauritius": "Mauritius",
"Mayotte": "Mayotte",
"Mexico": "Mexico",
"Micronesia": "Micronesia",
"Moldova": "Moldova",
"Monaco": "Monaco",
"Mongolia": "Mongolia",
"Montenegro": "Montenegro",
"Montserrat": "Montserrat",
"Morocco": "Morocco",
"Mozambique": "Mozambique",
"Myanmar": "Myanmar",
"Namibia": "Namibia",
"Nauru": "Nauru",
"Nepal": "Nepal",
"Netherlands": "Netherlands",
"New Caledonia": "New Caledonia",
"New Zealand": "New Zealand",
"Nicaragua": "Nicaragua",
"Niger": "Niger",
"Nigeria": "Nigeria",
"Niue": "Niue",
"Norfolk Island": "Norfolk Island",
"North Korea": "North Korea",
"Northern Mariana Islands": "Northern Mariana Islands",
"Norway": "Norway",
"Oman": "Oman",
"Pakistan": "Pakistan",
"Palau": "Palau",
"Palestine": "Palestine",
"Panama": "Panama",
"Papua New Guinea": "Papua New Guinea",
"Paraguay": "Paraguay",
"Peru": "Peru",
"Philippines": "Philippines",
"Pitcairn Islands": "Pitcairn Islands",
"Poland": "Poland",
"Portugal": "Portugal",
"Puerto Rico": "Puerto Rico",
"Qatar": "Qatar",
"Romania": "Romania",
"Russia": "Russia",
"Rwanda": "Rwanda",
"Réunion": "Réunion",
"Samoa": "Samoa",
"San Marino": "San Marino",
"Saudi Arabia": "Saudi Arabia",
"Senegal": "Senegal",
"Serbia": "Serbia",
"Seychelles": "Seychelles",
"Sierra Leone": "Sierra Leone",
"Singapore": "Singapore",
"Sint Maarten": "Sint Maarten",
"Slovakia": "Slovakia",
"Slovenia": "Slovenia",
"Solomon Islands": "Solomon Islands",
"Somalia": "Somalia",
"South Africa": "South Africa",
"South Georgia & South Sandwich Islands": "South Georgia & South Sandwich Islands",
"South Korea": "South Korea",
"South Sudan": "South Sudan",
"Spain": "Spain",
"Sri Lanka": "Sri Lanka",
"St. Barthélemy": "St. Barthélemy",
"St. Helena": "St. Helena",
"St. Kitts & Nevis": "St. Kitts & Nevis",
"St. Lucia": "St. Lucia",
"St. Martin": "St. Martin",
"St. Pierre & Miquelon": "St. Pierre & Miquelon",
"St. Vincent & Grenadines": "St. Vincent & Grenadines",
"Sudan": "Sudan",
"Suriname": "Suriname",
"Svalbard & Jan Mayen": "Svalbard & Jan Mayen",
"Swaziland": "Swaziland",
"Sweden": "Sweden",
"Switzerland": "Switzerland",
"Syria": "Syria",
"São Tomé & Príncipe": "São Tomé & Príncipe",
"Taiwan": "Taiwan",
"Tajikistan": "Tajikistan",
"Tanzania": "Tanzania",
"Thailand": "Thailand",
"Timor-Leste": "Timor-Leste",
"Togo": "Togo",
"Tokelau": "Tokelau",
"Tonga": "Tonga",
"Trinidad & Tobago": "Trinidad & Tobago",
"Tunisia": "Tunisia",
"Turkey": "Turkey",
"Turkmenistan": "Turkmenistan",
"Turks & Caicos Islands": "Turks & Caicos Islands",
"Tuvalu": "Tuvalu",
"U.S. Virgin Islands": "U.S. Virgin Islands",
"Uganda": "Uganda",
"Ukraine": "Ukraine",
"United Arab Emirates": "United Arab Emirates",
"Uruguay": "Uruguay",
"Uzbekistan": "Uzbekistan",
"Vanuatu": "Vanuatu",
"Vatican City": "Vatican City",
"Venezuela": "Venezuela",
"Vietnam": "Vietnam",
"Wallis & Futuna": "Wallis & Futuna",
"Western Sahara": "Western Sahara",
"Yemen": "Yemen",
"Zambia": "Zambia",
"Zimbabwe": "Zimbabwe",
"Sign In or Create Account": "Sign In or Create Account",
"Use your account or create a new one to continue.": "Use your account or create a new one to continue.",
"Create Account": "Create Account",
@ -213,6 +462,8 @@
"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",
"Places the call in the current room on hold": "Places the call in the current room on hold",
"Takes the call in the current room off hold": "Takes the call in the current room off hold",
"Displays action": "Displays action",
"Reason": "Reason",
"%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s accepted the invitation for %(displayName)s.",
@ -403,10 +654,10 @@
"Verify all your sessions to ensure your account & messages are safe": "Verify all your sessions to ensure your account & messages are safe",
"Review": "Review",
"Later": "Later",
"Don't miss a reply": "Don't miss a reply",
"Notifications": "Notifications",
"You are not receiving desktop notifications": "You are not receiving desktop notifications",
"Enable them now": "Enable them now",
"Close": "Close",
"Enable desktop notifications": "Enable desktop notifications",
"Enable": "Enable",
"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>.": "Contact your <a>server admin</a>.",
@ -424,9 +675,8 @@
"What's new?": "What's new?",
"What's New": "What's New",
"Update": "Update",
"Restart": "Restart",
"Upgrade your %(brand)s": "Upgrade your %(brand)s",
"A new version of %(brand)s is available!": "A new version of %(brand)s is available!",
"Update %(brand)s": "Update %(brand)s",
"New version of %(brand)s is available": "New version of %(brand)s is available",
"Guest": "Guest",
"There was an error joining the room": "There was an error joining the room",
"Sorry, your homeserver is too old to participate in this room.": "Sorry, your homeserver is too old to participate in this room.",
@ -437,8 +687,8 @@
"You joined the call": "You joined the call",
"%(senderName)s joined the call": "%(senderName)s joined the call",
"Call in progress": "Call in progress",
"You left the call": "You left the call",
"%(senderName)s left the call": "%(senderName)s left the call",
"You ended the call": "You ended the call",
"%(senderName)s ended the call": "%(senderName)s ended the call",
"Call ended": "Call ended",
"You started a call": "You started a call",
"%(senderName)s started a call": "%(senderName)s started a call",
@ -529,6 +779,7 @@
"My Ban List": "My Ban List",
"This is your list of users/servers you have blocked - don't leave the room!": "This is your list of users/servers you have blocked - don't leave the room!",
"Active call": "Active call",
"Call Paused": "Call Paused",
"Unknown caller": "Unknown caller",
"Incoming voice call": "Incoming voice call",
"Incoming video call": "Incoming video call",
@ -684,12 +935,10 @@
"Failed to set display name": "Failed to set display name",
"Encryption": "Encryption",
"Individually verify each session used by a user to mark it as trusted, not trusting cross-signed devices.": "Individually verify each session used by a user to mark it as trusted, not trusting cross-signed devices.",
"Securely cache encrypted messages locally for them to appear in search results, using ": "Securely cache encrypted messages locally for them to appear in search results, using ",
" to store messages from ": " to store messages from ",
"rooms.": "rooms.",
"Securely cache encrypted messages locally for them to appear in search results, using %(size)s to store messages from %(count)s rooms.|other": "Securely cache encrypted messages locally for them to appear in search results, using %(size)s to store messages from %(count)s rooms.",
"Securely cache encrypted messages locally for them to appear in search results, using %(size)s to store messages from %(count)s rooms.|one": "Securely cache encrypted messages locally for them to appear in search results, using %(size)s to store messages from %(count)s room.",
"Manage": "Manage",
"Securely cache encrypted messages locally for them to appear in search results.": "Securely cache encrypted messages locally for them to appear in search results.",
"Enable": "Enable",
"%(brand)s is missing some components required for securely caching encrypted messages locally. If you'd like to experiment with this feature, build a custom %(brand)s Desktop with <nativeLink>search components added</nativeLink>.": "%(brand)s is missing some components required for securely caching encrypted messages locally. If you'd like to experiment with this feature, build a custom %(brand)s Desktop with <nativeLink>search components added</nativeLink>.",
"%(brand)s can't securely cache encrypted messages locally while running in a web browser. Use <desktopLink>%(brand)s Desktop</desktopLink> for encrypted messages to appear in search results.": "%(brand)s can't securely cache encrypted messages locally while running in a web browser. Use <desktopLink>%(brand)s Desktop</desktopLink> for encrypted messages to appear in search results.",
"Connecting to integration manager...": "Connecting to integration manager...",
@ -708,8 +957,8 @@
"Enable notifications for this account": "Enable notifications for this account",
"Clear notifications": "Clear notifications",
"All notifications are currently disabled for all targets.": "All notifications are currently disabled for all targets.",
"Add an email address to configure email notifications": "Add an email address to configure email notifications",
"Enable email notifications": "Enable email notifications",
"Add an email address to configure email notifications": "Add an email address to configure email notifications",
"Notifications on the following keywords follow rules which cant be displayed here:": "Notifications on the following keywords follow rules which cant be displayed here:",
"Unable to fetch notification target list": "Unable to fetch notification target list",
"Notification targets": "Notification targets",
@ -872,6 +1121,7 @@
"Ban list rules - %(roomName)s": "Ban list rules - %(roomName)s",
"Server rules": "Server rules",
"User rules": "User rules",
"Close": "Close",
"You have not ignored anyone.": "You have not ignored anyone.",
"You are currently ignoring:": "You are currently ignoring:",
"You are not subscribed to any lists": "You are not subscribed to any lists",
@ -1084,6 +1334,15 @@
"Strikethrough": "Strikethrough",
"Code block": "Code block",
"Quote": "Quote",
"Only the two of you are in this conversation, unless either of you invites anyone to join.": "Only the two of you are in this conversation, unless either of you invites anyone to join.",
"This is the beginning of your direct message history with <displayName/>.": "This is the beginning of your direct message history with <displayName/>.",
"Topic: %(topic)s (<a>edit</a>)": "Topic: %(topic)s (<a>edit</a>)",
"Topic: %(topic)s ": "Topic: %(topic)s ",
"<a>Add a topic</a> to help people know what it is about.": "<a>Add a topic</a> to help people know what it is about.",
"You created this room.": "You created this room.",
"%(displayName)s created this room.": "%(displayName)s created this room.",
"Add a photo, so people can easily spot your room.": "Add a photo, so people can easily spot your room.",
"This is the start of <roomName/>.": "This is the start of <roomName/>.",
"No pinned messages.": "No pinned messages.",
"Loading...": "Loading...",
"Pinned Messages": "Pinned Messages",
@ -1132,6 +1391,7 @@
"Custom Tag": "Custom Tag",
"Can't see what youre looking for?": "Can't see what youre looking for?",
"Explore all public rooms": "Explore all public rooms",
"Use the + to make a new room or explore existing ones below": "Use the + to make a new room or explore existing ones below",
"%(count)s results|other": "%(count)s results",
"%(count)s results|one": "%(count)s result",
"This room": "This room",
@ -1337,7 +1597,6 @@
"Remove this user from community?": "Remove this user from community?",
"Failed to withdraw invitation": "Failed to withdraw invitation",
"Failed to remove user from community": "Failed to remove user from community",
"<strong>%(role)s</strong> in %(roomName)s": "<strong>%(role)s</strong> in %(roomName)s",
"Failed to change power level": "Failed to change power level",
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.",
"Are you sure?": "Are you sure?",
@ -1345,6 +1604,7 @@
"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?": "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?",
"Deactivate user": "Deactivate user",
"Failed to deactivate user": "Failed to deactivate user",
"Role": "Role",
"This client does not support end-to-end encryption.": "This client does not support end-to-end encryption.",
"Security": "Security",
"The session you are trying to verify doesn't support scanning a QR code or emoji verification, which is what %(brand)s supports. Try with a different client.": "The session you are trying to verify doesn't support scanning a QR code or emoji verification, which is what %(brand)s supports. Try with a different client.",
@ -1381,8 +1641,9 @@
"Today": "Today",
"Yesterday": "Yesterday",
"View Source": "View Source",
"Messages here are end-to-end encrypted. Verify %(displayName)s in their profile - tap on their avatar.": "Messages here are end-to-end encrypted. Verify %(displayName)s in their profile - tap on their avatar.",
"Messages in this room are end-to-end encrypted. When people join, you can verify them in their profile, just tap on their avatar.": "Messages in this room are end-to-end encrypted. When people join, you can verify them in their profile, just tap on their avatar.",
"Encryption enabled": "Encryption enabled",
"Messages in this room are end-to-end encrypted. Learn more & verify this user in their user profile.": "Messages in this room are end-to-end encrypted. Learn more & verify this user in their user profile.",
"Ignored attempt to disable encryption": "Ignored attempt to disable encryption",
"Encryption not enabled": "Encryption not enabled",
"The encryption used by this room isn't supported.": "The encryption used by this room isn't supported.",
@ -1428,8 +1689,8 @@
"%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s changed the avatar for %(roomName)s",
"%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s removed the room avatar.",
"%(senderDisplayName)s changed the room avatar to <img/>": "%(senderDisplayName)s changed the room avatar to <img/>",
"This room is a continuation of another conversation.": "This room is a continuation of another conversation.",
"Click here to see older messages.": "Click here to see older messages.",
"This room is a continuation of another conversation.": "This room is a continuation of another conversation.",
"Copied!": "Copied!",
"Failed to copy": "Failed to copy",
"Add an Integration": "Add an Integration",
@ -1724,6 +1985,7 @@
"To continue, use Single Sign On to prove your identity.": "To continue, use Single Sign On to prove your identity.",
"Confirm to continue": "Confirm to continue",
"Click the button below to confirm your identity.": "Click the button below to confirm your identity.",
"Invite by email": "Invite by email",
"Failed to invite the following users to chat: %(csvUsers)s": "Failed to invite the following users to chat: %(csvUsers)s",
"We couldn't create your DM. Please check the users you want to invite and try again.": "We couldn't create your DM. Please check the users you want to invite and try again.",
"Something went wrong trying to invite the users.": "Something went wrong trying to invite the users.",
@ -1735,11 +1997,11 @@
"May include members not in %(communityName)s": "May include members not in %(communityName)s",
"Recently Direct Messaged": "Recently Direct Messaged",
"Direct Messages": "Direct Messages",
"Start a conversation with someone using their name, username (like <userId/>) or email address.": "Start a conversation with someone using their name, username (like <userId/>) or email address.",
"Start a conversation with someone using their name, email address or username (like <userId/>).": "Start a conversation with someone using their name, email address or username (like <userId/>).",
"Start a conversation with someone using their name or username (like <userId/>).": "Start a conversation with someone using their name or username (like <userId/>).",
"This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click <a>here</a>": "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click <a>here</a>",
"Go": "Go",
"Invite someone using their name, username (like <userId/>), email address or <a>share this room</a>.": "Invite someone using their name, username (like <userId/>), email address or <a>share this room</a>.",
"Invite someone using their name, email address, username (like <userId/>) or <a>share this room</a>.": "Invite someone using their name, email address, username (like <userId/>) or <a>share this room</a>.",
"Invite someone using their name, username (like <userId/>) or <a>share this room</a>.": "Invite someone using their name, username (like <userId/>) or <a>share this room</a>.",
"a new master key signature": "a new master key signature",
"a new cross-signing key signature": "a new cross-signing key signature",
@ -2053,6 +2315,10 @@
"Community %(groupId)s not found": "Community %(groupId)s not found",
"This homeserver does not support communities": "This homeserver does not support communities",
"Failed to load %(groupId)s": "Failed to load %(groupId)s",
"Great, that'll help people know it's you": "Great, that'll help people know it's you",
"Add a photo so people know it's you.": "Add a photo so people know it's you.",
"Welcome %(name)s": "Welcome %(name)s",
"Now, let's help you get started": "Now, let's help you get started",
"Welcome to %(appName)s": "Welcome to %(appName)s",
"Liberate your communication": "Liberate your communication",
"Send a Direct Message": "Send a Direct Message",
@ -2074,6 +2340,7 @@
"Data from an older version of %(brand)s has been detected. This will have caused end-to-end cryptography to malfunction in the older version. End-to-end encrypted messages exchanged recently whilst using the older version may not be decryptable in this version. This may also cause messages exchanged with this version to fail. If you experience problems, log out and back in again. To retain message history, export and re-import your keys.": "Data from an older version of %(brand)s has been detected. This will have caused end-to-end cryptography to malfunction in the older version. End-to-end encrypted messages exchanged recently whilst using the older version may not be decryptable in this version. This may also cause messages exchanged with this version to fail. If you experience problems, log out and back in again. To retain message history, export and re-import your keys.",
"Self-verification request": "Self-verification request",
"Logout": "Logout",
"%(creator)s created this DM.": "%(creator)s created this DM.",
"%(creator)s created and configured the room.": "%(creator)s created and configured the room.",
"Your Communities": "Your Communities",
"Did you know: you can use communities to filter your %(brand)s experience!": "Did you know: you can use communities to filter your %(brand)s experience!",
@ -2119,7 +2386,6 @@
"Starting microphone...": "Starting microphone...",
"Connectivity to the server has been lost.": "Connectivity to the server has been lost.",
"Sent messages will be stored until your connection has returned.": "Sent messages will be stored until your connection has returned.",
"There's no one else here! Would you like to <inviteText>invite others</inviteText> or <nowarnText>stop warning about the empty room</nowarnText>?": "There's no one else here! Would you like to <inviteText>invite others</inviteText> or <nowarnText>stop warning about the empty room</nowarnText>?",
"You seem to be uploading files, are you sure you want to quit?": "You seem to be uploading files, are you sure you want to quit?",
"You seem to be in a call, are you sure you want to quit?": "You seem to be in a call, are you sure you want to quit?",
"Search failed": "Search failed",

View file

@ -1,7 +1,7 @@
{
"This email address is already in use": "Tiu ĉi retpoŝtadreso jam estas uzata",
"This phone number is already in use": "Tiu ĉi telefonnumero jam estas uzata",
"Failed to verify email address: make sure you clicked the link in the email": "Kontrolo de via retpoŝtadreso malsukcesis: certigu, ke vi alklakis la ligilon en la retmesaĝo",
"Failed to verify email address: make sure you clicked the link in the email": "Kontrolo de via retpoŝtadreso malsukcesis: certigu, ke vi klakis la ligilon en la retmesaĝo",
"Call Timeout": "Voka tempolimo",
"The remote side failed to pick up": "La defora flanko malsukcesis respondi",
"Unable to capture screen": "Ne povas registri ekranon",
@ -278,7 +278,7 @@
"Only people who have been invited": "Nur invititaj uzantoj",
"Anyone who knows the room's link, apart from guests": "Iu ajn kun la ligilo, krom gastoj",
"Anyone who knows the room's link, including guests": "Iu ajn kun la ligilo, inkluzive gastojn",
"Publish this room to the public in %(domain)s's room directory?": "Ĉu publikigi ĉi tiun ĉambron al la publika listo de ĉambroj de %(domain)s?",
"Publish this room to the public in %(domain)s's room directory?": "Ĉu publikigi ĉi tiun ĉambron per la katalogo de ĉambroj de %(domain)s?",
"Who can read history?": "Kiu povas legi la historion?",
"Anyone": "Iu ajn",
"Members only (since the point in time of selecting this option)": "Nur ĉambranoj (ekde ĉi tiu elekto)",
@ -365,7 +365,7 @@
"%(severalUsers)sleft %(count)s times|other": "%(severalUsers)s%(count)s-foje foriris",
"%(severalUsers)sleft %(count)s times|one": "%(severalUsers)sforiris",
"%(oneUser)sleft %(count)s times|other": "%(oneUser)s%(count)s-foje foriris",
"%(oneUser)sleft %(count)s times|one": "%(oneUser)sforiris",
"%(oneUser)sleft %(count)s times|one": "%(oneUser)s foriris",
"%(severalUsers)sjoined and left %(count)s times|other": "%(severalUsers)s%(count)s-foje aliĝis kaj foriris",
"%(severalUsers)sjoined and left %(count)s times|one": "%(severalUsers)saliĝis kaj foriris",
"%(oneUser)sjoined and left %(count)s times|other": "%(oneUser)s%(count)s-foje aliĝis kaj foriris",
@ -412,7 +412,7 @@
"collapse": "maletendi",
"expand": "etendi",
"Custom level": "Propra nivelo",
"Room directory": "Listo de ĉambroj",
"Room directory": "Katalogo de ĉambroj",
"Username not available": "Uzantonomo ne disponeblas",
"Username invalid: %(errMessage)s": "Uzantonomo ne validas: %(errMessage)s",
"Username available": "Uzantonomo disponeblas",
@ -448,7 +448,7 @@
"Invalid Email Address": "Malvalida retpoŝtadreso",
"This doesn't appear to be a valid email address": "Tio ĉi ne ŝajnas esti valida retpoŝtadreso",
"Verification Pending": "Atendante kontrolon",
"Please check your email and click on the link it contains. Once this is done, click continue.": "Bonvolu kontroli vian retpoŝton, kaj alklaki la ligilon enhavatan en la sendita mesaĝo. Farinte tion, klaku je 'daŭrigi'.",
"Please check your email and click on the link it contains. Once this is done, click continue.": "Bonvolu kontroli vian retpoŝton, kaj klaki la ligilon enhavatan en la sendita mesaĝo. Farinte tion, klaku je «daŭrigi».",
"Unable to add email address": "Ne povas aldoni retpoŝtadreson",
"Unable to verify email address.": "Retpoŝtadreso ne kontroleblas.",
"This will allow you to reset your password and receive notifications.": "Tio ĉi permesos al vi restarigi vian pasvorton kaj ricevi sciigojn.",
@ -484,7 +484,7 @@
"Leave Community": "Forlasi komunumon",
"Leave %(groupName)s?": "Ĉu foriri el %(groupName)s?",
"Community Settings": "Komunumaj agordoj",
"These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.": "Tiuj ĉi ĉambroj montriĝas al komunumanoj sur la paĝo de la komunumo. Ili povas aliĝi al la ĉambroj per alklakoj.",
"These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.": "Ĉi tiuj ĉambroj montriĝas al komunumanoj sur la paĝo de la komunumo. Komunumanoj povas klaki ĉambrojn por aliĝi.",
"Featured Rooms:": "Elstarigitaj ĉambroj:",
"Featured Users:": "Elstarigitaj uzantoj:",
"%(inviter)s has invited you to join this community": "%(inviter)s invitis vin al tiu ĉi komunumo",
@ -640,7 +640,7 @@
"Friday": "Vendredo",
"Update": "Ĝisdatigi",
"What's New": "Kio novas",
"On": "Ek",
"On": "Jes",
"Changelog": "Protokolo de ŝanĝoj",
"Waiting for response from server": "Atendante respondon el la servilo",
"Send Custom Event": "Sendi propran okazon",
@ -649,14 +649,14 @@
"You cannot delete this image. (%(code)s)": "Vi ne povas forigi tiun ĉi bildon. (%(code)s)",
"Cancel Sending": "Nuligi sendon",
"This Room": "Ĉi tiu ĉambro",
"Noisy": "Brua",
"Noisy": "Brue",
"Room not found": "Ĉambro ne troviĝis",
"Messages containing my display name": "Mesaĝoj enhavantaj mian vidigan nomon",
"Messages in one-to-one chats": "Mesaĝoj en duopaj babiloj",
"Unavailable": "Nedisponebla",
"View Decrypted Source": "Vidi malĉifritan fonton",
"Failed to update keywords": "Malsukcesis ĝisdatigi la ŝlosilvortojn",
"remove %(name)s from the directory.": "forigi %(name)s de la ujo.",
"remove %(name)s from the directory.": "forigi %(name)s de la katalogo.",
"Notifications on the following keywords follow rules which cant be displayed here:": "La sciigoj de la jenaj ŝlosilvortoj sekvas regulojn kiuj ne povas esti montrataj ĉi tie:",
"Please set a password!": "Bonvolu agordi pasvorton!",
"You have successfully set a password!": "Vi sukcese agordis pasvorton!",
@ -677,7 +677,7 @@
"Enter keywords separated by a comma:": "Entajpu ŝlosilvortojn apartigitajn per komoj:",
"Search…": "Serĉi…",
"You have successfully set a password and an email address!": "Vi sukcese agordis pasvorton kaj retpoŝtadreson!",
"Remove %(name)s from the directory?": "Ĉu forigi %(name)s de la ujo?",
"Remove %(name)s from the directory?": "Ĉu forigi %(name)s de la katalogo?",
"%(brand)s uses many advanced browser features, some of which are not available or experimental in your current browser.": "%(brand)s uzas multajn specialajn funkciojn, el kiuj kelkaj ne disponeblas aŭ estas eksperimentaj en via nuna foliumilo.",
"Event sent!": "Okazo sendiĝis!",
"Explore Account Data": "Esplori kontajn datumojn",
@ -689,7 +689,7 @@
"Reject": "Rifuzi",
"Failed to set Direct Message status of room": "Malsukcesis agordi staton de rekteco al la ĉambro",
"Monday": "Lundo",
"Remove from Directory": "Forigi de ujo",
"Remove from Directory": "Forigi de katalogo",
"Enable them now": "Ŝalti ilin nun",
"Toolbox": "Ilaro",
"Collecting logs": "Kolektante protokolon",
@ -730,7 +730,7 @@
"Low Priority": "Malalta prioritato",
"Unable to fetch notification target list": "Ne povas akiri la liston de celoj por sciigoj",
"Set Password": "Agordi pasvorton",
"Off": "For",
"Off": "Ne",
"%(brand)s does not know how to join a room on this network": "%(brand)s ne scias aliĝi al ĉambroj en tiu ĉi reto",
"Mentions only": "Nur mencioj",
"Failed to remove tag %(tagName)s from room": "Malsukcesis forigi etikedon %(tagName)s el la ĉambro",
@ -771,7 +771,7 @@
"This homeserver has hit its Monthly Active User limit.": "Tiu ĉi hejmservilo atingis sian monatan limon de aktivaj uzantoj.",
"This homeserver has exceeded one of its resource limits.": "Tiu ĉi hejmservilo superis je unu el siaj risurcaj limoj.",
"Unable to connect to Homeserver. Retrying...": "Ne povas konektiĝi al hejmservilo. Reprovante…",
"You do not have permission to invite people to this room.": "Vi ne havas permeson inviti homojn al la ĉambro.",
"You do not have permission to invite people to this room.": "Vi ne havas permeson inviti personojn al la ĉambro.",
"User %(user_id)s does not exist": "Uzanto %(user_id)s ne ekzistas",
"Unknown server error": "Nekonata servila eraro",
"Use a few words, avoid common phrases": "Uzu malmultajn vortojn, kaj evitu oftajn frazojn",
@ -888,8 +888,8 @@
"Phone numbers": "Telefonnumeroj",
"Legal": "Jura",
"Credits": "Dankoj",
"For help with using %(brand)s, click <a>here</a>.": "Por helpo kun uzo de %(brand)s, alklaku <a>ĉi tie</a>.",
"For help with using %(brand)s, click <a>here</a> or start a chat with our bot using the button below.": "Por helpo kun uzo de %(brand)s, alklaku <a>ĉi tie</a> aŭ komencu babilon kun nia roboto uzante la butonon sube.",
"For help with using %(brand)s, click <a>here</a>.": "Por helpo pri uzado de %(brand)s, klaku <a>ĉi tien</a>.",
"For help with using %(brand)s, click <a>here</a> or start a chat with our bot using the button below.": "Por helpo pri uzado de %(brand)s, klaku <a>ĉi tien</a> aŭ komencu babilon kun nia roboto per la butono sube.",
"Chat with %(brand)s Bot": "Babilu kun la roboto %(brand)s Bot",
"Help & About": "Helpo kaj Prio",
"Bug reporting": "Cim-raportado",
@ -1159,7 +1159,7 @@
"Create a new room with the same name, description and avatar": "Kreos novan ĉàmbron kun la sama nomo, priskribo, kaj profilbildo",
"Update any local room aliases to point to the new room": "Religos ĉiujn lokajn kromnomojn al la nova ĉambro",
"Stop users from speaking in the old version of the room, and post a message advising users to move to the new room": "Haltigos parolojn al la malnova versio de al ĉambro, kaj konsilos uzantojn pri la nova per mesaĝo",
"Put a link back to the old room at the start of the new room so people can see old messages": "Metos en la novan ĉambron ligilon al la malnova, por ke homoj povu rigardi la malnovajn mesaĝojn",
"Put a link back to the old room at the start of the new room so people can see old messages": "Metos en la novan ĉambron ligilon al la malnova, por ke oni povu rigardi la malnovajn mesaĝojn",
"Sign out and remove encryption keys?": "Ĉu adiaŭi kaj forigi ĉifrajn ŝlosilojn?",
"Send Logs": "Sendi protokolon",
"A username can only contain lower case letters, numbers and '=_-./'": "Uzantonomo povas enhavi nur minusklojn, numerojn, kaj la signojn: =_-./",
@ -1342,7 +1342,7 @@
"To continue using the %(homeserverDomain)s homeserver you must review and agree to our terms and conditions.": "Por daŭre uzadi la hejmservilon %(homeserverDomain)s, vi devas tralegi kaj konsenti niajn uzokondiĉojn.",
"Review terms and conditions": "Tralegi uzokondiĉojn",
"Did you know: you can use communities to filter your %(brand)s experience!": "Ĉu vi sciis: vi povas uzi komunumojn por filtri vian sperton de %(brand)s!",
"To set up a filter, drag a community avatar over to the filter panel on the far left hand side of the screen. You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "Por agordi filtrilon, tiru komunuman profilbildon sur la filtran panelon je la maldekstra flanko. Vi povas klaki sur profilbildon en la filtra panelo iam ajn, por vidi nur ĉambrojn kaj homojn ligitaj al ties komunumo.",
"To set up a filter, drag a community avatar over to the filter panel on the far left hand side of the screen. You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "Por agordi filtrilon, tiru komunuman profilbildon sur la filtran panelon je la maldekstra flanko. Vi povas klaki profilbildon en la filtra panelo iam ajn, por vidi nur ĉambrojn kaj personojn ligitajn al ties komunumo.",
"%(brand)s failed to get the protocol list from the homeserver. The homeserver may be too old to support third party networks.": "%(brand)s malsukcesis akiri liston de protokoloj de la hejmservilo. Eble la hejmservilo estas tro malnova por subteni eksterajn retojn.",
"%(brand)s failed to get the public room list.": "%(brand)s malsukcesis akiri la liston de publikaj ĉambroj.",
"The homeserver may be unavailable or overloaded.": "La hejmservilo eble estas neatingebla aŭ troŝarĝita.",
@ -1407,7 +1407,7 @@
"Please ask the administrator of your homeserver (<code>%(homeserverDomain)s</code>) to configure a TURN server in order for calls to work reliably.": "Bonvolu peti la administranton de via hejmservilo (<code>%(homeserverDomain)s</code>) agordi TURN-servilon, por ke vokoj funkciu dependeble.",
"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.": "Alternative, vi povas prove uzi la publikan servilon je <code>turn.matrix.org</code>, sed tio ne estas same dependebla, kaj ĝi havigos vian IP-adreson al tiu servilo. Vi povas administri tion ankaŭ en Agordoj.",
"Try using turn.matrix.org": "Provu uzi servilon turn.matrix.org",
"Sends a message as plain text, without interpreting it as markdown": "Sendas mesaĝon kiel platan tekston, sen interpreto al MarkDown",
"Sends a message as plain text, without interpreting it as markdown": "Sendas mesaĝon kiel platan tekston, sen interpreto al Markdown",
"You do not have the required permissions to use this command.": "Vi ne havas sufiĉajn permesojn por uzi ĉi tiun komandon.",
"Changes the avatar of the current room": "Ŝanĝas la profilbildon de la nuna ĉambro",
"Use an identity server": "Uzi identigan servilon",
@ -1595,7 +1595,7 @@
"%(senderName)s placed a voice call. (not supported by this browser)": "%(senderName)s metis voĉvokon. (mankas subteno en ĉi tiu foliumilo)",
"%(senderName)s placed a video call.": "%(senderName)s metis vidvokon.",
"%(senderName)s placed a video call. (not supported by this browser)": "%(senderName)s metis vidvokon. (mankas subteno en ĉi tiu foliumilo)",
"Try out new ways to ignore people (experimental)": "Elprovi novajn manierojn malatenti homojn (eksperimente)",
"Try out new ways to ignore people (experimental)": "Elprovi novajn manierojn malatenti personojn (eksperimente)",
"Match system theme": "Similiĝi la sisteman haŭton",
"My Ban List": "Mia listo de forbaroj",
"This is your list of users/servers you have blocked - don't leave the room!": "Ĉi tio estas la listo de uzantoj/serviloj, kiujn vi blokis ne eliru el la ĉambro!",
@ -1675,7 +1675,7 @@
"Automatically invite users": "Memage inviti uzantojn",
"Upgrade private room": "Gradaltigi privatan ĉambron",
"Upgrade public room": "Gradaltigi publikan ĉambron",
"Notification settings": "Sciigaj agordoj",
"Notification settings": "Agordoj pri sciigoj",
"Take picture": "Foti",
"Start": "Komenci",
"Done": "Fini",
@ -1827,7 +1827,7 @@
"Server rules": "Servilaj reguloj",
"User rules": "Uzantulaj reguloj",
"You are currently ignoring:": "Vi nun malatentas:",
"Ignoring people is done through ban lists which contain rules for who to ban. Subscribing to a ban list means the users/servers blocked by that list will be hidden from you.": "Malatento de homoj okazas per listoj de forbaroj, kiuj enhavas regulojn pri tio, kiun forbari. Abono de listo de forbaroj signifas, ke la uzantoj/serviloj blokataj de la listo estos kaŝitaj de vi.",
"Ignoring people is done through ban lists which contain rules for who to ban. Subscribing to a ban list means the users/servers blocked by that list will be hidden from you.": "Malatentado de personoj okazas per listoj de forbaroj, kiuj enhavas regulojn pri tio, kiun forbari. Abonado de listo de forbaroj signifas, ke la uzantoj/serviloj blokataj de la listo estos kaŝitaj de vi.",
"Personal ban list": "Persona listo de forbaroj",
"Your personal ban list holds all the users/servers you personally don't want to see messages from. After ignoring your first user/server, a new room will show up in your room list named 'My Ban List' - stay in this room to keep the ban list in effect.": "Via persona listo de forbaroj tenas ĉiujn uzantojn/servilojn, kies mesaĝoj vi persone ne volas vidi. Post via unua malatento, nova ĉambro aperos en via listo de ĉambroj, nomita «Mia listo de forbaroj» restu en ĝi por efikigi la liston de forbaroj.",
"Server or user ID to ignore": "Malatentota servilo aŭ identigilo de uzanto",
@ -1838,7 +1838,7 @@
"Session key:": "Ŝlosilo de salutaĵo:",
"Message search": "Serĉado de mesaĝoj",
"Cross-signing": "Delegaj subskriboj",
"A session's public name is visible to people you communicate with": "Publika nomo de salutaĵo estas videbla al homoj, kun kiuj vi komunikas",
"A session's public name is visible to people you communicate with": "Publika nomo de salutaĵo estas videbla al personoj, kun kiuj vi komunikas",
"This room is bridging messages to the following platforms. <a>Learn more.</a>": "Ĉi tiu ĉambro transpontigas mesaĝojn al la jenaj platformoj. <a>Eksciu plion.</a>",
"This room isnt bridging messages to any platforms. <a>Learn more.</a>": "Ĉi tiu ĉambro transpontigas mesaĝojn al neniu platformo. <a>Eksciu plion.</a>",
"Bridges": "Pontoj",
@ -1939,7 +1939,7 @@
"Incorrect recovery passphrase": "Malĝusta rehava pasfrazo",
"Enter recovery passphrase": "Enigu la rehavan pasfrazon",
"Enter recovery key": "Enigu la rehavan ŝlosilon",
"<b>Warning</b>: You should only set up key backup from a trusted computer.": "<b>Averto</b>: savkopiadon de ŝlosiloj vi starigu nur el fidata komputilo.",
"<b>Warning</b>: You should only set up key backup from a trusted computer.": "<b>Averto</b>: savkopiadon de ŝlosiloj vi starigu nur per fidata komputilo.",
"If you've forgotten your recovery key you can <button>set up new recovery options</button>": "Se vi forgesis vian rehavan ŝlosilon, vi povas <button>reagordi rehavon</button>",
"Reload": "Relegi",
"Remove for everyone": "Forigi por ĉiuj",
@ -2025,7 +2025,7 @@
"Confirm by comparing the following with the User Settings in your other session:": "Konfirmu per komparo de la sekva kun la agardoj de uzanto en via alia salutaĵo:",
"Confirm this user's session by comparing the following with their User Settings:": "Konfirmu la salutaĵon de ĉi tiu uzanto per komparo de la sekva kun ĝiaj agordoj de uzanto:",
"If they don't match, the security of your communication may be compromised.": "Se ili ne akordas, la sekureco de via komunikado eble estas rompita.",
"Navigation": "Navigado",
"Navigation": "Navigacio",
"Calls": "Vokoj",
"Room List": "Listo de ĉambroj",
"Autocomplete": "Memkompletigo",
@ -2038,13 +2038,13 @@
"Toggle Italics": "Ŝalti kursivon",
"Toggle Quote": "Ŝalti citaĵon",
"New line": "Nova linio",
"Navigate recent messages to edit": "Navigi freŝajn mesaĝojn redaktotajn",
"Navigate recent messages to edit": "Trairi freŝajn mesaĝojn redaktotajn",
"Jump to start/end of the composer": "Salti al komenco/fino de la komponilo",
"Navigate composer history": "Navigi historion de la komponilo",
"Navigate composer history": "Trairi historion de la komponilo",
"Toggle microphone mute": "Baskuligi silentigon de mikrofono",
"Toggle video on/off": "Baskuligi filmojn",
"Jump to room search": "Salti al serĉo de ĉambroj",
"Navigate up/down in the room list": "Navigi supren/malsupren en la listo de ĉambroj",
"Navigate up/down in the room list": "Iri supren/malsupren en la listo de ĉambroj",
"Select room from the room list": "Elekti ĉambron el la listo de ĉambroj",
"Collapse room list section": "Maletendi parton kun listo de ĉambroj",
"Expand room list section": "Etendi parton kun listo de ĉambroj",
@ -2089,7 +2089,7 @@
"Review where youre logged in": "Kontrolu, kie vi salutis",
"New login. Was this you?": "Nova saluto. Ĉu tio estis vi?",
"%(name)s is requesting verification": "%(name)s petas kontrolon",
"Sends a message as html, without interpreting it as markdown": "Sendas mesaĝon kiel HTML, ne interpretante ĝin kiel MarkDown",
"Sends a message as html, without interpreting it as markdown": "Sendas mesaĝon kiel HTML, ne interpretante ĝin kiel Markdown",
"Failed to set topic": "Malsukcesis agordi temon",
"Command failed": "Komando malsukcesis",
"Could not find user in room": "Ne povis trovi uzanton en ĉambro",
@ -2214,7 +2214,7 @@
"Set a room address to easily share your room with other people.": "Agordu adreson de ĉambro por facile konigi la ĉambron al aliuloj.",
"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.": "Vi antaŭe uzis pli novan version de %(brand)s kun tiu ĉi salutaĵo. Por ree uzi ĉi tiun version kun tutvoja ĉifrado, vi devos adiaŭi kaj resaluti.",
"Address (optional)": "Adreso (malnepra)",
"Delete the room address %(alias)s and remove %(name)s from the directory?": "Ĉu forigi la adreson de ĉambro %(alias)s kaj forigi %(name)s de la listo?",
"Delete the room address %(alias)s and remove %(name)s from the directory?": "Ĉu forigi la adreson de ĉambro %(alias)s kaj forigi %(name)s de la katalogo?",
"delete the address.": "forigi la adreson.",
"Use a different passphrase?": "Ĉu uzi alian pasfrazon?",
"Help us improve %(brand)s": "Helpu al ni plibonigi %(brand)son",
@ -2317,7 +2317,7 @@
"Low priority rooms show up at the bottom of your room list in a dedicated section at the bottom of your room list": "Ĉambroj kun malalta prioritato montriĝas en aparta sekcio, en la suba parto de via ĉambrobreto,",
"The authenticity of this encrypted message can't be guaranteed on this device.": "La aŭtentikeco de ĉi tiu ĉifrita mesaĝo ne povas esti garantiita sur ĉi tiu aparato.",
"No recently visited rooms": "Neniuj freŝdate vizititaj ĉambroj",
"People": "Homoj",
"People": "Personoj",
"Unread rooms": "Nelegitaj ĉambroj",
"Always show first": "Ĉiam montri unuaj",
"Show": "Montri",
@ -2407,5 +2407,136 @@
"Recent changes that have not yet been received": "Freŝaj ŝanĝoj ankoraŭ ne ricevitaj",
"No files visible in this room": "Neniuj dosieroj videblas en ĉi tiu ĉambro",
"You have no visible notifications in this room.": "Vi havas neniujn videblajn sciigojn en ĉi tiu ĉambro.",
"%(brand)s Android": "%(brand)s por Android"
"%(brand)s Android": "%(brand)s por Android",
"Community and user menu": "Menuo de komunumo kaj uzanto",
"User settings": "Agordoj de uzanto",
"Community settings": "Agordoj de komunumo",
"Failed to find the general chat for this community": "Malsukcesis trovi la ĝeneralan babilejon por ĉi tiu komunumo",
"Starting camera...": "Pretigante filmilon…",
"Starting microphone...": "Pretigante mikrofonon…",
"Call connecting...": "Konektante vokon…",
"Calling...": "Vokante…",
"Explore rooms in %(communityName)s": "Esploru ĉambrojn en %(communityName)s",
"You do not have permission to create rooms in this community.": "Vi ne havas permeson krei ĉambrojn en ĉi tiu komunumo.",
"Cannot create rooms in this community": "Ne povas krei ĉambrojn en ĉi tiu komunumo",
"Create community": "Krei komunumon",
"Attach files from chat or just drag and drop them anywhere in a room.": "Kunsendu dosierojn per la babilujo, aŭ trenu ilin kien ajn en ĉambro vi volas.",
"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>.": "Enigu la lokon de via hejmservilo de «Element Matrix Services». Ĝi povas uzi vian propran retnomon aŭ esti subretnomo de <a>element.io</a>.",
"Move right": "Movi dekstren",
"Move left": "Movi maldekstren",
"Revoke permissions": "Nuligi permesojn",
"Take a picture": "Foti",
"Unable to set up keys": "Ne povas agordi ŝlosilojn",
"You're all caught up.": "Sen sciigoj.",
"Invite someone using their name, username (like <userId/>) or <a>share this room</a>.": "Invitu iun per ĝia nomo, uzantonomo (kiel <userId/>), aŭ <a>diskonigu la ĉambron</a>.",
"This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click <a>here</a>": "Ĉi tio ne invitos ĝin al %(communityName)s. Por inviti iun al %(communityName)s, klaku <a>ĉi tien</a>",
"Start a conversation with someone using their name or username (like <userId/>).": "Komencu interparolon kun iu per ĝia nomo aŭ uzantonomo (kiel <userId/>).",
"May include members not in %(communityName)s": "Povas inkluzivi anojn ekster %(communityName)s",
"Update community": "Ĝisdatigi komunumon",
"There was an error updating your community. The server is unable to process your request.": "Eraris ĝisdatigo de via komunumo. La servilo ne povas trakti vian peton.",
"Block anyone not part of %(serverName)s from ever joining this room.": "Bloki de la ĉambro ĉiun ekster %(serverName)s.",
"Create a room in %(communityName)s": "Krei ĉambron en %(communityName)s",
"You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Vi povas malŝalti ĉi tion se la ĉambro estos uzata por kunlaborado kun eksteraj skipoj, kun iliaj propraj hejmserviloj. Ĝi ne povas ŝanĝiĝi poste.",
"You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "Vi povus ŝalti ĉi tion se la ĉambro estus uzota nur por kunlaborado de internaj skipoj je via hejmservilo. Ĝi ne ŝanĝeblas poste.",
"Your server requires encryption to be enabled in private rooms.": "Via servilo postulas ŝaltitan ĉifradon en privataj ĉambroj.",
"Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Privataj ĉambroj povas esti trovitaj kaj aliĝitaj nur per invito. Publikaj ĉambroj povas esti trovitaj kaj aliĝitaj de iu ajn en ĉi tiu komunumo.",
"Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.": "Privataj ĉambroj povas esti trovitaj kaj aliĝitaj nur per invito. Publikaj ĉambroj povas esti trovitaj kaj aliĝitaj de iu ajn.",
"An image will help people identify your community.": "Bildo helpos al aliuloj rekoni vian komunumon.",
"Add image (optional)": "Aldonu bildon (se vi volas)",
"Enter name": "Enigu nomon",
"What's the name of your community or team?": "Kio estas la nomo de via komunumo aŭ skipo?",
"You can change this later if needed.": "Vi povas ŝanĝi ĉi tion poste, laŭbezone.",
"Use this when referencing your community to others. The community ID cannot be changed.": "Uzu ĉi tion kiam vi montras vian komunumon al aliuloj. La identigilo ne povas ŝanĝiĝi.",
"Community ID: +<localpart />:%(domain)s": "Identigilo de komunumo: +<localpart />:%(domain)s",
"There was an error creating your community. The name may be taken or the server is unable to process your request.": "Eraris kreado de via komunumo. Eble la nomo jam estas prenita, aŭ la servilo ne povas trakti vian peton.",
"Invite people to join %(communityName)s": "Inviti personojn al komunumo %(communityName)s",
"Send %(count)s invites|one": "Sendi %(count)s inviton",
"Send %(count)s invites|other": "Sendi %(count)s invitojn",
"People you know on %(brand)s": "Personoj, kiujn vi scias je %(brand)s",
"Add another email": "Aldoni alian retpoŝtadreson",
"Preparing to download logs": "Preparante elŝuton de protokolo",
"Download logs": "Elŝuti protokolon",
"Information": "Informoj",
"This version of %(brand)s does not support searching encrypted messages": "Ĉi tiu versio de %(brand)s ne subtenas serĉadon de ĉifritaj mesaĝoj",
"This version of %(brand)s does not support viewing some encrypted files": "Ĉi tiu versio de %(brand)s ne subtenas montradon de iuj ĉifritaj dosieroj",
"Use the <a>Desktop app</a> to search encrypted messages": "Uzu la <a>labortablan aplikaĵon</a> por serĉi ĉifritajn mesaĝojn",
"Use the <a>Desktop app</a> to see all encrypted files": "Uzu la <a>labortablan aplikaĵon</a> por vidi ĉiujn ĉifritajn dosierojn",
"Video conference started by %(senderName)s": "Grupan vidvokon komencis %(senderName)s",
"Video conference updated by %(senderName)s": "Grupan vidvokon ĝisdatigis %(senderName)s",
"Video conference ended by %(senderName)s": "Grupan vidvokon finis %(senderName)s",
"Join the conference from the room information card on the right": "Aliĝu al la grupa voko per la dekstraflanka karto kun informoj pri ĉambro",
"Join the conference at the top of this room": "Aliĝu al la grupa voko supre je la ĉambro",
"Ignored attempt to disable encryption": "Malatentis provon malŝalti ĉifradon",
"Room settings": "Agordoj de ĉambro",
"Show files": "Montri dosierojn",
"%(count)s people|one": "%(count)s persono",
"%(count)s people|other": "%(count)s personoj",
"About": "Prio",
"Not encrypted": "Neĉifrita",
"Add widgets, bridges & bots": "Aldonu fenestraĵojn, pontojn, kaj robotojn",
"Edit widgets, bridges & bots": "Redakti fenestraĵojn, pontojn, kaj robotojn",
"Widgets": "Fenestraĵoj",
"Unpin a widget to view it in this panel": "Malfiksu fenestraĵon por vidi ĝin sur ĉi tiu breto",
"Unpin": "Malfiksi",
"You can only pin up to %(count)s widgets|other": "Vi povas fiksi maksimume %(count)s fenestraĵojn",
"Room Info": "Informoj pri ĉambro",
"%(count)s results|one": "%(count)s rezulto",
"%(count)s results|other": "%(count)s rezultoj",
"Explore all public rooms": "Esplori ĉiujn publiajn ĉambrojn",
"Can't see what youre looking for?": "Ĉu vi ne sukcesas trovi la serĉaton?",
"Explore public rooms": "Esplori publikajn ĉambrojn",
"Explore community rooms": "Esplori komunumajn ĉambrojn",
"Show Widgets": "Montri fenestraĵojn",
"Hide Widgets": "Kaŝi fenestraĵojn",
"Remove messages sent by others": "Forigi mesaĝojn senditajn de aliaj",
"Privacy": "Privateco",
"Secure Backup": "Sekura savkopiado",
"not ready": "neprete",
"ready": "prete",
"Secret storage:": "Sekreta deponejo:",
"Backup key cached:": "Kaŝmemorita Savkopia ŝlosilo:",
"Backup key stored:": "Deponita Savkopia ŝlosilo:",
"Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.": "Savkopiu viajn ĉifrajn ŝlosilojn kun la datumoj de via konto, pro eblo ke vi perdus aliron al viaj salutaĵoj. Viaj ŝlosiloj estos sekurigitaj per unika Rehava ŝlosilo.",
"Algorithm:": "Algoritmo:",
"Backup version:": "Repaŝa versio:",
"The operation could not be completed": "La ago ne povis finiĝi",
"Failed to save your profile": "Malsukcesis konservi vian profilon",
"Master private key:": "Ĉefa privata ŝlosilo:",
"not found in storage": "netrovite en deponejo",
"Cross-signing is not set up.": "Delegaj subskriboj ne estas agorditaj.",
"Cross-signing is ready for use.": "Delegaj subskriboj estas pretaj por uzado.",
"Downloading logs": "Elŝutante protokolon",
"Uploading logs": "Alŝutante protokolon",
"Safeguard against losing access to encrypted messages & data": "Malhelpu perdon de aliro al ĉifritaj mesaĝoj kaj datumoj",
"Set up Secure Backup": "Agordi Sekuran savkopiadon",
"Unknown App": "Nekonata aplikaĵo",
"Error leaving room": "Eraro dum foriro de la ĉambro",
"Unexpected server error trying to leave the room": "Neatendita servila eraro dum foriro de ĉambro",
"%(senderName)s declined the call.": "%(senderName)s rifuzis la vokon.",
"(an error occurred)": "(okazis eraro)",
"(their device couldn't start the camera / microphone)": "(ĝia aparato ne povis funkciigi la filmilon / mikrofonon)",
"(connection failed)": "(konekto malsukcesis)",
"🎉 All servers are banned from participating! This room can no longer be used.": "🎉 Ĉiuj serviloj estas forbaritaj de partoprenado! La ĉambro ne plu povas esti uzata.",
"Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Antaŭmetas ( ͡° ͜ʖ ͡°) al platteksta mesaĝo",
"This will end the conference for everyone. Continue?": "Ĉi tio finos la grupan vokon por ĉiuj. Ĉu daŭrigi?",
"End conference": "Fini grupan vokon",
"The call was answered on another device.": "La voko estis respondita per alia aparato.",
"Answered Elsewhere": "Respondita aliloke",
"The call could not be established": "Ne povis meti la vokon",
"The other party declined the call.": "La alia persono rifuzis la vokon.",
"Call Declined": "Voko rifuziĝis",
"Rate %(brand)s": "Taksu pri %(brand)s",
"Feedback sent": "Prikomentoj sendiĝis",
"Youre all caught up": "Vi nenion preterpasis",
"Data on this screen is shared with %(widgetDomain)s": "Datumoj sur tiu ĉi ekrano estas havigataj al %(widgetDomain)s",
"Send feedback": "Prikomenti",
"PRO TIP: If you start a bug, please submit <debugLogsLink>debug logs</debugLogsLink> to help us track down the problem.": "KONSILO: Kiam vi raportas eraron, bonvolu kunsendi <debugLogsLink>erarserĉan protokolon</debugLogsLink>, por ke ni povu pli facile trovi la problemon.",
"Please view <existingIssuesLink>existing bugs on Github</existingIssuesLink> first. No match? <newIssueLink>Start a new one</newIssueLink>.": "Bonvolu unue vidi <existingIssuesLink>jamajn erarojn en GitHub</existingIssuesLink>. Ĉu neniu akordas la vian? <newIssueLink>Raportu novan</newIssueLink>.",
"Report a bug": "Raporti eraron",
"There are two ways you can provide feedback and help us improve %(brand)s.": "Ekzistas du manieroj, kiel vi povas prikomenti kaj helpi nin plibonigi la programon %(brand)s.",
"Comment": "Komento",
"Add comment": "Aldoni komenton",
"Please go into as much detail as you like, so we can track down the problem.": "Bonvolu detaligi tiel multe, kiel vi volas, por ke ni povu pli facile trovi la problemon.",
"Tell us below how you feel about %(brand)s so far.": "Diru al ni, kion vi ĝis nun pensas pri %(brand)s.",
"Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Pratipoj de la 2-a versio de komunumoj. Bezonas konforman hejmservilon. Tre eksperimenta uzu nur zorge."
}

View file

@ -45,7 +45,7 @@
"Failed to add the following rooms to %(groupId)s:": "Järgnevate jututubade lisamine %(groupId)s kogukonda ebaõnnestus:",
"Changes your avatar in all rooms": "Muuda oma tunnuspilti kõikides jututubades",
"Ask this user to verify their session, or manually verify it below.": "Palu nimetatud kasutajal verifitseerida see sessioon või tee seda alljärgnevas käsitsi.",
"Manually Verify by Text": "Verifitseeri käsitsi etteantud teksti abil",
"Manually Verify by Text": "Verifitseeri käsitsi etteantud teksti abil",
"Interactively verify by Emoji": "Verifitseeri interaktiivselt Emoji abil",
"Enable big emoji in chat": "Kasuta vestlustes suuri emoji'sid",
"Order rooms by name": "Järjesta jututoad nime alusel",
@ -306,7 +306,7 @@
"Use your account or create a new one to continue.": "Jätkamaks kasuta oma kontot või loo uus konto.",
"Anyone": "Kõik kasutajad",
"Encryption": "Krüptimine",
"Once enabled, encryption cannot be disabled.": "Kui krüptimine on juba kasutusele võetud, siis ei saa seda enam eemaldada.",
"Once enabled, encryption cannot be disabled.": "Kui krüptimine on juba kasutusele võetud, siis ei saa seda enam eemaldada.",
"Encrypted": "Krüptitud",
"Who can access this room?": "Kes pääsevad ligi siia jututuppa?",
"Who can read history?": "Kes võivad lugeda ajalugu?",
@ -443,7 +443,7 @@
"Sat": "Laupäev",
"%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(time)s",
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(day)s %(monthName)s %(fullYear)s",
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(day)s %(monthName)s %(fullYear)s %(time)s",
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(day)s %(monthName)s %(fullYear)s %(time)s",
"Encryption upgrade available": "Krüptimise uuendus on saadaval",
"Set up encryption": "Seadista krüptimine",
"Review where youre logged in": "Vaata üle, kust sa oled Matrix'i võrku loginud",
@ -806,7 +806,7 @@
"Cancelling…": "Tühistan…",
"They match": "Nad klapivad",
"They don't match": "Nad ei klapi",
"To be secure, do this in person or use a trusted way to communicate.": "Turvalisuse mõttes on oluline, et teed seda nii, et kas olete üheskoos või kasutate suhtluskanalit, mida mõlemad usaldate.",
"To be secure, do this in person or use a trusted way to communicate.": "Turvalisuse mõttes on oluline, et teed seda nii, et kas olete üheskoos või kasutate suhtluskanalit, mida mõlemad usaldate.",
"Dog": "Koer",
"Cat": "Kass",
"Lion": "Lõvi",
@ -942,7 +942,7 @@
"This homeserver doesn't offer any login flows which are supported by this client.": "See koduserver ei paku ühtegi sisselogimislahendust, mida see klient toetab.",
"Error: Problem communicating with the given homeserver.": "Viga: Suhtlusel koduserveriga tekkis probleem.",
"Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or <a>enable unsafe scripts</a>.": "Kui aadressiribal on HTTPS-aadress, siis HTTP-protokolli kasutades ei saa ühendust koduserveriga. Palun pruugi HTTPS-protokolli või <a>luba brauseris ebaturvaliste skriptide kasutamine</a>.",
"Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.": "Ei sa ühendust koduserveriga. Palun kontrolli, et sinu <a>koduserveri SSL sertifikaat</a> oleks usaldusväärne ning mõni brauseri lisamoodul ei blokeeri päringuid.",
"Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.": "Ei sa ühendust koduserveriga. Palun kontrolli, et sinu <a>koduserveri SSL sertifikaat</a> oleks usaldusväärne ning mõni brauseri lisamoodul ei blokeeri päringuid.",
"Syncing...": "Sünkroniseerin...",
"Signing In...": "Login sisse...",
"Create account": "Loo kasutajakonto",
@ -999,7 +999,7 @@
"Leave %(groupName)s?": "Lahku %(groupName)s kogukonnast?",
"Unable to leave community": "Kogukonnast lahkumine ei õnnestunud",
"Community Settings": "Kogukonna seadistused",
"Want more than a community? <a>Get your own server</a>": "Soovid enamat kui kogukond? <a>Pane üles oma server</a>",
"Want more than a community? <a>Get your own server</a>": "Soovid enamat kui kogukond? <a>Pane üles oma server</a>",
"Changes made to your community <bold1>name</bold1> and <bold2>avatar</bold2> might not be seen by other users for up to 30 minutes.": "Oma kogukonnas tehtud <bold1>nime</bold1> ja <bold2>avatari</bold2> muudatused ei pruugi teistele näha olla kuni 30 minuti jooksul.",
"Featured Users:": "Esiletõstetud kasutajad:",
"%(inviter)s has invited you to join this community": "%(inviter)s on kutsunud sind kogukonna liikmeks",
@ -1177,7 +1177,7 @@
"Recently Direct Messaged": "Viimased otsesõnumite saajad",
"Invite someone using their name, username (like <userId/>), email address or <a>share this room</a>.": "Kutsu kedagi tema nime, kasutajanime (nagu <userId/>), e-posti aadressi alusel või <a>jaga seda jututuba</a>.",
"Upload completed": "Üleslaadimine valmis",
"%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "%(brand)s kasutab varasemaga võrreldes 3-5 korda vähem mälu, sest laeb teavet kasutajate kohta vaid siis, kui vaja. Palun oota hetke, kuni sünkroniseerime andmeid serveriga!",
"%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "%(brand)s kasutab varasemaga võrreldes 3-5 korda vähem mälu, sest laeb teavet kasutajate kohta vaid siis, kui vaja. Palun oota hetke, kuni sünkroniseerime andmeid serveriga!",
"Updating %(brand)s": "Uuendan rakendust %(brand)s",
"I don't want my encrypted messages": "Ma ei soovi oma krüptitud sõnumeid",
"Manually export keys": "Ekspordi võtmed käsitsi",
@ -1268,7 +1268,7 @@
"Fill screen": "Täida ekraan",
"No identity server is configured: add one in server settings to reset your password.": "Ühtegi isikutuvastusserverit pole seadistatud: salasõna taastamiseks määra see serveri sätetes.",
"Sign in instead": "Pigem logi sisse",
"A verification email will be sent to your inbox to confirm setting your new password.": "Kontrollimaks, et just sina sina ise sisestasid uue salasõna, siis saadame sulle kinnituskirja.",
"A verification email will be sent to your inbox to confirm setting your new password.": "Kontrollimaks, et just sina ise sisestasid uue salasõna, saadame sulle kinnituskirja.",
"Send Reset Email": "Saada salasõna taastamise e-kiri",
"An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.": "Saatsime kirja %(emailAddress)s aadressile. Kui sa oled klõpsinud kirjas olnud linki, siis jätka alljärgnevaga.",
"I have verified my email address": "Ma olen teinud läbi oma e-posti aadressi kontrolli",
@ -1876,7 +1876,7 @@
"⚠ These settings are meant for advanced users.": "⚠ Need seadistused on mõeldud kogenud kasutajatele.",
"Ignoring people is done through ban lists which contain rules for who to ban. Subscribing to a ban list means the users/servers blocked by that list will be hidden from you.": "Kasutajate eiramine toimub ligipääsukeelu reeglite loendite alusel ning seal on kirjas blokeeritavad kasutajad, jututoad või serverid. Sellise loendi kasutusele võtmine tähendab et blokeeritud kasutajad või serverid ei ole sulle nähtavad.",
"Personal ban list": "Minu isiklik ligipääsukeelu reeglite loend",
"Your personal ban list holds all the users/servers you personally don't want to see messages from. After ignoring your first user/server, a new room will show up in your room list named 'My Ban List' - stay in this room to keep the ban list in effect.": "Sinu isiklikus ligipääsukeelu reeglite loendis on kasutajad ja serverid, kellelt sa ei soovi sõnumeid saada. Peale esimese kasutaja või serveri blokeerimist tekib sinu jututubade loendisse uus jututuba „Minu isiklik ligipääsukeelu reeglite loend“ ning selle jõustamiseks ära logi nimetatud jututoast välja.",
"Your personal ban list holds all the users/servers you personally don't want to see messages from. After ignoring your first user/server, a new room will show up in your room list named 'My Ban List' - stay in this room to keep the ban list in effect.": "Sinu isiklikus ligipääsukeelu reeglite loendis on kasutajad ja serverid, kellelt sa ei soovi sõnumeid saada. Peale esimese kasutaja või serveri blokeerimist tekib sinu jututubade loendisse uus jututuba „Minu isiklik ligipääsukeelu reeglite loend“ ning selle jõustamiseks ära logi nimetatud jututoast välja.",
"Subscribing to a ban list will cause you to join it!": "Ligipääsukeelu reeglite loendi tellimine tähendab sellega liitumist!",
"Room ID or address of ban list": "Ligipääsukeelu reeglite loendi jututoa tunnus või aadress",
"Show tray icon and minimize window to it on close": "Näita süsteemisalve ikooni ja Element'i akna sulgemisel minimeeri ta salve",
@ -2239,7 +2239,7 @@
"Away": "Eemal",
"Room avatar": "Jututoa tunnuspilt ehk avatar",
"Publish this room to the public in %(domain)s's room directory?": "Kas avaldame selle jututoa %(domain)s jututubade loendis?",
"When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.": "Kui keegi lisab oma sõnumisse URL'i, siis võidakse näidata selle URL'i eelvaadet, mis annab lisateavet tema kohta, nagu näiteks pealkiri, kirjeldus ja kuidas ta välja näeb.",
"When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.": "Kui keegi lisab oma sõnumisse URL'i, siis võidakse näidata selle URL'i eelvaadet, mis annab lisateavet tema kohta, nagu näiteks pealkiri, kirjeldus ja kuidas ta välja näeb.",
"Waiting for you to accept on your other session…": "Ootan, et sa nõustuksid teises sessioonis…",
"Waiting for %(displayName)s to accept…": "Ootan, et %(displayName)s nõustuks…",
"Accepting…": "Nõustun …",
@ -2432,7 +2432,7 @@
"Send %(count)s invites|other": "Saada %(count)s kutset",
"Send %(count)s invites|one": "Saada %(count)s kutse",
"Invite people to join %(communityName)s": "Kutsu kasutajaid %(communityName)s kogukonna liikmeks",
"There was an error creating your community. The name may be taken or the server is unable to process your request.": "Kogukonna loomisel tekkis viga. Selline nimi kas on juba kasutusel või server ei saa hetkel seda päringut töödelda.",
"There was an error creating your community. The name may be taken or the server is unable to process your request.": "Kogukonna loomisel tekkis viga. Selline nimi kas on juba kasutusel või server ei saa hetkel seda päringut töödelda.",
"Community ID: +<localpart />:%(domain)s": "Kogukonna tunnus: +<localpart />:%(domain)s",
"Use this when referencing your community to others. The community ID cannot be changed.": "Viidates kogukonnale kasuta seda tunnust. Kogukonna tunnust ei ole võimalik muuta.",
"You can change this later if needed.": "Kui vaja, siis sa saad seda hiljem muuta.",
@ -2538,5 +2538,29 @@
"The call could not be established": "Kõnet ei saa korraldada",
"%(senderName)s declined the call.": "%(senderName)s ei võtnud kõnet vastu.",
"The other party declined the call.": "Teine osapool ei võtnud kõnet vastu.",
"Call Declined": "Kõne on tagasilükatud"
"Call Declined": "Kõne on tagasilükatud",
"Move right": "Liigu paremale",
"Move left": "Liigu vasakule",
"Revoke permissions": "Tühista õigused",
"Unpin a widget to view it in this panel": "Sellel paneelil kuvamiseks eemaldage vidin lemmikutest",
"You can only pin up to %(count)s widgets|other": "Sa saad kinnitada kuni %(count)s vidinat",
"Show Widgets": "Näita vidinaid",
"Hide Widgets": "Peida vidinad",
"The call was answered on another device.": "Kõnele vastati teises seadmes.",
"Answered Elsewhere": "Vastatud mujal",
"Data on this screen is shared with %(widgetDomain)s": "Andmeid selles vaates jagatakse %(widgetDomain)s serveriga",
"Modal Widget": "Modaalne vidin",
"PRO TIP: If you start a bug, please submit <debugLogsLink>debug logs</debugLogsLink> to help us track down the problem.": "SOOVITUS: Kui sa koostad uut veateadet, siis meil on lihtsam vea põhjuseni leida, kui sa lisad juurde ka <debugLogsLink>silumislogid</debugLogsLink>.",
"Send feedback": "Saada tagasiside",
"Please view <existingIssuesLink>existing bugs on Github</existingIssuesLink> first. No match? <newIssueLink>Start a new one</newIssueLink>.": "Palun esmalt vaata, kas <existingIssuesLink>Githubis on selline viga juba kirjeldatud</existingIssuesLink>. Sa ei leidnud midagi? <newIssueLink>Siis saada uus veateade</newIssueLink>.",
"Report a bug": "Teata veast",
"There are two ways you can provide feedback and help us improve %(brand)s.": "On olemas kaks viisi, kuidas sa saad meile tagasisidet jagada ning aidata teha %(brand)s rakendust paremaks.",
"Comment": "Kommentaar",
"Add comment": "Lisa kommentaar",
"Please go into as much detail as you like, so we can track down the problem.": "Selleks, et saaksime probleemi põhjuse leida, lisa nii palju lisateavet kui soovid.",
"Tell us below how you feel about %(brand)s so far.": "Kirjelda meile, mis mulje sulle on %(brand)s rakendusest seni jäänud.",
"Rate %(brand)s": "Hinda %(brand)s rakendust",
"Feedback sent": "Tagasiside on saadetud",
"%(senderName)s ended the call": "%(senderName)s lõpetas kõne",
"You ended the call": "Sina lõpetasid kõne"
}

View file

@ -2244,5 +2244,24 @@
"You can only pin 2 widgets at a time": "Vain kaksi sovelmaa voi olla kiinnitettynä samaan aikaan",
"Add widgets, bridges & bots": "Lisää sovelmia, siltoja ja botteja",
"Edit widgets, bridges & bots": "Muokkaa sovelmia, siltoja ja botteja",
"Widgets": "Sovelmat"
"Widgets": "Sovelmat",
"Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Yhteisöjen v2 prototyypit. Vaatii yhteensopivan kotipalvelimen. Erittäin kokeellinen — käytä varoen.",
"Change notification settings": "Muokkaa ilmoitusasetuksia",
"The person who invited you already left the room, or their server is offline.": "Henkilö, joka kutsui sinut, on jo lähtenyt huoneesta, tai hänen palvelin on pois verkosta.",
"The person who invited you already left the room.": "Henkilö, joka kutsui sinut, lähti jo huoneesta.",
"Unknown App": "Tuntematon sovellus",
"Error leaving room": "Virhe poistuessa huoneesta",
"Unexpected server error trying to leave the room": "Huoneesta poistuessa tapahtui odottamaton palvelinvirhe",
"%(senderName)s declined the call.": "%(senderName)s hylkäsi puhelun.",
"(an error occurred)": "(virhe tapahtui)",
"(their device couldn't start the camera / microphone)": "(hänen laitteensa ei voinut käynnistää kameraa tai mikrofonia)",
"(connection failed)": "(yhteys katkesi)",
"🎉 All servers are banned from participating! This room can no longer be used.": "Kaikki palvelimet ovat saaneet porttikiellon huoneeseen! Tätä huonetta ei voi enää käyttää.",
"Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Lisää hymiön ( ͡° ͜ʖ ͡°) viestin alkuun",
"Are you sure you want to cancel entering passphrase?": "Haluatko varmasti peruuttaa salalauseen syöttämisen?",
"The call was answered on another device.": "Puheluun vastattiin toisessa laitteessa.",
"Answered Elsewhere": "Vastattu muualla",
"The call could not be established": "Puhelua ei voitu aloittaa",
"The other party declined the call.": "Toinen osapuoli hylkäsi puhelun.",
"Call Declined": "Puhelu hylätty"
}

View file

@ -377,7 +377,7 @@
"%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (rang %(powerLevelNumber)s)",
"(could not connect media)": "(impossible de se connecter au média)",
"(no answer)": "(pas de réponse)",
"(unknown failure: %(reason)s)": "(erreur inconnue : %(reason)s)",
"(unknown failure: %(reason)s)": "(erreur inconnue : %(reason)s)",
"Your browser does not support the required cryptography extensions": "Votre navigateur ne supporte pas les extensions cryptographiques nécessaires",
"Not a valid %(brand)s keyfile": "Fichier de clé %(brand)s non valide",
"Authentication check failed: incorrect password?": "Erreur dauthentification : mot de passe incorrect ?",
@ -1126,7 +1126,7 @@
"Pizza": "Pizza",
"Cake": "Gâteau",
"Heart": "Cœur",
"Smiley": "Smiley",
"Smiley": "Émoticône",
"Robot": "Robot",
"Hat": "Chapeau",
"Glasses": "Lunettes",
@ -1759,7 +1759,7 @@
"about a day from now": "dans un jour environ",
"%(num)s days from now": "dans %(num)s jours",
"Failed to invite the following users to chat: %(csvUsers)s": "Échec de linvitation des utilisateurs suivants à discuter : %(csvUsers)s",
"We couldn't create your DM. Please check the users you want to invite and try again.": "Impossible de créer votre Message direct. Vérifiez les utilisateurs que vous souhaitez inviter et réessayez.",
"We couldn't create your DM. Please check the users you want to invite and try again.": "Impossible de créer votre message direct. Vérifiez les utilisateurs que vous souhaitez inviter et réessayez.",
"Something went wrong trying to invite the users.": "Une erreur est survenue en essayant dinviter les utilisateurs.",
"We couldn't invite those users. Please check the users you want to invite and try again.": "Impossible dinviter ces utilisateurs. Vérifiez les utilisateurs que vous souhaitez inviter et réessayez.",
"Recently Direct Messaged": "Messages directs récents",
@ -1903,7 +1903,7 @@
"Regain access to your account and recover encryption keys stored in this session. Without them, you wont be able to read all of your secure messages in any session.": "Récupérez laccès à votre compte et restaurez les clés de chiffrement dans cette session. Sans elles, vous ne pourrez pas lire tous vos messages chiffrés dans nimporte quelle session.",
"Warning: Your personal data (including encryption keys) is still stored in this session. Clear it if you're finished using this session, or want to sign in to another account.": "Attention : Vos données personnelles (y compris les clés de chiffrement) seront stockées dans cette session. Effacez-les si vous nutilisez plus cette session ou si vous voulez vous connecter à un autre compte.",
"Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.": "Mettez à niveau cette session pour lautoriser à vérifier dautres sessions, ce qui leur permettra daccéder aux messages chiffrés et de les marquer comme fiables pour les autres utilisateurs.",
"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.": "Si vous ne configurez pas la récupération de messages sécurisée, vous ne pourrez pas restaurer lhistorique de vos messages chiffrés si vous vous déconnectez ou si vous utilisez une autre session.",
"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.": "Si vous ne configurez pas la récupération de messages sécurisée, vous ne pourrez pas restaurer lhistorique de vos messages chiffrés si vous vous déconnectez ou si vous utilisez une autre session.",
"This session is encrypting history using the new recovery method.": "Cette session chiffre lhistorique en utilisant la nouvelle méthode de récupération.",
"This session has detected that your recovery passphrase and key for Secure Messages have been removed.": "Cette session a détecté que votre mot de passe et votre clé de récupération pour les messages chiffrés ont été supprimés.",
"Setting up keys": "Configuration des clés",
@ -2395,5 +2395,155 @@
"Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.": "La signature croisée est prête à l'emploi, mais le coffre secret n'est pas actuellement utilisé pour sauvegarder vos clés.",
"Master private key:": "Clé privée maîtresse :",
"%(brand)s can't securely cache encrypted messages locally while running in a web browser. Use <desktopLink>%(brand)s Desktop</desktopLink> for encrypted messages to appear in search results.": "%(brand)s ne peut actuellement mettre en cache vos messages chiffrés localement de manière sécurisée via le navigateur Web. Utilisez <desktopLink>%(brand)s Desktop</desktopLink> pour que les messages chiffrés apparaissent dans vos résultats de recherche.",
"There are advanced notifications which are not shown here.": "Des notifications avancées ne sont pas affichées ici."
"There are advanced notifications which are not shown here.": "Des notifications avancées ne sont pas affichées ici.",
"ready": "prêt",
"The operation could not be completed": "L'opération n'a pas pu être terminée",
"Failed to save your profile": "Erreur lors de l'enregistrement du profile",
"Unknown App": "Application inconnue",
"%(senderName)s declined the call.": "%(senderName)s a refusé l'appel.",
"(an error occurred)": "(une erreur est survenue)",
"(connection failed)": "(échec de connexion)",
"%(senderDisplayName)s changed the server ACLs for this room.": "%(senderDisplayName)s a changé les paramètres d'accès du serveur pour ce salon.",
"%(senderDisplayName)s set the server ACLs for this room.": "%(senderDisplayName)s a défini les paramètres d'accès du serveur pour ce salon.",
"Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Ajoute ( ͡° ͜ʖ ͡°) devant un message en texte brut",
"This will end the conference for everyone. Continue?": "Ceci arrêtera la téléconférence pour tout le monde. Continuer ?",
"End conference": "Finir la téléconférence",
"The call was answered on another device.": "L'appel a été répondu sur un autre appareil.",
"Answered Elsewhere": "Répondu autre-part",
"The call could not be established": "L'appel n'a pas pu être établi",
"The other party declined the call.": "L'autre personne a décliné l'appel.",
"Call Declined": "Appel rejeté",
"Ignored attempt to disable encryption": "Essai de désactiver le chiffrement ignoré",
"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.": "Ajoutez les utilisateurs et les serveurs que vous voulez ignorer ici. Utilisez des astérisques pour que %(brand)s comprenne tous les caractères. Par exemple, <code>@bot:*</code> va ignorer tous les utilisateurs ayant le nom 'bot' sur n'importe quel serveur.",
"not ready": "pas prêt",
"Secret storage:": "Coffre secret :",
"Backup key cached:": "Clé de sauvegarde mise en cache :",
"Backup key stored:": "Clé de sauvegarde enregistrée :",
"Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.": "Sauvegarder vos clés de chiffrement avec les données de votre compte dans le cas où vous perdez l'accès à vos sessions. Vos clés seront sécurisées grâce à une unique clé de récupération.",
"Backup version:": "Version de la sauvegarde :",
"You might have configured them in a client other than %(brand)s. You cannot tune them in %(brand)s but they still apply.": "Vous les avez peut-être configurés dans un autre client que %(brand)s. Vous ne pouvez pas les configurer dans %(brand)s mais ils s'appliquent quand même.",
"not found in storage": "non trouvé dans le coffre",
"Cross-signing is not set up.": "La signature croisée n'est pas configurée.",
"Cross-signing is ready for use.": "La signature croisée est prête à être utilisée.",
"Offline encrypted messaging using dehydrated devices": "Messagerie hors-ligne chiffrée utilisant des appareils déshydratés",
"Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Prototype de communautés v2. Requiert un serveur d'accueil compatible. Très expérimental - à utiliser avec précaution.",
"Safeguard against losing access to encrypted messages & data": "Sécurité contre la perte d'accès aux messages & données chiffrées",
"(their device couldn't start the camera / microphone)": "(son appareil ne peut pas démarrer la caméra / le microphone)",
"🎉 All servers are banned from participating! This room can no longer be used.": "🎉 Tous les serveurs sont interdits de participation ! Ce salon ne peut plus être utilisé.",
"What's the name of your community or team?": "Quel est le nom de votre communauté ou équipe ?",
"You can change this later if needed.": "Vous pouvez modifier ceci après si besoin.",
"Use this when referencing your community to others. The community ID cannot be changed.": "Utilisez ceci lorsque vous faites référence à votre communauté aux autres. L'identifiant de la communauté ne peut pas être modifié.",
"There was an error creating your community. The name may be taken or the server is unable to process your request.": "Une erreur est survenue lors de la création de votre communauté. Le nom est peut-être pris ou le serveur ne peut pas exécuter votre requête.",
"This version of %(brand)s does not support searching encrypted messages": "Cette version de %(brand)s ne prend pas en charge la recherche dans les messages chiffrés",
"This version of %(brand)s does not support viewing some encrypted files": "Cette version de %(brand)s ne prend pas en charge l'affichage de certains fichiers chiffrés",
"Use the <a>Desktop app</a> to search encrypted messages": "Utilisez une <a>Application de bureau</a> pour rechercher dans tous les messages chiffrés",
"Use the <a>Desktop app</a> to see all encrypted files": "Utilisez une <a>Application de bureau</a> pour voir tous les fichiers chiffrés",
"Join the conference at the top of this room": "Rejoignez la téléconférence en haut de ce salon",
"Join the conference from the room information card on the right": "Rejoignez la téléconférence à partir de la carte d'informations sur la droite",
"Set the name of a font installed on your system & %(brand)s will attempt to use it.": "Définissez le nom d'une police de caractères installée sur votre système et %(brand)s essaiera de l'utiliser.",
"Create a room in %(communityName)s": "Créer un salon dans %(communityName)s",
"An image will help people identify your community.": "Une image aidera les personnes à identifier votre communauté.",
"Add image (optional)": "Ajouter une image (facultatif)",
"Enter name": "Saisir un nom",
"Community ID: +<localpart />:%(domain)s": "Identifiant de la communauté : +<localpart />:%(domain)s",
"Invite people to join %(communityName)s": "Inviter des personnes à rejoindre %(communityName)s",
"Send %(count)s invites|one": "Envoyer %(count)s invitation",
"Send %(count)s invites|other": "Envoyer %(count)s invitations",
"People you know on %(brand)s": "Les personnes que vous connaissez dans %(brand)s",
"Add another email": "Ajouter un autre e-mail",
"Download logs": "Télécharger les journaux",
"Preparing to download logs": "Préparation du téléchargement des journaux",
"Information": "Informations",
"Video conference started by %(senderName)s": "vidéoconférence démarrée par %(senderName)s",
"Video conference updated by %(senderName)s": "vidéoconférence mise à jour par %(senderName)s",
"Video conference ended by %(senderName)s": "vidéoconférence terminée par %(senderName)s",
"Room settings": "Paramètres du salon",
"Show files": "Afficher les fichiers",
"%(count)s people|other": "%(count)s personnes",
"%(count)s people|one": "%(count)s personne",
"About": "À propos",
"Not encrypted": "Non-chiffré",
"Add widgets, bridges & bots": "Ajouter des widgets, ponts et bots",
"Edit widgets, bridges & bots": "Modifier les widgets, ponts et bots",
"Widgets": "Widgets",
"Unpin a widget to view it in this panel": "Dépingler un widget pour l'afficher dans ce panneau",
"Unpin": "Dépingler",
"You can only pin up to %(count)s widgets|other": "Vous ne pouvez épingler que jusqu'à %(count)s widgets",
"Room Info": "Informations sur le salon",
"%(count)s results|one": "%(count)s résultat",
"%(count)s results|other": "%(count)s résultats",
"Explore all public rooms": "Explorer tous les salons publiques",
"Can't see what youre looking for?": "Vous ne voyez pas ce que vous cherchez ?",
"Custom Tag": "Étiquette personnalisée",
"Explore public rooms": "Explorer les salons publiques",
"Explore community rooms": "Explorer les salons de la communauté",
"Show Widgets": "Afficher les widgets",
"Hide Widgets": "Masquer les widgets",
"Remove messages sent by others": "Supprimer les messages envoyés par d'autres",
"Privacy": "Vie privée",
"Secure Backup": "Sauvegarde sécurisée",
"Algorithm:": "Algorithme :",
"Set up Secure Backup": "Configurer la sauvegarde sécurisée",
"%(brand)s Android": "%(brand)s Android",
"Community and user menu": "Menu de la communauté et de l'utilisateur",
"User settings": "Paramètres de l'utilisateur",
"Community settings": "Paramètres de la communauté",
"Failed to find the general chat for this community": "Impossible de trouver le chat général de cette communauté",
"Starting microphone...": "Allumage du microphone …",
"Starting camera...": "Allumage de la caméra ...",
"Call connecting...": "Connexion à l'appel …",
"Calling...": "Appel en cours …",
"Explore rooms in %(communityName)s": "Explorer les salons dans %(communityName)s",
"You have no visible notifications in this room.": "Vous n'avez pas de notification visible dans ce salon.",
"Youre all caught up": "Vous êtes à jour",
"You do not have permission to create rooms in this community.": "Vous n'avez pas la permission de créer des salons dans cette communauté.",
"Cannot create rooms in this community": "Impossible de créer des salons dans cette communauté",
"Create community": "Créer une communauté",
"Attach files from chat or just drag and drop them anywhere in a room.": "Envoyez des fichiers depuis le chat ou juste glissez et déposez-les n'importe où dans le salon.",
"No files visible in this room": "Aucun fichier visible dans ce salon",
"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>.": "Entrez l'emplacement de votre serveur d'accueil Element Matrix Services. Cela peut utiliser votre propre nom de domaine ou être un sous-domaine de <a>element.io</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.": "Vous pouvez utiliser l'option de serveur personnalisé pour vous connecter à d'autres serveurs Matrix en spécifiant une URL de serveur d'accueil différente. Cela vous permet d'utiliser %(brand)s avec un compte Matrix existant sur un serveur d'accueil différent.",
"Away": "Tout droit",
"Move right": "Aller à droite",
"Move left": "Aller à gauche",
"Revoke permissions": "Révoquer les permissions",
"Take a picture": "Prendre une photo",
"Unable to set up keys": "Impossible de configurer les clés",
"Recent changes that have not yet been received": "Changements récents qui n'ont pas encore été reçus",
"The server is not configured to indicate what the problem is (CORS).": "Le n'est n'est pas configuré pour indiquer quel est le problème (CORS).",
"A connection error occurred while trying to contact the server.": "Une erreur de connexion est survenue en essayant de contacter le serveur.",
"Your area is experiencing difficulties connecting to the internet.": "Votre emplacement connaît des difficultés à se connecter à Internet.",
"The server has denied your request.": "Le serveur a refusé votre requête.",
"The server is offline.": "Le serveur est éteint.",
"A browser extension is preventing the request.": "Une extension du navigateur bloque la requête.",
"Your firewall or anti-virus is blocking the request.": "Votre pare-feu ou votre antivirus bloque la requête.",
"The server (%(serverName)s) took too long to respond.": "Le serveur (%(serverName)s) met trop de temps à répondre.",
"Your server isn't responding to some of your requests. Below are some of the most likely reasons.": "Votre serveur ne répond pas à certaines requêtes. Vous trouverez ci-dessus quelles sont la plupart des raisons.",
"Server isn't responding": "Le serveur ne répond pas",
"You're all caught up.": "Vous êtes à jour.",
"Data on this screen is shared with %(widgetDomain)s": "Les données sur cet écran sont partagées avec %(widgetDomain)s",
"Modal Widget": "Fenêtre de widget",
"Invite someone using their name, username (like <userId/>) or <a>share this room</a>.": "Invitez quelqu'un à partir de son nom, pseudo (comme <userId/>) ou <a>partagez ce salon</a>.",
"This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click <a>here</a>": "Cela ne va pas l'inviter à %(communityName)s. Pour inviter quelqu'un à %(communityName)s, cliquez <a>ici</a>",
"Start a conversation with someone using their name or username (like <userId/>).": "Démarrer une conversation avec quelqu'un en utilisant son nom ou son pseudo (comme <userId/>).",
"May include members not in %(communityName)s": "Peut inclure des membres qui ne sont pas dans %(communityName)s",
"Send feedback": "Envoyer un commentaire",
"PRO TIP: If you start a bug, please submit <debugLogsLink>debug logs</debugLogsLink> to help us track down the problem.": "CONSEIL : si vous reportez un bug, merci d'envoyer <debugLogsLink>les logs de débogage</debugLogsLink> pour nous aider à identifier le problème.",
"Please view <existingIssuesLink>existing bugs on Github</existingIssuesLink> first. No match? <newIssueLink>Start a new one</newIssueLink>.": "Merci de regarder d'abord les <existingIssuesLink>bugs déjà répertoriés sur Github</existingIssuesLink>. Pas de résultat ? <newIssueLink>Reportez un nouveau bug</newIssueLink>.",
"Report a bug": "Reporter un bug",
"There are two ways you can provide feedback and help us improve %(brand)s.": "Il y a deux manières pour que vous puissiez faire vos retour et nous aider à améliorer %(brand)s.",
"Comment": "Commentaire",
"Add comment": "Ajouter un commentaire",
"Please go into as much detail as you like, so we can track down the problem.": "Merci d'ajouter le plus de détails possible, pour que nous puissions mieux identifier le problème.",
"Tell us below how you feel about %(brand)s so far.": "Dites-nous ci-dessous quel est votre ressenti à propos de %(brand)s jusque là.",
"Rate %(brand)s": "Noter %(brand)s",
"Feedback sent": "Commentaire envoyé",
"Update community": "Modifier la communauté",
"There was an error updating your community. The server is unable to process your request.": "Une erreur est survenue lors de la mise à jour de votre communauté. Le serveur est incapable de traiter votre requête.",
"Block anyone not part of %(serverName)s from ever joining this room.": "Bloque n'importe qui qui n'est pas membre de %(serverName)s de rejoindre ce salon.",
"You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Vous devriez le déactiver si le salon est utilisé pour collaborer avec des équipes externes qui ont leur propre serveur d'accueil. Ce ne peut pas être changé plus tard.",
"You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "Vous devriez l'activer si le salon n'est utilisé que pour collaborer avec des équipes internes sur votre serveur d'accueil. Ce ne peut pas être changé plus tard.",
"Your server requires encryption to be enabled in private rooms.": "Votre serveur requiert d'activer le chiffrement dans les salons privés.",
"Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Les salons privés ne peuvent être trouvés et rejoints seulement par invitation. Les salons publics peut être trouvés et rejoints par n'importe qui dans cette communauté.",
"Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.": "Les salons privés ne peuvent être trouvés et rejoints seulement par invitation. Les salons publics peut être trouvés et rejoints par n'importe qui."
}

View file

@ -137,7 +137,7 @@
"Enable automatic language detection for syntax highlighting": "Activar a detección automática de idioma para o resalte da sintaxe",
"Automatically replace plain text Emoji": "Substituír automaticamente Emoji en texto plano",
"Enable inline URL previews by default": "Activar por defecto as vistas previas en liña de URL",
"Enable URL previews for this room (only affects you)": "Activar avista previa de URL nesta sala (só che afesta a ti)",
"Enable URL previews for this room (only affects you)": "Activar vista previa de URL nesta sala (só che afecta a ti)",
"Enable URL previews by default for participants in this room": "Activar a vista previa de URL por defecto para as participantes nesta sala",
"Room Colour": "Cor da sala",
"Active call (%(roomName)s)": "Chamada activa (%(roomName)s)",
@ -2537,5 +2537,29 @@
"(connection failed)": "(fallou a conexión)",
"The call could not be established": "Non se puido establecer a chamada",
"The other party declined the call.": "A outra persoa rexeitou a chamada.",
"Call Declined": "Chamada rexeitada"
"Call Declined": "Chamada rexeitada",
"Move right": "Mover á dereita",
"Move left": "Mover á esquerda",
"Revoke permissions": "Revogar permisos",
"Unpin a widget to view it in this panel": "Desafixar un widget para velo neste panel",
"You can only pin up to %(count)s widgets|other": "Só podes fixar ata %(count)s widgets",
"Show Widgets": "Mostrar Widgets",
"Hide Widgets": "Agochar Widgets",
"The call was answered on another device.": "A chamada foi respondida noutro dispositivo.",
"Answered Elsewhere": "Respondido noutro lugar",
"Data on this screen is shared with %(widgetDomain)s": "Os datos nesta pantalla compártense con %(widgetDomain)s",
"Modal Widget": "Widget modal",
"Tell us below how you feel about %(brand)s so far.": "Cóntanos que opinas acerca de %(brand)s ata o momento.",
"Rate %(brand)s": "Valora %(brand)s",
"Feedback sent": "Comentario enviado",
"Send feedback": "Enviar comentario",
"PRO TIP: If you start a bug, please submit <debugLogsLink>debug logs</debugLogsLink> to help us track down the problem.": "PRO TIP: se inicias un novo informe, envía <debugLogsLink>rexistros de depuración</debugLogsLink> para axudarnos a investigar o problema.",
"Please view <existingIssuesLink>existing bugs on Github</existingIssuesLink> first. No match? <newIssueLink>Start a new one</newIssueLink>.": "Primeiro revisa <existingIssuesLink>a lista existente de fallo en Github</existingIssuesLink>. Non hai nada? <newIssueLink>Abre un novo</newIssueLink>.",
"Report a bug": "Informar dun fallo",
"There are two ways you can provide feedback and help us improve %(brand)s.": "Podes axudarnos a mellorar %(brand)s contactando destos dous xeitos.",
"Comment": "Comentar",
"Add comment": "Engadir comentario",
"Please go into as much detail as you like, so we can track down the problem.": "Podes entrar en detalle canto desexes, así poderemos entender mellor o problema.",
"%(senderName)s ended the call": "%(senderName)s finalizou a chamada",
"You ended the call": "Finalizaches a chamada"
}

View file

@ -330,7 +330,7 @@
"Start automatically after system login": "Rendszerindításkor automatikus elindítás",
"Analytics": "Analitika",
"Options": "Opciók",
"%(brand)s collects anonymous analytics to allow us to improve the application.": "%(brand)s személytelen analitikai adatokat gyűjt annak érdekében, hogy fejleszteni tudjuk az alkalmazást.",
"%(brand)s collects anonymous analytics to allow us to improve the application.": "A(z) %(brand)s névtelen analitikai adatokat gyűjt annak érdekében, hogy fejleszteni tudjuk az alkalmazást.",
"Passphrases must match": "A jelmondatoknak meg kell egyezniük",
"Passphrase must not be empty": "A jelmondat nem lehet üres",
"Export room keys": "Szoba kulcsok mentése",
@ -339,7 +339,7 @@
"File to import": "Fájl betöltése",
"The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "A kimentett fájl jelmondattal van védve. A kibontáshoz add meg a jelmondatot.",
"You must join the room to see its files": "Ahhoz hogy lásd a fájlokat be kell lépned a szobába",
"Reject all %(invitedRooms)s invites": "Minden %(invitedRooms)s meghívó elutasítása",
"Reject all %(invitedRooms)s invites": "Mind a(z) %(invitedRooms)s meghívó elutasítása",
"Failed to invite": "Meghívás sikertelen",
"Failed to invite the following users to the %(roomName)s room:": "Az alábbi felhasználókat nem sikerült meghívni a(z) %(roomName)s szobába:",
"Confirm Removal": "Törlés megerősítése",
@ -645,7 +645,7 @@
"To set up a filter, drag a community avatar over to the filter panel on the far left hand side of the screen. You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "A szűrő beállításához húzd a közösség avatarját a szűrő panel fölé a képernyő bal szélén. A szűrő panelen az avatarra kattintva bármikor leszűrheted azokat a szobákat és embereket akik a megadott közösséghez tartoznak.",
"Key request sent.": "Kulcs kérés elküldve.",
"Code": "Kód",
"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.": "Ha a GitHubon keresztül küldted be a hibát, a hibakeresési napló segíthet nekünk a javításban. A napló felhasználási adatokat tartalmaz mint a felhasználói neved, az általad meglátogatott szobák vagy csoportok azonosítóját vagy alternatív nevét és mások felhasználói nevét. De nem tartalmazzák az üzeneteket.",
"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.": "Ha a GitHubon keresztül küldted be a hibát, a hibakeresési napló segíthet nekünk a javításban. A napló felhasználási adatokat tartalmaz, mint például a felhasználói neved, az általad meglátogatott szobák vagy csoportok azonosítóját vagy alternatív nevét és mások felhasználói nevét. De nem tartalmazzák az üzeneteket.",
"Submit debug logs": "Hibakeresési napló küldése",
"Opens the Developer Tools dialog": "Megnyitja a fejlesztői eszközök ablakát",
"Seen by %(displayName)s (%(userName)s) at %(dateTime)s": "%(displayName)s (%(userName)s) az alábbi időpontban látta: %(dateTime)s",
@ -1210,7 +1210,7 @@
"User %(userId)s is already in the room": "%(userId)s felhasználó már a szobában van",
"The user must be unbanned before they can be invited.": "A felhasználó kitiltását először vissza kell vonni mielőtt újra meghívható lesz.",
"<a>Upgrade</a> to your own domain": "<a>Frissíts</a> a saját domain-re",
"Accept all %(invitedRooms)s invites": "Minden meghívást elfogad: %(invitedRooms)s",
"Accept all %(invitedRooms)s invites": "Mind a(z) %(invitedRooms)s meghívás elfogadása",
"Change room avatar": "Szoba profilképének megváltoztatása",
"Change room name": "Szoba nevének megváltoztatása",
"Change main address for the room": "A szoba elsődleges címének megváltoztatása",
@ -1458,7 +1458,7 @@
"Set an email for account recovery. Use email to optionally be discoverable by existing contacts.": "E-mail cím beállítása a fiók visszaállításához. E-mail cím, hogy ismerősök megtalálhassanak.",
"Enter your custom homeserver URL <a>What does this mean?</a>": "Add meg a matrix szervered URL-jét <a>Mit jelent ez?</a>",
"Enter your custom identity server URL <a>What does this mean?</a>": "Add meg az azonosítási szervered URL-jét <a>Mit jelent ez?</a>",
"Use an identity server to invite by email. <default>Use the default (%(defaultIdentityServerName)s)</default> or manage in <settings>Settings</settings>.": "Használj azonosítási szervert e-mail címmel való meghíváshoz. <default> Használd az alapértelmezett szervert (%(defaultIdentityServerName)s)</default> vagy adj meg mást a <settings>Beállításokban</settings>.",
"Use an identity server to invite by email. <default>Use the default (%(defaultIdentityServerName)s)</default> or manage in <settings>Settings</settings>.": "Használj azonosítási szervert az e-mail címmel való meghíváshoz. <default> Használd az alapértelmezett szervert (%(defaultIdentityServerName)s)</default> vagy adj meg mást a <settings>Beállításokban</settings>.",
"Use an identity server to invite by email. Manage in <settings>Settings</settings>.": "Használj azonosítási szervert e-mail címmel való meghíváshoz. Megadása a <settings>Beállításokban</settings>.",
"Enable room encryption": "Szoba titkosításának bekapcsolása",
"Use an identity server": "Azonosítási kiszolgáló használata",
@ -1590,7 +1590,7 @@
"%(name)s accepted": "%(name)s elfogadta",
"You cancelled": "Megszakítottad",
"%(name)s cancelled": "%(name)s megszakította",
"%(name)s wants to verify": "%(name)s ellenőrizni szeretné",
"%(name)s wants to verify": "%(name)s ellenőrizni szeretne",
"You sent a verification request": "Ellenőrzési kérést küldtél",
"Try out new ways to ignore people (experimental)": "Emberek figyelmen kívül hagyásához próbálj ki új utakat (kísérleti)",
"My Ban List": "Tiltólistám",
@ -1797,9 +1797,9 @@
"Channel: %(channelName)s": "Csatorna: %(channelName)s",
"Show less": "Kevesebbet mutat",
"Securely cache encrypted messages locally for them to appear in search results, using ": "A titkosított üzenetek kereséséhez azokat biztonságos módon helyileg kell tárolnod, felhasználva: ",
" to store messages from ": " üzenetek eltárolásához innen ",
"rooms.": "szobák.",
"Manage": "Kezel",
" to store messages from ": " üzenetek tárolásához ",
"rooms.": "szobából.",
"Manage": "Kezelés",
"Securely cache encrypted messages locally for them to appear in search results.": "A titkosított üzenetek kereséséhez azokat biztonságos módon helyileg kell tárolnod.",
"Enable": "Engedélyez",
"%(brand)s is missing some components required for securely caching encrypted messages locally. If you'd like to experiment with this feature, build a custom %(brand)s Desktop with <nativeLink>search components added</nativeLink>.": "A %(brand)sból a titkosított üzenetek biztonságos helyi tárolásához hiányzik néhány dolog. Ha kísérletezni szeretnél ezzel a lehetőséggel fordíts le egy saját %(brand)sot a <nativeLink>kereső komponens hozzáadásával</nativeLink>.",
@ -2375,7 +2375,7 @@
"Away": "Távol",
"Are you sure you want to cancel entering passphrase?": "Biztos vagy benne, hogy megszakítod a jelmondat bevitelét?",
"Enable advanced debugging for the room list": "Kibővített hibakeresés bekapcsolása a szoba listához",
"%(brand)s can't securely cache encrypted messages locally while running in a web browser. Use <desktopLink>%(brand)s Desktop</desktopLink> for encrypted messages to appear in search results.": "%(brand)s nem képes a web böngészőben futva biztonságosan elmenteni a titkosított üzeneteket helyben. Használd az <desktopLink>Asztali %(brand)s</desktopLink> ahhoz, hogy az üzenetekben való keresésekkor a titkosított üzenetek is megjelenhessenek.",
"%(brand)s can't securely cache encrypted messages locally while running in a web browser. Use <desktopLink>%(brand)s Desktop</desktopLink> for encrypted messages to appear in search results.": "A(z) %(brand)s nem képes a web böngészőben futva biztonságosan elmenteni a titkosított üzeneteket helyben. Használd az <desktopLink>Asztali %(brand)s</desktopLink> alkalmazást ahhoz, hogy az üzenetekben való keresésekkor a titkosított üzenetek is megjelenhessenek.",
"You might have configured them in a client other than %(brand)s. You cannot tune them in %(brand)s but they still apply.": "Valószínűleg egy %(brand)s klienstől eltérő programmal konfiguráltad. %(brand)s kliensben nem tudod módosítani de attól még érvényesek.",
"Set the name of a font installed on your system & %(brand)s will attempt to use it.": "Add meg a rendszer által használt font nevét és %(brand)s megpróbálja majd azt használni.",
"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.": "A figyelmen kívül hagyandó felhasználókat és szervereket itt add meg. %(brand)s kliensben használj csillagot hogy a helyén minden karakterre illeszkedjen a kifejezés. Például: <code>@bot:*</code> figyelmen kívül fog hagyni minden „bot” nevű felhasználót bármely szerverről.",
@ -2484,7 +2484,7 @@
"Cross-signing is not set up.": "Eszközök közötti hitelesítés nincs beállítva.",
"Backup version:": "Mentés verzió:",
"Algorithm:": "Algoritmus:",
"Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.": "Ments el a titkosítási kulcsaidat a fiókadatokkal arra az esetre ha levesztenéd a hozzáférést a munkameneteidhez. A kulcsok egy egyedi visszaállítási kulccsal lesznek védve.",
"Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.": "Ments el a titkosítási kulcsaidat a fiókadatokkal arra az esetre ha elvesztenéd a hozzáférést a munkameneteidhez. A kulcsok egy egyedi visszaállítási kulccsal lesznek védve.",
"Backup key stored:": "Mentési kulcs tár:",
"Backup key cached:": "Mentési kulcs gyorsítótár:",
"Secret storage:": "Biztonsági tároló:",
@ -2507,7 +2507,7 @@
"not found in storage": "a tárban nem található",
"Widgets": "Kisalkalmazások",
"Edit widgets, bridges & bots": "Kisalkalmazások, hidak és botok szerkesztése",
"Use the <a>Desktop app</a> to see all encrypted files": "Minden titkosított fájl eléréséhez használd az <a>Asztali alkalmazást</a>",
"Use the <a>Desktop app</a> to see all encrypted files": "Ahhoz, hogy elérd az összes titkosított fájlt, használd az <a>Asztali alkalmazást</a>",
"Use the <a>Desktop app</a> to search encrypted messages": "A titkosított üzenetek kereséséhez használd az <a>Asztali alkalmazást</a>",
"This version of %(brand)s does not support viewing some encrypted files": "%(brand)s ezen verziója nem minden titkosított fájl megjelenítését támogatja",
"This version of %(brand)s does not support searching encrypted messages": "%(brand)s ezen verziója nem támogatja a keresést a titkosított üzenetekben",
@ -2538,5 +2538,16 @@
"(connection failed)": "(kapcsolódás sikertelen)",
"The call could not be established": "A hívás kapcsolatot nem lehet felépíteni",
"The other party declined the call.": "A másik fél elutasította a hívást.",
"Call Declined": "Hívás elutasítva"
"Call Declined": "Hívás elutasítva",
"Move right": "Jobbra mozgat",
"Move left": "Balra mozgat",
"Revoke permissions": "Jogosultságok visszavonása",
"Data on this screen is shared with %(widgetDomain)s": "Az adatok erről a képernyőről megosztásra kerülnek ezzel: %(widgetDomain)s",
"Modal Widget": "Előugró kisalkalmazás",
"Unpin a widget to view it in this panel": "Kisalkalmazás megjelenítése ezen a panelen",
"You can only pin up to %(count)s widgets|other": "Csak %(count)s kisalkalmazást tudsz kitűzni",
"Show Widgets": "Kisalkalmazások megjelenítése",
"Hide Widgets": "Kisalkalmazások elrejtése",
"The call was answered on another device.": "A hívás másik eszközön lett fogadva.",
"Answered Elsewhere": "Máshol lett felvéve"
}

View file

@ -2540,5 +2540,29 @@
"(connection failed)": "(connessione fallita)",
"The call could not be established": "Impossibile stabilire la chiamata",
"The other party declined the call.": "Il destinatario ha rifiutato la chiamata.",
"Call Declined": "Chiamata rifiutata"
"Call Declined": "Chiamata rifiutata",
"Offline encrypted messaging using dehydrated devices": "Messaggistica offline criptata usando dispositivi \"disidratati\"",
"Move right": "Sposta a destra",
"Move left": "Sposta a sinistra",
"Revoke permissions": "Revoca autorizzazioni",
"Unpin a widget to view it in this panel": "Sblocca un widget per vederlo in questo pannello",
"You can only pin up to %(count)s widgets|other": "Puoi ancorare al massimo %(count)s widget",
"Show Widgets": "Mostra i widget",
"Hide Widgets": "Nascondi i widget",
"The call was answered on another device.": "La chiamata è stata accettata su un altro dispositivo.",
"Answered Elsewhere": "Risposto altrove",
"Data on this screen is shared with %(widgetDomain)s": "I dati in questa schermata vengono condivisi con %(widgetDomain)s",
"Send feedback": "Invia feedback",
"PRO TIP: If you start a bug, please submit <debugLogsLink>debug logs</debugLogsLink> to help us track down the problem.": "CONSIGLIO: se segnali un errore, invia <debugLogsLink>i log di debug</debugLogsLink> per aiutarci ad individuare il problema.",
"Please view <existingIssuesLink>existing bugs on Github</existingIssuesLink> first. No match? <newIssueLink>Start a new one</newIssueLink>.": "Prima controlla <existingIssuesLink>gli errori esistenti su Github</existingIssuesLink>. Non l'hai trovato? <newIssueLink>Apri una segnalazione</newIssueLink>.",
"Report a bug": "Segnala un errore",
"There are two ways you can provide feedback and help us improve %(brand)s.": "Ci sono due modi per fornire un feedback ed aiutarci a migliorare %(brand)s.",
"Comment": "Commento",
"Add comment": "Aggiungi commento",
"Please go into as much detail as you like, so we can track down the problem.": "Cerca di aggiungere più dettagli possibile, in modo che possiamo individuare il problema.",
"Tell us below how you feel about %(brand)s so far.": "Dicci qua sotto come ti sembra %(brand)s finora.",
"Rate %(brand)s": "Valuta %(brand)s",
"Feedback sent": "Feedback inviato",
"%(senderName)s ended the call": "%(senderName)s ha terminato la chiamata",
"You ended the call": "Hai terminato la chiamata"
}

File diff suppressed because it is too large Load diff

View file

@ -1422,8 +1422,8 @@
"Compact": "Kompakt",
"Modern": "Moderne",
"Server or user ID to ignore": "Tjener- eller bruker-ID-en som skal ignoreres",
"Show %(count)s more|other": "Vis %(count) til",
"Show %(count)s more|one": "Vis %(count) til",
"Show %(count)s more|other": "Vis %(count)s til",
"Show %(count)s more|one": "Vis %(count)s til",
"Use default": "Bruk standarden",
"Notification options": "Varselsinnstillinger",
"Room options": "Rominnstillinger",

View file

@ -1331,5 +1331,6 @@
"Join millions for free on the largest public server": "Kom ihop med millionar av andre på den største offentlege tenaren",
"Order rooms by name": "Sorter rom etter namn",
"Show rooms with unread notifications first": "Vis rom med ulesne varsel fyrst",
"Show rooms with unread messages first": "Vis rom med ulesne meldingar fyrst"
"Show rooms with unread messages first": "Vis rom med ulesne meldingar fyrst",
"People": "Folk"
}

View file

@ -492,7 +492,7 @@
"Remove this user from community?": "Remover esta pessoa da comunidade?",
"Failed to withdraw invitation": "Não foi possível retirar o convite",
"Failed to remove user from community": "Não foi possível remover esta pessoa da comunidade",
"Filter community members": "Filtrar participantes da comunidade",
"Filter community members": "Pesquisar participantes da comunidade",
"Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Tem certeza que quer remover a sala '%(roomName)s' da comunidade %(groupId)s?",
"Removing a room from the community will also remove it from the community page.": "Remover uma sala da comunidade também a removerá da página da comunidade.",
"Failed to remove room from community": "Não foi possível remover a sala da comunidade",
@ -581,7 +581,7 @@
"example": "exemplo",
"Create": "Criar",
"To get started, please pick a username!": "Para começar, escolha um nome de usuário!",
"<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 para a página da sua comunidade</h1>\n<p>\n Use a descrição longa para apresentar a comunidade para novas/os participantes, ou compartilhe <a href=\"foo\">links</a> importantes.\n</p>\n<p>\n Você pode até mesmo usar tags 'img' em HTML\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 para a página da sua comunidade</h1>\n<p>\n Use a descrição longa para apresentar a comunidade para novas/os participantes,\n ou compartilhe <a href=\"foo\">links</a> importantes.\n</p>\n<p>\n Você pode até mesmo usar tags 'img' em HTML\n</p>\n",
"Add rooms to the community summary": "Adicionar salas para o índice da comunidade",
"Which rooms would you like to add to this summary?": "Quais salas você gostaria de adicionar a este índice?",
"Add to summary": "Adicionar ao índice",
@ -870,8 +870,8 @@
"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ê precisa exportar as chaves da sua sala antes de se desconectar. Quando entrar novamente, você precisará usar a versão mais atual do %(brand)s",
"Incompatible Database": "Banco de dados incompatível",
"Continue With Encryption Disabled": "Continuar com criptografia desativada",
"This will make your account permanently unusable. You will not be able to log in, and no one will be able to re-register the same user ID. This will cause your account to leave all rooms it is participating in, and it will remove your account details from your identity server. <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>",
"Deactivating your account <b>does not by default cause us to forget messages you have sent.</b> If you would like us to forget your messages, please tick the box below.": "Desativar sua conta <b>não faz com que, por padrão, esqueçamos as mensagens que você enviou.</ B> Se você quiser que esqueçamos suas mensagens, marque a caixa abaixo.",
"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>",
"Deactivating your account <b>does not by default cause us to forget messages you have sent.</b> If you would like us to forget your messages, please tick the box below.": "Desativar sua conta <b>não faz com que, por padrão, esqueçamos as mensagens que você enviou.</b> Se você quiser que esqueçamos suas mensagens, marque a opção abaixo.",
"Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.": "A visibilidade das mensagens no Matrix é semelhante ao e-mail. O fato de esquecermos suas mensagens significa que as mensagens que você enviou não serão compartilhadas com usuários novos ou não registrados, mas os usuários registrados que já têm acesso a essas mensagens ainda terão acesso à cópia delas.",
"Please forget all messages I have sent when my account is deactivated (<b>Warning:</b> this will cause future users to see an incomplete view of conversations)": "Quando minha conta for desativada, exclua todas as mensagens que eu enviei (<b>Atenção:</b> isso fará com que futuros usuários tenham uma visão incompleta das conversas)",
"To continue, please enter your password:": "Para continuar, por favor digite sua senha:",
@ -928,7 +928,7 @@
"Can't leave Server Notices room": "Não é possível sair da sala Avisos do Servidor",
"This room is used for important messages from the Homeserver, so you cannot leave it.": "Esta sala é usada para mensagens importantes do Homeserver, então você não pode sair dela.",
"Terms and Conditions": "Termos e Condições",
"To continue using the %(homeserverDomain)s homeserver you must review and agree to our terms and conditions.": "To continue using the %(homeserverDomain)s homeserver you must review and agree to our terms and conditions.\n\nPara continuar usando o homeserver %(homeserverDomain)s, você deve rever e concordar com nossos termos e condições.",
"To continue using the %(homeserverDomain)s homeserver you must review and agree to our terms and conditions.": "Para continuar usando o servidor local %(homeserverDomain)s, você deve ler e concordar com nossos termos e condições.",
"Review terms and conditions": "Revise os termos e condições",
"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.",
@ -947,9 +947,9 @@
"That doesn't match.": "Isto não corresponde.",
"Go back to set it again.": "Voltar para configurar novamente.",
"Download": "Baixar",
"<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>Copy it</b> to your personal cloud storage": "<b>Copie isto</ b> para seu armazenamento em nuvem pessoal",
"<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>Copy it</b> to your personal cloud storage": "<b>Copie isto</b> para o seu armazenamento em nuvem pessoal",
"Set up Secure Message Recovery": "Configurar Recuperação Segura de Mensagens",
"Unable to create key backup": "Não foi possível criar backup da chave",
"Retry": "Tentar novamente",
@ -1042,7 +1042,7 @@
"Glasses": "Óculos",
"Spanner": "Chave inglesa",
"Santa": "Papai-noel",
"Prepends ¯\\_(ツ)_/¯ to a plain-text message": "Adiciona ¯ \\ _ (ツ) _ / ¯ a uma mensagem de texto",
"Prepends ¯\\_(ツ)_/¯ to a plain-text message": "Adiciona ¯ \\ _ (ツ) _ / ¯ a uma mensagem de texto",
"User %(userId)s is already in the room": "O usuário %(userId)s já está na sala",
"The user must be unbanned before they can be invited.": "O banimento do usuário precisa ser removido antes de ser convidado.",
"Show display name changes": "Mostrar alterações de nome e sobrenome",
@ -1206,7 +1206,7 @@
"Session already verified!": "Sessão já confirmada!",
"WARNING: Session already verified, but keys do NOT MATCH!": "ATENÇÃO: Sessão já confirmada, mas as chaves NÃO SÃO IGUAIS!",
"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!": "ATENÇÃO: A CONFIRMAÇÃO DA CHAVE FALHOU! A chave de assinatura para %(userId)s e sessão %(deviceId)s é \"%(fprint)s\", o que não corresponde à chave fornecida \"%(fingerprint)s\". Isso pode significar que suas comunicações estejam sendo interceptadas por terceiros!",
"The signing key you provided matches the signing key you received from %(userId)s's session %(deviceId)s. Session marked as verified.": "A chave de assinatura que você forneceu corresponde à chave de assinatura que você recebeu da sessão %(deviceId)s do usuário %(userId)s. Esta sessão foi marcada como confirmada.",
"The signing key you provided matches the signing key you received from %(userId)s's session %(deviceId)s. Session marked as verified.": "A chave de assinatura que você forneceu corresponde à chave de assinatura que você recebeu da sessão %(deviceId)s do usuário %(userId)s. Esta sessão foi marcada como confirmada.",
"Sends the given message coloured as a rainbow": "Envia a mensagem colorida como arco-íris",
"Sends the given emote coloured as a rainbow": "Envia o emoji colorido como um arco-íris",
"Displays list of commands with usages and descriptions": "Exibe a lista de comandos com usos e descrições",
@ -2469,5 +2469,29 @@
"(an error occurred)": "(ocorreu um erro)",
"(connection failed)": "(a conexão falhou)",
"🎉 All servers are banned from participating! This room can no longer be used.": "🎉 Todos os servidores foram banidos desta sala! Esta sala não pode mais ser utilizada.",
"Call Declined": "Chamada recusada"
"Call Declined": "Chamada recusada",
"You can only pin up to %(count)s widgets|other": "Você pode fixar até %(count)s widgets",
"Move right": "Mover para a direita",
"Move left": "Mover para a esquerda",
"Revoke permissions": "Revogar permissões",
"Unpin a widget to view it in this panel": "Desafixe um widget para exibi-lo neste painel",
"Show Widgets": "Mostrar widgets",
"Hide Widgets": "Esconder widgets",
"The call was answered on another device.": "A chamada foi atendida em outro aparelho.",
"Answered Elsewhere": "Respondido em algum lugar",
"Modal Widget": "Popup do widget",
"Data on this screen is shared with %(widgetDomain)s": "Dados nessa tela são compartilhados com %(widgetDomain)s",
"Send feedback": "Enviar comentário",
"PRO TIP: If you start a bug, please submit <debugLogsLink>debug logs</debugLogsLink> to help us track down the problem.": "DICA: se você nos informar um erro, envie <debugLogsLink> relatórios de erro </debugLogsLink> para nos ajudar a rastrear o problema.",
"Please view <existingIssuesLink>existing bugs on Github</existingIssuesLink> first. No match? <newIssueLink>Start a new one</newIssueLink>.": "Por favor, consulte os <existingIssuesLink> erros conhecidos no Github </existingIssuesLink> antes de enviar o seu. Se ninguém tiver mencionado o seu erro, <newIssueLink> informe-nos sobre um erro novo </newIssueLink>.",
"Feedback sent": "Comentário enviado",
"Report a bug": "Reportar um erro",
"There are two ways you can provide feedback and help us improve %(brand)s.": "Há duas maneiras para fornecer seu comentário e nos ajudar a aprimorar o %(brand)s.",
"Comment": "Comentário",
"Add comment": "Comentar",
"Please go into as much detail as you like, so we can track down the problem.": "Detalhe o quanto for necessário, para que possamos rastrear o problema.",
"Tell us below how you feel about %(brand)s so far.": "Conte-nos abaixo o que você sente sobre o %(brand)s até o momento.",
"Rate %(brand)s": "Avalie o %(brand)s",
"%(senderName)s ended the call": "%(senderName)s encerrou a chamada",
"You ended the call": "Você encerrou a chamada"
}

View file

@ -98,7 +98,7 @@
"click to reveal": "нажмите для открытия",
"%(senderName)s invited %(targetName)s.": "%(senderName)s пригласил %(targetName)s.",
"%(targetName)s joined the room.": "%(targetName)s вошёл в комнату.",
"%(senderName)s kicked %(targetName)s.": "%(senderName)s выгнал(а) %(targetName)s.",
"%(senderName)s kicked %(targetName)s.": "%(senderName)s исключил(а) %(targetName)s.",
"%(targetName)s left the room.": "%(targetName)s покинул(а) комнату.",
"%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s сделал(а) историю разговора видимой для всех собеседников с момента их приглашения.",
"%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s сделал(а) историю разговора видимой для всех собеседников с момента их входа в комнату.",
@ -1085,7 +1085,7 @@
"Folder": "Папка",
"Pin": "Кнопка",
"Your keys are being backed up (the first backup could take a few minutes).": "Выполняется резервная копия ключей (первый раз это может занять несколько минут).",
"The file '%(fileName)s' exceeds this homeserver's size limit for uploads": "Размер файла '%(fileName)s' превышает допустимый размер для этого сервера",
"The file '%(fileName)s' exceeds this homeserver's size limit for uploads": "Размер файла '%(fileName)s' превышает ограничения сервера для загрузки.",
"Prepends ¯\\_(ツ)_/¯ to a plain-text message": "Добавляет смайл ¯\\_(ツ)_/¯ в начало сообщения",
"Changes your display nickname in the current room only": "Изменяет ваш псевдоним только для текущей комнаты",
"Gets or sets the room topic": "Читает или устанавливает тему комнаты",
@ -1153,7 +1153,7 @@
"No homeserver URL provided": "URL-адрес домашнего сервера не указан",
"Unexpected error resolving homeserver configuration": "Неожиданная ошибка в настройках домашнего сервера",
"The user's homeserver does not support the version of the room.": "Домашний сервер пользователя не поддерживает версию комнаты.",
"Show read receipts sent by other users": "Показывать отметки о прочтении, посылаемые другими пользователями",
"Show read receipts sent by other users": "Показывать информацию о прочтении, посылаемую другими пользователями",
"Show hidden events in timeline": "Показать скрытые события в хронологии",
"When rooms are upgraded": "При обновлении комнат",
"<a>Upgrade</a> to your own domain": "<a>Обновление</a> до собственного домена",
@ -2522,5 +2522,41 @@
"Call connecting...": "Устанавливается соединение…",
"Starting camera...": "Запуск камеры…",
"Starting microphone...": "Запуск микрофона…",
"🎉 All servers are banned from participating! This room can no longer be used.": "🎉 Все серверы запрещены к участию! Эта комната больше не может быть использована."
"🎉 All servers are banned from participating! This room can no longer be used.": "🎉 Все серверы запрещены к участию! Эта комната больше не может быть использована.",
"Remove messages sent by others": "Удалить сообщения, отправленные другими",
"Offline encrypted messaging using dehydrated devices": "Автономный обмен зашифрованными сообщениями с сохраненными устройствами",
"Move right": "Сдвинуть вправо",
"Move left": "Сдвинуть влево",
"Revoke permissions": "Отозвать разрешения",
"Data on this screen is shared with %(widgetDomain)s": "Данные на этом экране используются %(widgetDomain)s",
"Modal Widget": "Модальный виджет",
"Ignored attempt to disable encryption": "Игнорируемая попытка отключить шифрование",
"Unpin a widget to view it in this panel": "Открепите виджет, чтобы просмотреть его на этой панели",
"You can only pin up to %(count)s widgets|other": "Вы можете закрепить не более %(count)s виджетов",
"Show Widgets": "Показать виджеты",
"Hide Widgets": "Скрыть виджеты",
"%(senderName)s declined the call.": "%(senderName)s отклонил(а) вызов.",
"(an error occurred)": "(произошла ошибка)",
"(their device couldn't start the camera / microphone)": "(их устройство не может запустить камеру / микрофон)",
"(connection failed)": "(ошибка соединения)",
"%(senderDisplayName)s changed the server ACLs for this room.": "%(senderDisplayName)s изменил(а) серверные разрешения для этой комнаты.",
"%(senderDisplayName)s set the server ACLs for this room.": "%(senderDisplayName)s устанавливает серверные разрешения для этой комнаты.",
"The call was answered on another device.": "На звонок ответили на другом устройстве.",
"Answered Elsewhere": "Ответил в другом месте",
"The call could not be established": "Звонок не может быть установлен",
"The other party declined the call.": "Другой абонент отклонил звонок.",
"Call Declined": "Вызов отклонён",
"Send feedback": "Отправить отзыв",
"PRO TIP: If you start a bug, please submit <debugLogsLink>debug logs</debugLogsLink> to help us track down the problem.": "СОВЕТ ДЛЯ ПРОФЕССИОНАЛОВ: если вы запустите ошибку, отправьте <debugLogsLink>журналы отладки</debugLogsLink>, чтобы помочь нам отследить проблему.",
"Please view <existingIssuesLink>existing bugs on Github</existingIssuesLink> first. No match? <newIssueLink>Start a new one</newIssueLink>.": "Сначала просмотрите <existingIssuesLink>существующий список ошибок в Github</existingIssuesLink>. Нет совпадений? <newIssueLink>Открыть новую</newIssueLink>.",
"Report a bug": "Сообщить об ошибке",
"There are two ways you can provide feedback and help us improve %(brand)s.": "Вы можете оставить отзыв и помочь нам улучшить %(brand)s двумя способами.",
"Comment": "Комментарий",
"Add comment": "Добавить комментарий",
"Please go into as much detail as you like, so we can track down the problem.": "Пожалуйста, расскажите как можно более подробно, чтобы мы могли отследить проблему.",
"Tell us below how you feel about %(brand)s so far.": "Расскажите нам ниже, что вы думаете о %(brand)s на данный момент.",
"Rate %(brand)s": "Оценить %(brand)s",
"Feedback sent": "Отзыв отправлен",
"%(senderName)s ended the call": "%(senderName)s завершил(а) звонок",
"You ended the call": "Вы закончили звонок"
}

View file

@ -2534,5 +2534,27 @@
"(connection failed)": "(dështoi lidhja)",
"The call could not be established": "Thirrja su nis dot",
"The other party declined the call.": "Pala tjetër hodhi poshtë thirrjen.",
"Call Declined": "Thirrja u Hodh Poshtë"
"Call Declined": "Thirrja u Hodh Poshtë",
"Move right": "Lëvize djathtas",
"Move left": "Lëvize majtas",
"Revoke permissions": "Shfuqizoji lejet",
"Unpin a widget to view it in this panel": "Që ta shihni te ku panel, shfiksojeni një widget",
"You can only pin up to %(count)s widgets|other": "Mundeni të fiksoni deri në %(count)s widget-e",
"Show Widgets": "Shfaqi Widget-et",
"Hide Widgets": "Fshihi Widget-et",
"The call was answered on another device.": "Thirrjes iu përgjigj në një tjetër pajisje.",
"Answered Elsewhere": "Përgjigjur Gjetkë",
"Data on this screen is shared with %(widgetDomain)s": "Të dhënat në këtë skenë ndahen me %(widgetDomain)s",
"Modal Widget": "Widget Modal",
"Send feedback": "Dërgoni përshtypjet",
"PRO TIP: If you start a bug, please submit <debugLogsLink>debug logs</debugLogsLink> to help us track down the problem.": "NDIHMËZ PROFESIONISTËSH: Nëse nisni një njoftim të mete, ju lutemi, parashtroni <debugLogsLink>regjistra diagnostikimi</debugLogsLink>, që të na ndihmoni të gjejmë problemin.",
"Please view <existingIssuesLink>existing bugs on Github</existingIssuesLink> first. No match? <newIssueLink>Start a new one</newIssueLink>.": "Ju lutemi, shihni <existingIssuesLink>të meta ekzistuese në Github</existingIssuesLink> së pari. Ska përputhje? <newIssueLink>Nisni një të re</newIssueLink>.",
"Report a bug": "Njoftoni një të metë",
"There are two ways you can provide feedback and help us improve %(brand)s.": "Ka dy rrugë se si mund të jepni përshtypjet dhe të na ndihmoni të përmirësojmë %(brand)s.",
"Comment": "Koment",
"Add comment": "Shtoni koment",
"Please go into as much detail as you like, so we can track down the problem.": "Ju lutemi, shkoni aq thellë sa doni, që të mund të ndjekim problemin.",
"Tell us below how you feel about %(brand)s so far.": "Tregonani më poshtë se si ndiheni rreth %(brand)s deri këtu.",
"Rate %(brand)s": "Vlerësojeni %(brand)s",
"Feedback sent": "Përshtypjet u dërguan"
}

View file

@ -2471,5 +2471,27 @@
"(connection failed)": "(anslutning misslyckad)",
"The call could not be established": "Samtalet kunde inte etableras",
"The other party declined the call.": "Den andra parten avböjde samtalet.",
"Call Declined": "Samtal avböjt"
"Call Declined": "Samtal avböjt",
"Move right": "Flytta till höger",
"Move left": "Flytta till vänster",
"Revoke permissions": "Återkalla behörigheter",
"Data on this screen is shared with %(widgetDomain)s": "Data på den här skärmen delas med %(widgetDomain)s",
"Modal Widget": "Dialogruta",
"Unpin a widget to view it in this panel": "Avfäst en widget för att visa den i den här panelen",
"You can only pin up to %(count)s widgets|other": "Du kan bara fästa upp till %(count)s widgets",
"Show Widgets": "Visa widgets",
"Hide Widgets": "Dölj widgets",
"The call was answered on another device.": "Samtalet mottogs på en annan enhet.",
"Answered Elsewhere": "Mottaget någon annanstans",
"Send feedback": "Skicka återkoppling",
"PRO TIP: If you start a bug, please submit <debugLogsLink>debug logs</debugLogsLink> to help us track down the problem.": "TIPS: Om du startar en bugg, vänligen inkludera <debugLogsLink>avbuggninsloggar</debugLogsLink> för att hjälpa oss att hitta problemet.",
"Please view <existingIssuesLink>existing bugs on Github</existingIssuesLink> first. No match? <newIssueLink>Start a new one</newIssueLink>.": "Vänligen se <existingIssuesLink> existerade buggar på GitHub</existingIssuesLink> först. Finns det ingen som matchar? <newIssueLink>Starta en ny</newIssueLink>.",
"Report a bug": "Rapportera en bugg",
"There are two ways you can provide feedback and help us improve %(brand)s.": "Det finns två sätt du kan ge återkoppling och hjälpa oss att förbättra %(brand)s.",
"Comment": "Kommentera",
"Add comment": "Lägg till kommentar",
"Please go into as much detail as you like, so we can track down the problem.": "Vänligen gå in i hur mycket detalj du vill, så att vi kan hitta problemet.",
"Tell us below how you feel about %(brand)s so far.": "Berätta för oss vad du tycker om %(brand)s än så länge.",
"Rate %(brand)s": "Betygsätt %(brand)s",
"Feedback sent": "Återkoppling skickad"
}

38
src/i18n/strings/tzm.json Normal file
View file

@ -0,0 +1,38 @@
{
"%(senderDisplayName)s sent an image.": "yuzen %(senderDisplayName)s yat twelaft.",
"Other": "Yaḍn",
"Actions": "Tugawin",
"Messages": "Tuzinin",
"Cancel": "Sser",
"Create Account": "Ssenflul amiḍan",
"Sign In": "Kcem",
"Name or Matrix ID": "Isem neɣ ID Matrix",
"Dec": "Duj",
"Nov": "Nuw",
"Oct": "Kṭu",
"Sep": "Cut",
"Aug": "Ɣuc",
"Jul": "Yul",
"Jun": "Yun",
"May": "May",
"Apr": "Ibr",
"Mar": "Maṛ",
"Sat": "Asḍ",
"Fri": "Asm",
"Thu": "Akw",
"Wed": "Akṛ",
"Tue": "Asn",
"Mon": "Ayn",
"Sun": "Asa",
"You are already in a call.": "tsuld Tellid g uɣuri.",
"OK": "WAX",
"Call Declined": "Aɣuri issern",
"Dismiss": "Nexxel",
"Error": "Tazgelt",
"e.g. <CurrentPageURL>": "a.m. <CurrentPageURL>",
"Every page you use in the app": "Akk tasna tssemrsed g tsensi",
"e.g. %(exampleValue)s": "a.m. %(exampleValue)s",
"The version of %(brand)s": "Taleqqemt n %(brand)s",
"Add Phone Number": "Rnu uṭṭun n utilifun",
"Add Email Address": "Rnu tasna imayl"
}

View file

@ -1231,7 +1231,7 @@
"Power level": "權力等級",
"Want more than a community? <a>Get your own server</a>": "想要的不只是社群?<a>架設您自己的伺服器</a>",
"Please install <chromeLink>Chrome</chromeLink>, <firefoxLink>Firefox</firefoxLink>, or <safariLink>Safari</safariLink> for the best experience.": "請安裝 <chromeLink>Chrome</chromeLink>、<firefoxLink>Firefox</firefoxLink> 或 <safariLink>Safari</safariLink> 以取得最佳體驗。",
"<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>。我們會在舊版聊天室中貼出到新聊天室的連結,聊天室成員必須點選此連結以加入新聊天室。",
"Adds a custom widget by URL to the room": "透過 URL 新增自訂小工具到聊天室",
"Please supply a https:// or http:// widget URL": "請提供 https:// 或 http:// 小工具 URL",
"You cannot modify widgets in this room.": "您無法在此聊天室中修改小工具。",
@ -2541,5 +2541,29 @@
"(connection failed)": "(連線失敗)",
"The call could not be established": "無法建立通話",
"The other party declined the call.": "對方拒絕了電話。",
"Call Declined": "通話已拒絕"
"Call Declined": "通話已拒絕",
"Move right": "向右移動",
"Move left": "向左移動",
"Revoke permissions": "撤銷權限",
"Unpin a widget to view it in this panel": "取消釘選小工具以在此面板檢視",
"You can only pin up to %(count)s widgets|other": "您最多只能釘選 %(count)s 個小工具",
"Show Widgets": "顯示小工具",
"Hide Widgets": "隱藏小工具",
"The call was answered on another device.": "通話已在其他裝置上回應。",
"Answered Elsewhere": "在其他地方回答",
"Data on this screen is shared with %(widgetDomain)s": "在此畫面上的資料會與 %(widgetDomain)s 分享",
"Modal Widget": "程式小工具",
"Send feedback": "傳送回饋",
"PRO TIP: If you start a bug, please submit <debugLogsLink>debug logs</debugLogsLink> to help us track down the problem.": "專業建議:如果您開始了一個新臭蟲,請遞交<debugLogsLink>除錯紀錄檔</debugLogsLink>以協助我們尋找問題。",
"Please view <existingIssuesLink>existing bugs on Github</existingIssuesLink> first. No match? <newIssueLink>Start a new one</newIssueLink>.": "請先檢視 <existingIssuesLink>GitHub 上既有的臭蟲</existingIssuesLink>。沒有符合的嗎?<newIssueLink>開始新的</newIssueLink>。",
"Report a bug": "回報臭蟲",
"There are two ways you can provide feedback and help us improve %(brand)s.": "您有兩種方式可以提供回饋並協助我們改善 %(brand)s。",
"Comment": "評論",
"Add comment": "新增評論",
"Please go into as much detail as you like, so we can track down the problem.": "請盡可能地描述問題,讓握們可以找出問題所在。",
"Tell us below how you feel about %(brand)s so far.": "請在下面告訴我們您到目前為止對 %(brand)s 的看法。",
"Rate %(brand)s": "評價 %(brand)s",
"Feedback sent": "已傳送回饋",
"%(senderName)s ended the call": "%(senderName)s 結束了通話",
"You ended the call": "您結束了通話"
}

File diff suppressed because it is too large Load diff

View file

@ -24,9 +24,9 @@ export class CallHangupEvent implements IPreview {
public getTextFor(event: MatrixEvent, tagId?: TagID): string {
if (shouldPrefixMessagesIn(event.getRoomId(), tagId)) {
if (isSelf(event)) {
return _t("You left the call");
return _t("You ended the call");
} else {
return _t("%(senderName)s left the call", {senderName: getSenderName(event)});
return _t("%(senderName)s ended the call", {senderName: getSenderName(event)});
}
} else {
return _t("Call ended");

View file

@ -29,15 +29,15 @@ const onReject = () => {
const TOAST_KEY = "desktopnotifications";
export const showToast = () => {
export const showToast = (fromMessageSend: boolean) => {
ToastStore.sharedInstance().addOrReplaceToast({
key: TOAST_KEY,
title: _t("Notifications"),
title: fromMessageSend ? _t("Don't miss a reply") : _t("Notifications"),
props: {
description: _t("You are not receiving desktop notifications"),
acceptLabel: _t("Enable them now"),
description: _t("Enable desktop notifications"),
acceptLabel: _t("Enable"),
onAccept,
rejectLabel: _t("Close"),
rejectLabel: _t("Dismiss"),
onReject,
},
component: GenericToast,

View file

@ -74,18 +74,18 @@ export const showToast = (version: string, newVersion: string, releaseNotes?: st
};
} else {
onAccept = installUpdate;
acceptLabel = _t("Restart");
acceptLabel = _t("Update");
}
const brand = SdkConfig.get().brand;
ToastStore.sharedInstance().addOrReplaceToast({
key: TOAST_KEY,
title: _t("Upgrade your %(brand)s", { brand }),
title: _t("Update %(brand)s", { brand }),
props: {
description: _t("A new version of %(brand)s is available!", { brand }),
description: _t("New version of %(brand)s is available", { brand }),
acceptLabel,
onAccept,
rejectLabel: _t("Later"),
rejectLabel: _t("Dismiss"),
onReject,
},
component: GenericToast,

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