Merge branch 'develop' of https://github.com/matrix-org/matrix-react-sdk into t3chguy/dpsah/6785
This commit is contained in:
commit
243af3c9f2
119 changed files with 4408 additions and 2295 deletions
|
@ -19,7 +19,7 @@ module.exports = {
|
|||
},
|
||||
|
||||
overrides: [{
|
||||
"files": ["src/**/*.{ts, tsx}"],
|
||||
"files": ["src/**/*.{ts,tsx}"],
|
||||
"extends": ["matrix-org/ts"],
|
||||
"rules": {
|
||||
// We disable this while we're transitioning
|
||||
|
|
69
CHANGELOG.md
69
CHANGELOG.md
|
@ -1,3 +1,72 @@
|
|||
Changes in [3.3.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.3.0) (2020-09-01)
|
||||
===================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.3.0-rc.1...v3.3.0)
|
||||
|
||||
* Upgrade to JS SDK 8.2.0
|
||||
|
||||
Changes in [3.3.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.3.0-rc.1) (2020-08-26)
|
||||
=============================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.2.0...v3.3.0-rc.1)
|
||||
|
||||
* Upgrade to JS SDK 8.2.0-rc.1
|
||||
* Update from Weblate
|
||||
[\#5146](https://github.com/matrix-org/matrix-react-sdk/pull/5146)
|
||||
* BaseAvatar avoid initial render with default avatar
|
||||
[\#5142](https://github.com/matrix-org/matrix-react-sdk/pull/5142)
|
||||
* Enforce Secure Backup completion when requested by HS
|
||||
[\#5130](https://github.com/matrix-org/matrix-react-sdk/pull/5130)
|
||||
* Communities v2 prototype: Explore rooms, global state, and default room
|
||||
[\#5139](https://github.com/matrix-org/matrix-react-sdk/pull/5139)
|
||||
* Add communities v2 prototyping feature flag + initial tag panel prototypes
|
||||
[\#5133](https://github.com/matrix-org/matrix-react-sdk/pull/5133)
|
||||
* Remove some unused components
|
||||
[\#5134](https://github.com/matrix-org/matrix-react-sdk/pull/5134)
|
||||
* Allow avatar image view for 1:1 rooms
|
||||
[\#5137](https://github.com/matrix-org/matrix-react-sdk/pull/5137)
|
||||
* Send mx_local_settings in rageshake
|
||||
[\#5136](https://github.com/matrix-org/matrix-react-sdk/pull/5136)
|
||||
* Run all room leaving behaviour through a single function
|
||||
[\#5132](https://github.com/matrix-org/matrix-react-sdk/pull/5132)
|
||||
* Add clarifying comment in media device selection
|
||||
[\#5131](https://github.com/matrix-org/matrix-react-sdk/pull/5131)
|
||||
* Settings v3: Feature flag changes
|
||||
[\#5124](https://github.com/matrix-org/matrix-react-sdk/pull/5124)
|
||||
* Clear url previews if they all get edited out of the event
|
||||
[\#5129](https://github.com/matrix-org/matrix-react-sdk/pull/5129)
|
||||
* Consider tab completions as modifications for editing purposes to unlock
|
||||
sending
|
||||
[\#5128](https://github.com/matrix-org/matrix-react-sdk/pull/5128)
|
||||
* Use matrix-doc for SAS emoji translations
|
||||
[\#5125](https://github.com/matrix-org/matrix-react-sdk/pull/5125)
|
||||
* Add a rageshake function to download the logs locally
|
||||
[\#3849](https://github.com/matrix-org/matrix-react-sdk/pull/3849)
|
||||
* Room List filtering visual tweaks
|
||||
[\#5123](https://github.com/matrix-org/matrix-react-sdk/pull/5123)
|
||||
* Make reply preview not an overlay so you can see new messages
|
||||
[\#5072](https://github.com/matrix-org/matrix-react-sdk/pull/5072)
|
||||
* Allow room tile context menu when minimized using right click
|
||||
[\#5113](https://github.com/matrix-org/matrix-react-sdk/pull/5113)
|
||||
* Add null guard to group inviter for corrupted groups
|
||||
[\#5121](https://github.com/matrix-org/matrix-react-sdk/pull/5121)
|
||||
* Room List styling tweaks
|
||||
[\#5118](https://github.com/matrix-org/matrix-react-sdk/pull/5118)
|
||||
* Fix corner rounding on images not always affecting right side
|
||||
[\#5120](https://github.com/matrix-org/matrix-react-sdk/pull/5120)
|
||||
* Change add room action for rooms to context menu
|
||||
[\#5108](https://github.com/matrix-org/matrix-react-sdk/pull/5108)
|
||||
* Switch out the globe icon and colour it depending on theme
|
||||
[\#5106](https://github.com/matrix-org/matrix-react-sdk/pull/5106)
|
||||
* Message Action Bar watch for event send changes
|
||||
[\#5115](https://github.com/matrix-org/matrix-react-sdk/pull/5115)
|
||||
* Put message previews for Emoji behind Labs
|
||||
[\#5110](https://github.com/matrix-org/matrix-react-sdk/pull/5110)
|
||||
* Fix styling for selected community marker
|
||||
[\#5107](https://github.com/matrix-org/matrix-react-sdk/pull/5107)
|
||||
* Fix action bar safe area regression
|
||||
[\#5111](https://github.com/matrix-org/matrix-react-sdk/pull/5111)
|
||||
* Fix /op slash command
|
||||
[\#5109](https://github.com/matrix-org/matrix-react-sdk/pull/5109)
|
||||
|
||||
Changes in [3.2.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.2.0) (2020-08-17)
|
||||
===================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.2.0-rc.1...v3.2.0)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "matrix-react-sdk",
|
||||
"version": "3.2.0",
|
||||
"version": "3.3.0",
|
||||
"description": "SDK for matrix.org using React",
|
||||
"author": "matrix.org",
|
||||
"repository": {
|
||||
|
@ -163,9 +163,7 @@
|
|||
"stylelint-config-standard": "^18.3.0",
|
||||
"stylelint-scss": "^3.18.0",
|
||||
"typescript": "^3.9.7",
|
||||
"walk": "^2.3.14",
|
||||
"webpack": "^4.43.0",
|
||||
"webpack-cli": "^3.3.12"
|
||||
"walk": "^2.3.14"
|
||||
},
|
||||
"jest": {
|
||||
"testMatch": [
|
||||
|
|
|
@ -61,7 +61,9 @@
|
|||
@import "./views/dialogs/_BugReportDialog.scss";
|
||||
@import "./views/dialogs/_ChangelogDialog.scss";
|
||||
@import "./views/dialogs/_ChatCreateOrReuseChatDialog.scss";
|
||||
@import "./views/dialogs/_CommunityPrototypeInviteDialog.scss";
|
||||
@import "./views/dialogs/_ConfirmUserActionDialog.scss";
|
||||
@import "./views/dialogs/_CreateCommunityPrototypeDialog.scss";
|
||||
@import "./views/dialogs/_CreateGroupDialog.scss";
|
||||
@import "./views/dialogs/_CreateRoomDialog.scss";
|
||||
@import "./views/dialogs/_DeactivateAccountDialog.scss";
|
||||
|
@ -106,6 +108,7 @@
|
|||
@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/_PowerSelector.scss";
|
||||
|
|
|
@ -30,30 +30,11 @@ limitations under the License.
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mx_TagPanel .mx_TagPanel_clearButton_container {
|
||||
/* Constant height within flex mx_TagPanel */
|
||||
height: 70px;
|
||||
width: 56px;
|
||||
|
||||
flex: none;
|
||||
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mx_TagPanel .mx_TagPanel_clearButton object {
|
||||
/* Same as .mx_SearchBox padding-top */
|
||||
margin-top: 24px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.mx_TagPanel .mx_TagPanel_divider {
|
||||
height: 0px;
|
||||
width: 34px;
|
||||
border-bottom: 1px solid $panel-divider-color;
|
||||
display: none;
|
||||
width: 90%;
|
||||
border: none;
|
||||
border-bottom: 1px solid $tagpanel-divider-color;
|
||||
}
|
||||
|
||||
.mx_TagPanel .mx_TagPanel_scroller {
|
||||
|
@ -76,12 +57,57 @@ limitations under the License.
|
|||
// opacity: 0.5;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mx_TagPanel .mx_TagTile.mx_TagTile_prototype {
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.mx_TagPanel .mx_TagTile:focus,
|
||||
.mx_TagPanel .mx_TagTile:hover,
|
||||
.mx_TagPanel .mx_TagTile.mx_TagTile_selected {
|
||||
// opacity: 1;
|
||||
}
|
||||
|
||||
.mx_TagPanel .mx_TagTile.mx_TagTile_selected_prototype {
|
||||
background-color: $primary-bg-color;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.mx_TagTile_selected_prototype {
|
||||
.mx_TagTile_homeIcon::before {
|
||||
background-color: $primary-fg-color; // dark-on-light
|
||||
}
|
||||
}
|
||||
|
||||
.mx_TagTile:not(.mx_TagTile_selected_prototype) .mx_TagTile_homeIcon {
|
||||
background-color: $roomheader-addroom-bg-color;
|
||||
border-radius: 48px;
|
||||
|
||||
&::before {
|
||||
background-color: $roomheader-addroom-fg-color;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_TagTile_homeIcon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
mask-image: url('$(res)/img/element-icons/home.svg');
|
||||
mask-position: center;
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: 21px;
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
position: absolute;
|
||||
top: calc(50% - 16px);
|
||||
left: calc(50% - 16px);
|
||||
}
|
||||
}
|
||||
|
||||
.mx_TagPanel .mx_TagTile_plus {
|
||||
margin-bottom: 12px;
|
||||
height: 32px;
|
||||
|
|
88
res/css/views/dialogs/_CommunityPrototypeInviteDialog.scss
Normal file
88
res/css/views/dialogs/_CommunityPrototypeInviteDialog.scss
Normal file
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
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_CommunityPrototypeInviteDialog {
|
||||
&.mx_Dialog_fixedWidth {
|
||||
width: 360px;
|
||||
}
|
||||
|
||||
.mx_Dialog_content {
|
||||
margin-bottom: 0;
|
||||
|
||||
.mx_CommunityPrototypeInviteDialog_people {
|
||||
position: relative;
|
||||
margin-bottom: 4px;
|
||||
|
||||
.mx_AccessibleButton {
|
||||
display: inline-block;
|
||||
background-color: $focus-bg-color; // XXX: Abuse of variables
|
||||
border-radius: 4px;
|
||||
padding: 3px 5px;
|
||||
font-size: $font-12px;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_CommunityPrototypeInviteDialog_morePeople {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.mx_CommunityPrototypeInviteDialog_person {
|
||||
position: relative;
|
||||
margin-top: 4px;
|
||||
|
||||
& > * {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.mx_Checkbox {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: calc(50% - 8px); // checkbox is 16px high
|
||||
width: 16px; // to force a square
|
||||
}
|
||||
|
||||
.mx_CommunityPrototypeInviteDialog_personIdentifiers {
|
||||
display: inline-block;
|
||||
|
||||
& > * {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mx_CommunityPrototypeInviteDialog_personName {
|
||||
font-weight: 600;
|
||||
font-size: $font-14px;
|
||||
color: $primary-fg-color;
|
||||
margin-left: 7px;
|
||||
}
|
||||
|
||||
.mx_CommunityPrototypeInviteDialog_personId {
|
||||
font-size: $font-12px;
|
||||
color: $muted-fg-color;
|
||||
margin-left: 7px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_CommunityPrototypeInviteDialog_primaryButton {
|
||||
display: block;
|
||||
font-size: $font-13px;
|
||||
line-height: 20px;
|
||||
height: 20px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
}
|
||||
}
|
102
res/css/views/dialogs/_CreateCommunityPrototypeDialog.scss
Normal file
102
res/css/views/dialogs/_CreateCommunityPrototypeDialog.scss
Normal file
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
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_CreateCommunityPrototypeDialog {
|
||||
.mx_Dialog_content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.mx_CreateCommunityPrototypeDialog_colName {
|
||||
flex-basis: 66.66%;
|
||||
padding-right: 100px;
|
||||
|
||||
.mx_Field input {
|
||||
font-size: $font-16px;
|
||||
line-height: $font-20px;
|
||||
}
|
||||
|
||||
.mx_CreateCommunityPrototypeDialog_subtext {
|
||||
display: block;
|
||||
color: $muted-fg-color;
|
||||
margin-bottom: 16px;
|
||||
|
||||
&:last-child {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
&.mx_CreateCommunityPrototypeDialog_subtext_error {
|
||||
color: $warning-color;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_CreateCommunityPrototypeDialog_communityId {
|
||||
position: relative;
|
||||
|
||||
.mx_InfoTooltip {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_AccessibleButton {
|
||||
display: block;
|
||||
height: 32px;
|
||||
font-size: $font-16px;
|
||||
line-height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_CreateCommunityPrototypeDialog_colAvatar {
|
||||
flex-basis: 33.33%;
|
||||
|
||||
.mx_CreateCommunityPrototypeDialog_avatarContainer {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.mx_CreateCommunityPrototypeDialog_avatar,
|
||||
.mx_CreateCommunityPrototypeDialog_placeholderAvatar {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
border-radius: 96px;
|
||||
}
|
||||
|
||||
.mx_CreateCommunityPrototypeDialog_placeholderAvatar {
|
||||
background-color: #368bd6; // hardcoded for both themes
|
||||
|
||||
&::before {
|
||||
display: inline-block;
|
||||
background-color: #fff; // hardcoded because the background is
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: 96px;
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
mask-position: center;
|
||||
content: '';
|
||||
vertical-align: middle;
|
||||
mask-image: url('$(res)/img/element-icons/add-photo.svg');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_CreateCommunityPrototypeDialog_tip {
|
||||
& > b, & > span {
|
||||
display: block;
|
||||
color: $muted-fg-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
34
res/css/views/elements/_InfoTooltip.scss
Normal file
34
res/css/views/elements/_InfoTooltip.scss
Normal 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.
|
||||
*/
|
||||
|
||||
.mx_InfoTooltip_icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.mx_InfoTooltip_icon::before {
|
||||
display: inline-block;
|
||||
background-color: $muted-fg-color;
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
mask-position: center;
|
||||
content: '';
|
||||
vertical-align: middle;
|
||||
mask-image: url('$(res)/img/element-icons/info.svg');
|
||||
}
|
5
res/img/element-icons/add-photo.svg
Normal file
5
res/img/element-icons/add-photo.svg
Normal file
|
@ -0,0 +1,5 @@
|
|||
<svg width="84" height="84" viewBox="0 0 84 84" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M36.7988 34.9062C37.077 33.5217 38.2978 32.5 39.7396 32.5H44.2604C45.7022 32.5 46.923 33.5217 47.2012 34.9062C47.2429 35.1137 47.3232 35.3141 47.4627 35.4731L48.0649 36.1595C48.2548 36.3759 48.5287 36.5 48.8166 36.5H52C53.1046 36.5 54 37.3954 54 38.5V49.5C54 50.6046 53.1046 51.5 52 51.5H32C30.8954 51.5 30 50.6046 30 49.5V38.5C30 37.3954 30.8954 36.5 32 36.5H35.1834C35.4713 36.5 35.7452 36.3759 35.9351 36.1595L36.5373 35.4731C36.6768 35.3141 36.7571 35.1137 36.7988 34.9062ZM42 47.5C44.2091 47.5 46 45.7091 46 43.5C46 41.2909 44.2091 39.5 42 39.5C39.7909 39.5 38 41.2909 38 43.5C38 45.7091 39.7909 47.5 42 47.5Z" fill="white"/>
|
||||
<rect x="32" y="35" width="3" height="1" rx="0.5" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M59.75 27C59.75 26.5858 59.4142 26.25 59 26.25C58.5858 26.25 58.25 26.5858 58.25 27V31.25L54 31.25C53.5858 31.25 53.25 31.5858 53.25 32C53.25 32.4142 53.5858 32.75 54 32.75L58.25 32.75V37C58.25 37.4142 58.5858 37.75 59 37.75C59.4142 37.75 59.75 37.4142 59.75 37V32.75L64 32.75C64.4142 32.75 64.75 32.4142 64.75 32C64.75 31.5858 64.4142 31.25 64 31.25L59.75 31.25V27Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
3
res/img/element-icons/home.svg
Normal file
3
res/img/element-icons/home.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.33301 7.28322V14.9493C2.33301 16.0735 3.25744 16.9776 4.38152 16.9659C4.90089 16.9605 5.44431 16.9567 6 16.9543V11.5C6 10.6716 6.67157 10 7.5 10H10.5C11.3284 10 12 10.6716 12 11.5V16.9662C12.6022 16.9703 13.1579 16.9748 13.6449 16.9791C14.7592 16.989 15.6663 16.0899 15.6663 14.9756V7.28178C15.6663 6.89062 15.4946 6.52064 15.1965 6.2673L9.97115 1.82572C9.411 1.3496 8.58834 1.3496 8.0282 1.82572L2.80281 6.2673C2.50477 6.52064 2.33301 6.89206 2.33301 7.28322Z" fill="#737D8C"/>
|
||||
</svg>
|
After Width: | Height: | Size: 634 B |
4
res/img/element-icons/info.svg
Normal file
4
res/img/element-icons/info.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="10" cy="10" r="9.5" stroke="#787878"/>
|
||||
<path d="M9.79248 14H11.2065V8H9.79248V14ZM10.5034 7.14844C10.9526 7.14844 11.3198 6.80469 11.3198 6.38281C11.3198 5.95703 10.9526 5.61328 10.5034 5.61328C10.0503 5.61328 9.68311 5.95703 9.68311 6.38281C9.68311 6.80469 10.0503 7.14844 10.5034 7.14844Z" fill="#787878"/>
|
||||
</svg>
|
After Width: | Height: | Size: 424 B |
|
@ -119,6 +119,8 @@ $roomlist-bg-color: rgba(33, 38, 44, 0.90);
|
|||
$roomlist-header-color: $tertiary-fg-color;
|
||||
$roomsublist-divider-color: $primary-fg-color;
|
||||
|
||||
$tagpanel-divider-color: $roomlist-header-color;
|
||||
|
||||
$roomtile-preview-color: $secondary-fg-color;
|
||||
$roomtile-default-badge-bg-color: #61708b;
|
||||
$roomtile-selected-bg-color: rgba(141, 151, 165, 0.2);
|
||||
|
|
|
@ -116,6 +116,8 @@ $roomlist-bg-color: $header-panel-bg-color;
|
|||
|
||||
$roomsublist-divider-color: $primary-fg-color;
|
||||
|
||||
$tagpanel-divider-color: $roomlist-header-color;
|
||||
|
||||
$roomtile-preview-color: #9e9e9e;
|
||||
$roomtile-default-badge-bg-color: #61708b;
|
||||
$roomtile-selected-bg-color: #1A1D23;
|
||||
|
|
|
@ -183,6 +183,8 @@ $roomlist-bg-color: $header-panel-bg-color;
|
|||
$roomlist-header-color: $primary-fg-color;
|
||||
$roomsublist-divider-color: $primary-fg-color;
|
||||
|
||||
$tagpanel-divider-color: $roomlist-header-color;
|
||||
|
||||
$roomtile-preview-color: #9e9e9e;
|
||||
$roomtile-default-badge-bg-color: #61708b;
|
||||
$roomtile-selected-bg-color: #fff;
|
||||
|
|
|
@ -177,6 +177,8 @@ $roomlist-bg-color: rgba(245, 245, 245, 0.90);
|
|||
$roomlist-header-color: $tertiary-fg-color;
|
||||
$roomsublist-divider-color: $primary-fg-color;
|
||||
|
||||
$tagpanel-divider-color: $roomlist-header-color;
|
||||
|
||||
$roomtile-preview-color: $secondary-fg-color;
|
||||
$roomtile-default-badge-bg-color: #61708b;
|
||||
$roomtile-selected-bg-color: #FFF;
|
||||
|
|
2
src/@types/global.d.ts
vendored
2
src/@types/global.d.ts
vendored
|
@ -27,10 +27,12 @@ import {ModalManager} from "../Modal";
|
|||
import SettingsStore from "../settings/SettingsStore";
|
||||
import {ActiveRoomObserver} from "../ActiveRoomObserver";
|
||||
import {Notifier} from "../Notifier";
|
||||
import type {Renderer} from "react-dom";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
Modernizr: ModernizrStatic;
|
||||
matrixChat: ReturnType<Renderer>;
|
||||
mxMatrixClientPeg: IMatrixClientPeg;
|
||||
Olm: {
|
||||
init: () => Promise<void>;
|
||||
|
|
|
@ -70,6 +70,7 @@ interface IContent {
|
|||
|
||||
interface IThumbnail {
|
||||
info: {
|
||||
// eslint-disable-next-line camelcase
|
||||
thumbnail_info: {
|
||||
w: number;
|
||||
h: number;
|
||||
|
@ -104,7 +105,12 @@ interface IAbortablePromise<T> extends Promise<T> {
|
|||
* @return {Promise} A promise that resolves with an object with an info key
|
||||
* and a thumbnail key.
|
||||
*/
|
||||
function createThumbnail(element: ThumbnailableElement, inputWidth: number, inputHeight: number, mimeType: string): Promise<IThumbnail> {
|
||||
function createThumbnail(
|
||||
element: ThumbnailableElement,
|
||||
inputWidth: number,
|
||||
inputHeight: number,
|
||||
mimeType: string,
|
||||
): Promise<IThumbnail> {
|
||||
return new Promise((resolve) => {
|
||||
let targetWidth = inputWidth;
|
||||
let targetHeight = inputHeight;
|
||||
|
@ -437,11 +443,13 @@ export default class ContentMessages {
|
|||
for (let i = 0; i < okFiles.length; ++i) {
|
||||
const file = okFiles[i];
|
||||
if (!uploadAll) {
|
||||
const {finished} = Modal.createTrackedDialog<[boolean, boolean]>('Upload Files confirmation', '', UploadConfirmDialog, {
|
||||
file,
|
||||
currentIndex: i,
|
||||
totalFiles: okFiles.length,
|
||||
});
|
||||
const {finished} = Modal.createTrackedDialog<[boolean, boolean]>('Upload Files confirmation',
|
||||
'', UploadConfirmDialog, {
|
||||
file,
|
||||
currentIndex: i,
|
||||
totalFiles: okFiles.length,
|
||||
},
|
||||
);
|
||||
const [shouldContinue, shouldUploadAll] = await finished;
|
||||
if (!shouldContinue) break;
|
||||
if (shouldUploadAll) {
|
||||
|
|
|
@ -21,6 +21,7 @@ import { deriveKey } from 'matrix-js-sdk/src/crypto/key_passphrase';
|
|||
import { decodeRecoveryKey } from 'matrix-js-sdk/src/crypto/recoverykey';
|
||||
import { _t } from './languageHandler';
|
||||
import {encodeBase64} from "matrix-js-sdk/src/crypto/olmlib";
|
||||
import { isSecureBackupRequired } from './utils/WellKnownUtils';
|
||||
|
||||
// This stores the secret storage private keys in memory for the JS SDK. This is
|
||||
// only meant to act as a cache to avoid prompting the user multiple times
|
||||
|
@ -34,6 +35,17 @@ function isCachingAllowed() {
|
|||
return secretStorageBeingAccessed;
|
||||
}
|
||||
|
||||
/**
|
||||
* This can be used by other components to check if secret storage access is in
|
||||
* progress, so that we can e.g. avoid intermittently showing toasts during
|
||||
* secret storage setup.
|
||||
*
|
||||
* @returns {bool}
|
||||
*/
|
||||
export function isSecretStorageBeingAccessed() {
|
||||
return secretStorageBeingAccessed;
|
||||
}
|
||||
|
||||
export class AccessCancelledError extends Error {
|
||||
constructor() {
|
||||
super("Secret storage access canceled");
|
||||
|
@ -57,19 +69,19 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) {
|
|||
if (keyInfoEntries.length > 1) {
|
||||
throw new Error("Multiple storage key requests not implemented");
|
||||
}
|
||||
const [name, info] = keyInfoEntries[0];
|
||||
const [keyId, keyInfo] = keyInfoEntries[0];
|
||||
|
||||
// Check the in-memory cache
|
||||
if (isCachingAllowed() && secretStorageKeys[name]) {
|
||||
return [name, secretStorageKeys[name]];
|
||||
if (isCachingAllowed() && secretStorageKeys[keyId]) {
|
||||
return [keyId, secretStorageKeys[keyId]];
|
||||
}
|
||||
|
||||
const inputToKey = async ({ passphrase, recoveryKey }) => {
|
||||
if (passphrase) {
|
||||
return deriveKey(
|
||||
passphrase,
|
||||
info.passphrase.salt,
|
||||
info.passphrase.iterations,
|
||||
keyInfo.passphrase.salt,
|
||||
keyInfo.passphrase.iterations,
|
||||
);
|
||||
} else {
|
||||
return decodeRecoveryKey(recoveryKey);
|
||||
|
@ -81,10 +93,10 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) {
|
|||
AccessSecretStorageDialog,
|
||||
/* props= */
|
||||
{
|
||||
keyInfo: info,
|
||||
keyInfo,
|
||||
checkPrivateKey: async (input) => {
|
||||
const key = await inputToKey(input);
|
||||
return await MatrixClientPeg.get().checkSecretStorageKey(key, info);
|
||||
return await MatrixClientPeg.get().checkSecretStorageKey(key, keyInfo);
|
||||
},
|
||||
},
|
||||
/* className= */ null,
|
||||
|
@ -106,11 +118,15 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) {
|
|||
const key = await inputToKey(input);
|
||||
|
||||
// Save to cache to avoid future prompts in the current session
|
||||
if (isCachingAllowed()) {
|
||||
secretStorageKeys[name] = key;
|
||||
}
|
||||
cacheSecretStorageKey(keyId, key);
|
||||
|
||||
return [name, key];
|
||||
return [keyId, key];
|
||||
}
|
||||
|
||||
function cacheSecretStorageKey(keyId, key) {
|
||||
if (isCachingAllowed()) {
|
||||
secretStorageKeys[keyId] = key;
|
||||
}
|
||||
}
|
||||
|
||||
const onSecretRequested = async function({
|
||||
|
@ -158,6 +174,7 @@ const onSecretRequested = async function({
|
|||
|
||||
export const crossSigningCallbacks = {
|
||||
getSecretStorageKey,
|
||||
cacheSecretStorageKey,
|
||||
onSecretRequested,
|
||||
};
|
||||
|
||||
|
@ -206,9 +223,20 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f
|
|||
const { finished } = Modal.createTrackedDialogAsync('Create Secret Storage dialog', '',
|
||||
import("./async-components/views/dialogs/secretstorage/CreateSecretStorageDialog"),
|
||||
{
|
||||
force: forceReset,
|
||||
forceReset,
|
||||
},
|
||||
null,
|
||||
/* priority = */ false,
|
||||
/* static = */ true,
|
||||
/* options = */ {
|
||||
onBeforeClose(reason) {
|
||||
// If Secure Backup is required, you cannot leave the modal.
|
||||
if (reason === "backgroundClick") {
|
||||
return !isSecureBackupRequired();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
null, /* priority = */ false, /* static = */ true,
|
||||
);
|
||||
const [confirmed] = await finished;
|
||||
if (!confirmed) {
|
||||
|
@ -216,7 +244,7 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f
|
|||
}
|
||||
} else {
|
||||
const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
|
||||
await cli.bootstrapSecretStorage({
|
||||
await cli.bootstrapCrossSigning({
|
||||
authUploadDeviceSigningKeys: async (makeRequest) => {
|
||||
const { finished } = Modal.createTrackedDialog(
|
||||
'Cross-signing keys dialog', '', InteractiveAuthDialog,
|
||||
|
@ -231,7 +259,9 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f
|
|||
throw new Error("Cross-signing key upload auth canceled");
|
||||
}
|
||||
},
|
||||
getBackupPassphrase: promptForBackupPassphrase,
|
||||
});
|
||||
await cli.bootstrapSecretStorage({
|
||||
getKeyBackupPassphrase: promptForBackupPassphrase,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import {MatrixClientPeg} from './MatrixClientPeg';
|
||||
import dis from "./dispatcher/dispatcher";
|
||||
import {
|
||||
hideToast as hideBulkUnverifiedSessionsToast,
|
||||
showToast as showBulkUnverifiedSessionsToast,
|
||||
|
@ -28,11 +29,16 @@ import {
|
|||
hideToast as hideUnverifiedSessionsToast,
|
||||
showToast as showUnverifiedSessionsToast,
|
||||
} from "./toasts/UnverifiedSessionToast";
|
||||
import {privateShouldBeEncrypted} from "./createRoom";
|
||||
import { privateShouldBeEncrypted } from "./createRoom";
|
||||
import { isSecretStorageBeingAccessed, accessSecretStorage } from "./CrossSigningManager";
|
||||
import { isSecureBackupRequired } from './utils/WellKnownUtils';
|
||||
import { isLoggedIn } from './components/structures/MatrixChat';
|
||||
|
||||
|
||||
const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;
|
||||
|
||||
export default class DeviceListener {
|
||||
private dispatcherRef: string;
|
||||
// device IDs for which the user has dismissed the verify toast ('Later')
|
||||
private dismissed = new Set<string>();
|
||||
// has the user dismissed any of the various nag toasts to setup encryption on this device?
|
||||
|
@ -60,6 +66,7 @@ export default class DeviceListener {
|
|||
MatrixClientPeg.get().on('crossSigning.keysChanged', this._onCrossSingingKeysChanged);
|
||||
MatrixClientPeg.get().on('accountData', this._onAccountData);
|
||||
MatrixClientPeg.get().on('sync', this._onSync);
|
||||
this.dispatcherRef = dis.register(this._onAction);
|
||||
this._recheck();
|
||||
}
|
||||
|
||||
|
@ -73,6 +80,10 @@ export default class DeviceListener {
|
|||
MatrixClientPeg.get().removeListener('accountData', this._onAccountData);
|
||||
MatrixClientPeg.get().removeListener('sync', this._onSync);
|
||||
}
|
||||
if (this.dispatcherRef) {
|
||||
dis.unregister(this.dispatcherRef);
|
||||
this.dispatcherRef = null;
|
||||
}
|
||||
this.dismissed.clear();
|
||||
this.dismissedThisDeviceToast = false;
|
||||
this.keyBackupInfo = null;
|
||||
|
@ -158,6 +169,11 @@ export default class DeviceListener {
|
|||
if (state === 'PREPARED' && prevState === null) this._recheck();
|
||||
};
|
||||
|
||||
_onAction = ({ action }) => {
|
||||
if (action !== "on_logged_in") return;
|
||||
this._recheck();
|
||||
};
|
||||
|
||||
// The server doesn't tell us when key backup is set up, so we poll
|
||||
// & cache the result
|
||||
async _getKeyBackupInfo() {
|
||||
|
@ -170,6 +186,9 @@ export default class DeviceListener {
|
|||
}
|
||||
|
||||
private shouldShowSetupEncryptionToast() {
|
||||
// If we're in the middle of a secret storage operation, we're likely
|
||||
// modifying the state involved here, so don't add new toasts to setup.
|
||||
if (isSecretStorageBeingAccessed()) return false;
|
||||
// In a default configuration, show the toasts. If the well-known config causes e2ee default to be false
|
||||
// then do not show the toasts until user is in at least one encrypted room.
|
||||
if (privateShouldBeEncrypted()) return true;
|
||||
|
@ -188,9 +207,13 @@ export default class DeviceListener {
|
|||
// (we add a listener on sync to do once check after the initial sync is done)
|
||||
if (!cli.isInitialSyncComplete()) return;
|
||||
|
||||
// JRS: This will change again in the next PR which moves secret storage
|
||||
// later in the process.
|
||||
const crossSigningReady = await cli.isCrossSigningReady();
|
||||
const secretStorageReady = await cli.isSecretStorageReady();
|
||||
const allSystemsReady = crossSigningReady && secretStorageReady;
|
||||
|
||||
if (this.dismissedThisDeviceToast || crossSigningReady) {
|
||||
if (this.dismissedThisDeviceToast || allSystemsReady) {
|
||||
hideSetupEncryptionToast();
|
||||
} else if (this.shouldShowSetupEncryptionToast()) {
|
||||
// make sure our keys are finished downloading
|
||||
|
@ -207,7 +230,15 @@ export default class DeviceListener {
|
|||
showSetupEncryptionToast(SetupKind.UPGRADE_ENCRYPTION);
|
||||
} else {
|
||||
// No cross-signing or key backup on account (set up encryption)
|
||||
showSetupEncryptionToast(SetupKind.SET_UP_ENCRYPTION);
|
||||
await cli.waitForClientWellKnown();
|
||||
if (isSecureBackupRequired() && isLoggedIn()) {
|
||||
// If we're meant to set up, and Secure Backup is required,
|
||||
// trigger the flow directly without a toast once logged in.
|
||||
hideSetupEncryptionToast();
|
||||
accessSecretStorage();
|
||||
} else {
|
||||
showSetupEncryptionToast(SetupKind.SET_UP_ENCRYPTION);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -339,33 +339,9 @@ class HtmlHighlighter extends BaseHighlighter<string> {
|
|||
}
|
||||
}
|
||||
|
||||
class TextHighlighter extends BaseHighlighter<React.ReactNode> {
|
||||
private key = 0;
|
||||
|
||||
/* create a <span> node to hold the given content
|
||||
*
|
||||
* snippet: content of the span
|
||||
* highlight: true to highlight as a search match
|
||||
*
|
||||
* returns a React node
|
||||
*/
|
||||
protected processSnippet(snippet: string, highlight: boolean): React.ReactNode {
|
||||
const key = this.key++;
|
||||
|
||||
let node = <span key={key} className={highlight ? this.highlightClass : null}>
|
||||
{ snippet }
|
||||
</span>;
|
||||
|
||||
if (highlight && this.highlightLink) {
|
||||
node = <a key={key} href={this.highlightLink}>{ node }</a>;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
interface IContent {
|
||||
format?: string;
|
||||
// eslint-disable-next-line camelcase
|
||||
formatted_body?: string;
|
||||
body: string;
|
||||
}
|
||||
|
@ -474,8 +450,13 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts
|
|||
});
|
||||
|
||||
return isDisplayedWithHtml ?
|
||||
<span key="body" ref={opts.ref} className={className} dangerouslySetInnerHTML={{ __html: safeBody }} dir="auto" /> :
|
||||
<span key="body" ref={opts.ref} className={className} dir="auto">{ strippedBody }</span>;
|
||||
<span
|
||||
key="body"
|
||||
ref={opts.ref}
|
||||
className={className}
|
||||
dangerouslySetInnerHTML={{ __html: safeBody }}
|
||||
dir="auto"
|
||||
/> : <span key="body" ref={opts.ref} className={className} dir="auto">{ strippedBody }</span>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -151,7 +151,7 @@ export class ModalManager {
|
|||
prom: Promise<React.ComponentType>,
|
||||
props?: IProps<T>,
|
||||
className?: string,
|
||||
options?: IOptions<T>
|
||||
options?: IOptions<T>,
|
||||
) {
|
||||
const modal: IModal<T> = {
|
||||
onFinished: props ? props.onFinished : null,
|
||||
|
@ -182,7 +182,7 @@ export class ModalManager {
|
|||
|
||||
private getCloseFn<T extends any[]>(
|
||||
modal: IModal<T>,
|
||||
props: IProps<T>
|
||||
props: IProps<T>,
|
||||
): [IHandle<T>["close"], IHandle<T>["finished"]] {
|
||||
const deferred = defer<T>();
|
||||
return [async (...args: T) => {
|
||||
|
@ -264,7 +264,7 @@ export class ModalManager {
|
|||
className?: string,
|
||||
isPriorityModal = false,
|
||||
isStaticModal = false,
|
||||
options: IOptions<T> = {}
|
||||
options: IOptions<T> = {},
|
||||
): IHandle<T> {
|
||||
const {modal, closeDialog, onFinishedProm} = this.buildModal<T>(prom, props, className, options);
|
||||
if (isPriorityModal) {
|
||||
|
@ -287,7 +287,7 @@ export class ModalManager {
|
|||
private appendDialogAsync<T extends any[]>(
|
||||
prom: Promise<React.ComponentType>,
|
||||
props?: IProps<T>,
|
||||
className?: string
|
||||
className?: string,
|
||||
): IHandle<T> {
|
||||
const {modal, closeDialog, onFinishedProm} = this.buildModal<T>(prom, props, className, {});
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import Modal from './Modal';
|
|||
import * as sdk from './';
|
||||
import { _t } from './languageHandler';
|
||||
import {KIND_DM, KIND_INVITE} from "./components/views/dialogs/InviteDialog";
|
||||
import CommunityPrototypeInviteDialog from "./components/views/dialogs/CommunityPrototypeInviteDialog";
|
||||
|
||||
/**
|
||||
* Invites multiple addresses to a room
|
||||
|
@ -56,6 +57,13 @@ export function showRoomInviteDialog(roomId) {
|
|||
);
|
||||
}
|
||||
|
||||
export function showCommunityRoomInviteDialog(roomId, communityName) {
|
||||
Modal.createTrackedDialog(
|
||||
'Invite Users to Community', '', CommunityPrototypeInviteDialog, {communityName, roomId},
|
||||
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given MatrixEvent is a valid 3rd party user invite.
|
||||
* @param {MatrixEvent} event The event to check
|
||||
|
@ -77,7 +85,7 @@ export function isValid3pidInvite(event) {
|
|||
export function inviteUsersToRoom(roomId, userIds) {
|
||||
return inviteMultipleToRoom(roomId, userIds).then((result) => {
|
||||
const room = MatrixClientPeg.get().getRoom(roomId);
|
||||
return _showAnyInviteErrors(result.states, room, result.inviter);
|
||||
showAnyInviteErrors(result.states, room, result.inviter);
|
||||
}).catch((err) => {
|
||||
console.error(err.stack);
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
|
@ -88,7 +96,7 @@ export function inviteUsersToRoom(roomId, userIds) {
|
|||
});
|
||||
}
|
||||
|
||||
function _showAnyInviteErrors(addrs, room, inviter) {
|
||||
export function showAnyInviteErrors(addrs, room, inviter) {
|
||||
// Show user any errors
|
||||
const failedUsers = Object.keys(addrs).filter(a => addrs[a] === 'error');
|
||||
if (failedUsers.length === 1 && inviter.fatal) {
|
||||
|
@ -100,6 +108,7 @@ function _showAnyInviteErrors(addrs, room, inviter) {
|
|||
title: _t("Failed to invite users to the room:", {roomName: room.name}),
|
||||
description: inviter.getErrorText(failedUsers[0]),
|
||||
});
|
||||
return false;
|
||||
} else {
|
||||
const errorList = [];
|
||||
for (const addr of failedUsers) {
|
||||
|
@ -118,8 +127,9 @@ function _showAnyInviteErrors(addrs, room, inviter) {
|
|||
title: _t("Failed to invite the following users to the %(roomName)s room:", {roomName: room.name}),
|
||||
description,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return addrs;
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -860,12 +860,12 @@ export const Commands = [
|
|||
_t('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!',
|
||||
{
|
||||
fprint,
|
||||
userId,
|
||||
deviceId,
|
||||
fingerprint,
|
||||
}));
|
||||
{
|
||||
fprint,
|
||||
userId,
|
||||
deviceId,
|
||||
fingerprint,
|
||||
}));
|
||||
}
|
||||
|
||||
await cli.setDeviceVerified(userId, deviceId, true);
|
||||
|
@ -879,7 +879,7 @@ export const Commands = [
|
|||
{
|
||||
_t('The signing key you provided matches the signing key you received ' +
|
||||
'from %(userId)s\'s session %(deviceId)s. Session marked as verified.',
|
||||
{userId, deviceId})
|
||||
{userId, deviceId})
|
||||
}
|
||||
</p>
|
||||
</div>,
|
||||
|
|
|
@ -168,7 +168,7 @@ const shortcuts: Record<Categories, IShortcut[]> = {
|
|||
key: Key.U,
|
||||
}],
|
||||
description: _td("Upload a file"),
|
||||
}
|
||||
},
|
||||
],
|
||||
|
||||
[Categories.ROOM_LIST]: [
|
||||
|
|
|
@ -190,7 +190,7 @@ export const RovingTabIndexProvider: React.FC<IProps> = ({children, handleHomeEn
|
|||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
} else if (onKeyDown) {
|
||||
return onKeyDown(ev, state);
|
||||
return onKeyDown(ev, context.state);
|
||||
}
|
||||
}, [context.state, onKeyDown, handleHomeEnd]);
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ const Toolbar: React.FC<IProps> = ({children, ...props}) => {
|
|||
const target = ev.target as HTMLElement;
|
||||
let handled = true;
|
||||
|
||||
// HOME and END are handled by RovingTabIndexProvider
|
||||
switch (ev.key) {
|
||||
case Key.ARROW_UP:
|
||||
case Key.ARROW_DOWN:
|
||||
|
@ -47,8 +48,6 @@ const Toolbar: React.FC<IProps> = ({children, ...props}) => {
|
|||
}
|
||||
break;
|
||||
|
||||
// HOME and END are handled by RovingTabIndexProvider
|
||||
|
||||
default:
|
||||
handled = false;
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ import React from "react";
|
|||
|
||||
import AccessibleTooltipButton from "../../components/views/elements/AccessibleTooltipButton";
|
||||
|
||||
interface IProps extends React.ComponentProps<typeof AccessibleTooltipButton> {
|
||||
interface IProps extends React.ComponentProps<typeof AccessibleTooltipButton> {
|
||||
// whether or not the context menu is currently open
|
||||
isExpanded: boolean;
|
||||
}
|
||||
|
|
|
@ -20,7 +20,8 @@ import AccessibleTooltipButton from "../../components/views/elements/AccessibleT
|
|||
import {useRovingTabIndex} from "../RovingTabIndex";
|
||||
import {Ref} from "./types";
|
||||
|
||||
interface IProps extends Omit<React.ComponentProps<typeof AccessibleTooltipButton>, "onFocus" | "inputRef" | "tabIndex"> {
|
||||
type ATBProps = React.ComponentProps<typeof AccessibleTooltipButton>;
|
||||
interface IProps extends Omit<ATBProps, "onFocus" | "inputRef" | "tabIndex"> {
|
||||
inputRef?: Ref;
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@ limitations under the License.
|
|||
|
||||
import React from "react";
|
||||
|
||||
import AccessibleButton from "../../components/views/elements/AccessibleButton";
|
||||
import {useRovingTabIndex} from "../RovingTabIndex";
|
||||
import {FocusHandler, Ref} from "./types";
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ import StyledRadioButton from '../../../../components/views/elements/StyledRadio
|
|||
import AccessibleButton from "../../../../components/views/elements/AccessibleButton";
|
||||
import DialogButtons from "../../../../components/views/elements/DialogButtons";
|
||||
import InlineSpinner from "../../../../components/views/elements/InlineSpinner";
|
||||
import { isSecureBackupRequired } from '../../../../utils/WellKnownUtils';
|
||||
|
||||
const PHASE_LOADING = 0;
|
||||
const PHASE_LOADERROR = 1;
|
||||
|
@ -55,12 +56,12 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
static propTypes = {
|
||||
hasCancel: PropTypes.bool,
|
||||
accountPassword: PropTypes.string,
|
||||
force: PropTypes.bool,
|
||||
forceReset: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
hasCancel: true,
|
||||
force: false,
|
||||
forceReset: false,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
@ -85,8 +86,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
canUploadKeysWithPasswordOnly: null,
|
||||
accountPassword: props.accountPassword || "",
|
||||
accountPasswordCorrect: null,
|
||||
|
||||
passPhraseKeySelected: CREATE_STORAGE_OPTION_KEY,
|
||||
canSkip: !isSecureBackupRequired(),
|
||||
};
|
||||
|
||||
this._passphraseField = createRef();
|
||||
|
@ -117,8 +118,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
MatrixClientPeg.get().isCryptoEnabled() && await MatrixClientPeg.get().isKeyBackupTrusted(backupInfo)
|
||||
);
|
||||
|
||||
const { force } = this.props;
|
||||
const phase = (backupInfo && !force) ? PHASE_MIGRATE : PHASE_CHOOSE_KEY_PASSPHRASE;
|
||||
const { forceReset } = this.props;
|
||||
const phase = (backupInfo && !forceReset) ? PHASE_MIGRATE : PHASE_CHOOSE_KEY_PASSPHRASE;
|
||||
|
||||
this.setState({
|
||||
phase,
|
||||
|
@ -276,20 +277,25 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
|
||||
const cli = MatrixClientPeg.get();
|
||||
|
||||
const { force } = this.props;
|
||||
const { forceReset } = this.props;
|
||||
|
||||
try {
|
||||
if (force) {
|
||||
console.log("Forcing secret storage reset"); // log something so we can debug this later
|
||||
if (forceReset) {
|
||||
console.log("Forcing cross-signing and secret storage reset");
|
||||
await cli.bootstrapSecretStorage({
|
||||
authUploadDeviceSigningKeys: this._doBootstrapUIAuth,
|
||||
createSecretStorageKey: async () => this._recoveryKey,
|
||||
setupNewKeyBackup: true,
|
||||
setupNewSecretStorage: true,
|
||||
});
|
||||
} else {
|
||||
await cli.bootstrapSecretStorage({
|
||||
await cli.bootstrapCrossSigning({
|
||||
authUploadDeviceSigningKeys: this._doBootstrapUIAuth,
|
||||
setupNewCrossSigning: true,
|
||||
});
|
||||
} else {
|
||||
await cli.bootstrapCrossSigning({
|
||||
authUploadDeviceSigningKeys: this._doBootstrapUIAuth,
|
||||
});
|
||||
await cli.bootstrapSecretStorage({
|
||||
createSecretStorageKey: async () => this._recoveryKey,
|
||||
keyBackupInfo: this.state.backupInfo,
|
||||
setupNewKeyBackup: !this.state.backupInfo,
|
||||
|
@ -470,7 +476,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
primaryButton={_t("Continue")}
|
||||
onPrimaryButtonClick={this._onChooseKeyPassphraseFormSubmit}
|
||||
onCancel={this._onCancelClick}
|
||||
hasCancel={true}
|
||||
hasCancel={this.state.canSkip}
|
||||
/>
|
||||
</form>;
|
||||
}
|
||||
|
@ -687,7 +693,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
<div className="mx_Dialog_buttons">
|
||||
<DialogButtons primaryButton={_t('Retry')}
|
||||
onPrimaryButtonClick={this._onLoadRetryClick}
|
||||
hasCancel={true}
|
||||
hasCancel={this.state.canSkip}
|
||||
onCancel={this._onCancel}
|
||||
/>
|
||||
</div>
|
||||
|
@ -714,7 +720,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
_titleForPhase(phase) {
|
||||
switch (phase) {
|
||||
case PHASE_CHOOSE_KEY_PASSPHRASE:
|
||||
return _t('Set up Secure backup');
|
||||
return _t('Set up Secure Backup');
|
||||
case PHASE_MIGRATE:
|
||||
return _t('Upgrade your encryption');
|
||||
case PHASE_PASSPHRASE:
|
||||
|
@ -742,7 +748,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
<div className="mx_Dialog_buttons">
|
||||
<DialogButtons primaryButton={_t('Retry')}
|
||||
onPrimaryButtonClick={this._bootstrapSecretStorage}
|
||||
hasCancel={true}
|
||||
hasCancel={this.state.canSkip}
|
||||
onCancel={this._onCancel}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -89,7 +89,11 @@ export default class CommandProvider extends AutocompleteProvider {
|
|||
|
||||
renderCompletions(completions: React.ReactNode[]): React.ReactNode {
|
||||
return (
|
||||
<div className="mx_Autocomplete_Completion_container_block" role="listbox" aria-label={_t("Command Autocomplete")}>
|
||||
<div
|
||||
className="mx_Autocomplete_Completion_container_block"
|
||||
role="listbox"
|
||||
aria-label={_t("Command Autocomplete")}
|
||||
>
|
||||
{ completions }
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -91,15 +91,15 @@ export default class CommunityProvider extends AutocompleteProvider {
|
|||
href: makeGroupPermalink(groupId),
|
||||
component: (
|
||||
<PillCompletion title={name} description={groupId}>
|
||||
<BaseAvatar name={name || groupId}
|
||||
width={24}
|
||||
height={24}
|
||||
url={avatarUrl ? cli.mxcUrlToHttp(avatarUrl, 24, 24) : null} />
|
||||
<BaseAvatar
|
||||
name={name || groupId}
|
||||
width={24}
|
||||
height={24}
|
||||
url={avatarUrl ? cli.mxcUrlToHttp(avatarUrl, 24, 24) : null} />
|
||||
</PillCompletion>
|
||||
),
|
||||
range,
|
||||
}))
|
||||
.slice(0, 4);
|
||||
})).slice(0, 4);
|
||||
}
|
||||
return completions;
|
||||
}
|
||||
|
|
|
@ -34,9 +34,9 @@ export const TextualCompletion = forwardRef<ITextualCompletionProps, any>((props
|
|||
const {title, subtitle, description, className, ...restProps} = props;
|
||||
return (
|
||||
<div {...restProps}
|
||||
className={classNames('mx_Autocomplete_Completion_block', className)}
|
||||
role="option"
|
||||
ref={ref}
|
||||
className={classNames('mx_Autocomplete_Completion_block', className)}
|
||||
role="option"
|
||||
ref={ref}
|
||||
>
|
||||
<span className="mx_Autocomplete_Completion_title">{ title }</span>
|
||||
<span className="mx_Autocomplete_Completion_subtitle">{ subtitle }</span>
|
||||
|
@ -53,9 +53,9 @@ export const PillCompletion = forwardRef<IPillCompletionProps, any>((props, ref)
|
|||
const {title, subtitle, description, className, children, ...restProps} = props;
|
||||
return (
|
||||
<div {...restProps}
|
||||
className={classNames('mx_Autocomplete_Completion_pill', className)}
|
||||
role="option"
|
||||
ref={ref}
|
||||
className={classNames('mx_Autocomplete_Completion_pill', className)}
|
||||
role="option"
|
||||
ref={ref}
|
||||
>
|
||||
{ children }
|
||||
<span className="mx_Autocomplete_Completion_title">{ title }</span>
|
||||
|
|
|
@ -139,7 +139,11 @@ export default class EmojiProvider extends AutocompleteProvider {
|
|||
|
||||
renderCompletions(completions: React.ReactNode[]): React.ReactNode {
|
||||
return (
|
||||
<div className="mx_Autocomplete_Completion_container_pill" role="listbox" aria-label={_t("Emoji Autocomplete")}>
|
||||
<div
|
||||
className="mx_Autocomplete_Completion_container_pill"
|
||||
role="listbox"
|
||||
aria-label={_t("Emoji Autocomplete")}
|
||||
>
|
||||
{ completions }
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -110,9 +110,7 @@ export default class RoomProvider extends AutocompleteProvider {
|
|||
),
|
||||
range,
|
||||
};
|
||||
})
|
||||
.filter((completion) => !!completion.completion && completion.completion.length > 0)
|
||||
.slice(0, 4);
|
||||
}).filter((completion) => !!completion.completion && completion.completion.length > 0).slice(0, 4);
|
||||
}
|
||||
return completions;
|
||||
}
|
||||
|
|
|
@ -71,8 +71,13 @@ export default class UserProvider extends AutocompleteProvider {
|
|||
}
|
||||
}
|
||||
|
||||
private onRoomTimeline = (ev: MatrixEvent, room: Room, toStartOfTimeline: boolean, removed: boolean,
|
||||
data: IRoomTimelineData) => {
|
||||
private onRoomTimeline = (
|
||||
ev: MatrixEvent,
|
||||
room: Room,
|
||||
toStartOfTimeline: boolean,
|
||||
removed: boolean,
|
||||
data: IRoomTimelineData,
|
||||
) => {
|
||||
if (!room) return;
|
||||
if (removed) return;
|
||||
if (room.roomId !== this.room.roomId) return;
|
||||
|
@ -171,7 +176,11 @@ export default class UserProvider extends AutocompleteProvider {
|
|||
|
||||
renderCompletions(completions: React.ReactNode[]): React.ReactNode {
|
||||
return (
|
||||
<div className="mx_Autocomplete_Completion_container_pill" role="listbox" aria-label={_t("User Autocomplete")}>
|
||||
<div
|
||||
className="mx_Autocomplete_Completion_container_pill"
|
||||
role="listbox"
|
||||
aria-label={_t("User Autocomplete")}
|
||||
>
|
||||
{ completions }
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,90 +0,0 @@
|
|||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
||||
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
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 createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
import { _t } from '../../languageHandler';
|
||||
import SdkConfig from '../../SdkConfig';
|
||||
|
||||
export default createReactClass({
|
||||
displayName: 'CompatibilityPage',
|
||||
propTypes: {
|
||||
onAccept: PropTypes.func,
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
onAccept: function() {}, // NOP
|
||||
};
|
||||
},
|
||||
|
||||
onAccept: function() {
|
||||
this.props.onAccept();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const brand = SdkConfig.get().brand;
|
||||
|
||||
return (
|
||||
<div className="mx_CompatibilityPage">
|
||||
<div className="mx_CompatibilityPage_box">
|
||||
<p>{_t(
|
||||
"Sorry, your browser is <b>not</b> able to run %(brand)s.",
|
||||
{
|
||||
brand,
|
||||
},
|
||||
{
|
||||
'b': (sub) => <b>{sub}</b>,
|
||||
})
|
||||
}</p>
|
||||
<p>
|
||||
{ _t(
|
||||
"%(brand)s uses many advanced browser features, some of which are not available " +
|
||||
"or experimental in your current browser.",
|
||||
{ brand },
|
||||
) }
|
||||
</p>
|
||||
<p>
|
||||
{ _t(
|
||||
'Please install <chromeLink>Chrome</chromeLink>, <firefoxLink>Firefox</firefoxLink>, ' +
|
||||
'or <safariLink>Safari</safariLink> for the best experience.',
|
||||
{},
|
||||
{
|
||||
'chromeLink': (sub) => <a href="https://www.google.com/chrome">{sub}</a>,
|
||||
'firefoxLink': (sub) => <a href="https://firefox.com">{sub}</a>,
|
||||
'safariLink': (sub) => <a href="https://apple.com/safari">{sub}</a>,
|
||||
},
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
{ _t(
|
||||
"With your current browser, the look and feel of the application may be " +
|
||||
"completely incorrect, and some or all features may not function. " +
|
||||
"If you want to try it anyway you can continue, but you are on your own in terms " +
|
||||
"of any issues you may encounter!",
|
||||
) }
|
||||
</p>
|
||||
<button onClick={this.onAccept}>
|
||||
{ _t("I understand the risks and wish to continue") }
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
|
@ -233,8 +233,7 @@ export class ContextMenu extends React.PureComponent<IProps, IState> {
|
|||
switch (ev.key) {
|
||||
case Key.TAB:
|
||||
case Key.ESCAPE:
|
||||
// close on left and right arrows too for when it is a context menu on a <Toolbar />
|
||||
case Key.ARROW_LEFT:
|
||||
case Key.ARROW_LEFT: // close on left and right arrows too for when it is a context menu on a <Toolbar />
|
||||
case Key.ARROW_RIGHT:
|
||||
this.props.onFinished();
|
||||
break;
|
||||
|
|
|
@ -377,7 +377,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
|||
public render(): React.ReactNode {
|
||||
const tagPanel = !this.state.showTagPanel ? null : (
|
||||
<div className="mx_LeftPanel_tagPanelContainer">
|
||||
<TagPanel/>
|
||||
<TagPanel />
|
||||
{SettingsStore.getValue("feature_custom_tags") ? <CustomRoomTagPanel /> : null}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -43,11 +43,11 @@ import PlatformPeg from "../../PlatformPeg";
|
|||
import { DefaultTagID } from "../../stores/room-list/models";
|
||||
import {
|
||||
showToast as showSetPasswordToast,
|
||||
hideToast as hideSetPasswordToast
|
||||
hideToast as hideSetPasswordToast,
|
||||
} from "../../toasts/SetPasswordToast";
|
||||
import {
|
||||
showToast as showServerLimitToast,
|
||||
hideToast as hideServerLimitToast
|
||||
hideToast as hideServerLimitToast,
|
||||
} from "../../toasts/ServerLimitToast";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
import LeftPanel from "./LeftPanel";
|
||||
|
@ -79,6 +79,7 @@ interface IProps {
|
|||
initialEventPixelOffset: number;
|
||||
leftDisabled: boolean;
|
||||
rightDisabled: boolean;
|
||||
// eslint-disable-next-line camelcase
|
||||
page_type: string;
|
||||
autoJoin: boolean;
|
||||
thirdPartyInvite?: object;
|
||||
|
@ -98,7 +99,9 @@ interface IProps {
|
|||
}
|
||||
|
||||
interface IUsageLimit {
|
||||
// eslint-disable-next-line camelcase
|
||||
limit_type: "monthly_active_user" | string;
|
||||
// eslint-disable-next-line camelcase
|
||||
admin_contact?: string;
|
||||
}
|
||||
|
||||
|
@ -316,10 +319,10 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
}
|
||||
};
|
||||
|
||||
_calculateServerLimitToast(syncErrorData: IState["syncErrorData"], usageLimitEventContent?: IUsageLimit) {
|
||||
const error = syncErrorData && syncErrorData.error && syncErrorData.error.errcode === "M_RESOURCE_LIMIT_EXCEEDED";
|
||||
_calculateServerLimitToast(syncError: IState["syncErrorData"], usageLimitEventContent?: IUsageLimit) {
|
||||
const error = syncError && syncError.error && syncError.error.errcode === "M_RESOURCE_LIMIT_EXCEEDED";
|
||||
if (error) {
|
||||
usageLimitEventContent = syncErrorData.error.data;
|
||||
usageLimitEventContent = syncError.error.data;
|
||||
}
|
||||
|
||||
if (usageLimitEventContent) {
|
||||
|
@ -620,18 +623,18 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
switch (this.props.page_type) {
|
||||
case PageTypes.RoomView:
|
||||
pageElement = <RoomView
|
||||
ref={this._roomView}
|
||||
autoJoin={this.props.autoJoin}
|
||||
onRegistered={this.props.onRegistered}
|
||||
thirdPartyInvite={this.props.thirdPartyInvite}
|
||||
oobData={this.props.roomOobData}
|
||||
viaServers={this.props.viaServers}
|
||||
eventPixelOffset={this.props.initialEventPixelOffset}
|
||||
key={this.props.currentRoomId || 'roomview'}
|
||||
disabled={this.props.middleDisabled}
|
||||
ConferenceHandler={this.props.ConferenceHandler}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
/>;
|
||||
ref={this._roomView}
|
||||
autoJoin={this.props.autoJoin}
|
||||
onRegistered={this.props.onRegistered}
|
||||
thirdPartyInvite={this.props.thirdPartyInvite}
|
||||
oobData={this.props.roomOobData}
|
||||
viaServers={this.props.viaServers}
|
||||
eventPixelOffset={this.props.initialEventPixelOffset}
|
||||
key={this.props.currentRoomId || 'roomview'}
|
||||
disabled={this.props.middleDisabled}
|
||||
ConferenceHandler={this.props.ConferenceHandler}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
/>;
|
||||
break;
|
||||
|
||||
case PageTypes.MyGroups:
|
||||
|
|
|
@ -69,7 +69,7 @@ import { ViewUserPayload } from "../../dispatcher/payloads/ViewUserPayload";
|
|||
import { Action } from "../../dispatcher/actions";
|
||||
import {
|
||||
showToast as showAnalyticsToast,
|
||||
hideToast as hideAnalyticsToast
|
||||
hideToast as hideAnalyticsToast,
|
||||
} from "../../toasts/AnalyticsToast";
|
||||
import {showToast as showNotificationsToast} from "../../toasts/DesktopNotificationsToast";
|
||||
import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
|
||||
|
@ -77,6 +77,7 @@ import ErrorDialog from "../views/dialogs/ErrorDialog";
|
|||
import { RoomNotificationStateStore } from "../../stores/notifications/RoomNotificationStateStore";
|
||||
import { SettingLevel } from "../../settings/SettingLevel";
|
||||
import { leaveRoomBehaviour } from "../../utils/membership";
|
||||
import CreateCommunityPrototypeDialog from "../views/dialogs/CreateCommunityPrototypeDialog";
|
||||
|
||||
/** constants for MatrixChat.state.view */
|
||||
export enum Views {
|
||||
|
@ -128,6 +129,7 @@ interface IScreen {
|
|||
params?: object;
|
||||
}
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
interface IRoomInfo {
|
||||
room_id?: string;
|
||||
room_alias?: string;
|
||||
|
@ -139,6 +141,7 @@ interface IRoomInfo {
|
|||
oob_data?: object;
|
||||
via_servers?: string[];
|
||||
}
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
interface IProps { // TODO type things better
|
||||
config: Record<string, any>;
|
||||
|
@ -164,6 +167,7 @@ interface IState {
|
|||
// the master view we are showing.
|
||||
view: Views;
|
||||
// What the LoggedInView would be showing if visible
|
||||
// eslint-disable-next-line camelcase
|
||||
page_type?: PageTypes;
|
||||
// The ID of the room we're viewing. This is either populated directly
|
||||
// in the case where we view a room by ID or by RoomView when it resolves
|
||||
|
@ -179,8 +183,11 @@ interface IState {
|
|||
middleDisabled: boolean;
|
||||
// the right panel's disabled state is tracked in its store.
|
||||
// Parameters used in the registration dance with the IS
|
||||
// eslint-disable-next-line camelcase
|
||||
register_client_secret?: string;
|
||||
// eslint-disable-next-line camelcase
|
||||
register_session_id?: string;
|
||||
// eslint-disable-next-line camelcase
|
||||
register_id_sid?: string;
|
||||
// When showing Modal dialogs we need to set aria-hidden on the root app element
|
||||
// and disable it when there are no dialogs
|
||||
|
@ -340,6 +347,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
}
|
||||
|
||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle stage
|
||||
// eslint-disable-next-line camelcase
|
||||
UNSAFE_componentWillUpdate(props, state) {
|
||||
if (this.shouldTrackPageChange(this.state, state)) {
|
||||
this.startPageChangeTimer();
|
||||
|
@ -609,8 +617,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
const UserSettingsDialog = sdk.getComponent("dialogs.UserSettingsDialog");
|
||||
Modal.createTrackedDialog('User settings', '', UserSettingsDialog,
|
||||
{initialTabId: tabPayload.initialTabId},
|
||||
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true
|
||||
);
|
||||
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
|
||||
|
||||
// View the welcome or home page if we need something to look at
|
||||
this.viewSomethingBehindModal();
|
||||
|
@ -620,7 +627,10 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
this.createRoom(payload.public);
|
||||
break;
|
||||
case 'view_create_group': {
|
||||
const CreateGroupDialog = sdk.getComponent("dialogs.CreateGroupDialog");
|
||||
let CreateGroupDialog = sdk.getComponent("dialogs.CreateGroupDialog")
|
||||
if (SettingsStore.getValue("feature_communities_v2_prototypes")) {
|
||||
CreateGroupDialog = CreateCommunityPrototypeDialog;
|
||||
}
|
||||
Modal.createTrackedDialog('Create Community', '', CreateGroupDialog);
|
||||
break;
|
||||
}
|
||||
|
@ -1076,7 +1086,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
title: _t("Leave room"),
|
||||
description: (
|
||||
<span>
|
||||
{ _t("Are you sure you want to leave the room '%(roomName)s'?", {roomName: roomToLeave.name}) }
|
||||
{ _t("Are you sure you want to leave the room '%(roomName)s'?", {roomName: roomToLeave.name}) }
|
||||
{ warnings }
|
||||
</span>
|
||||
),
|
||||
|
@ -1429,7 +1439,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
cli.on("crypto.warning", (type) => {
|
||||
switch (type) {
|
||||
case 'CRYPTO_WARNING_OLD_VERSION_DETECTED':
|
||||
const brand = SdkConfig.get().brand;
|
||||
Modal.createTrackedDialog('Crypto migrated', '', ErrorDialog, {
|
||||
title: _t('Old cryptography data detected'),
|
||||
description: _t(
|
||||
|
@ -1440,7 +1449,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
"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.",
|
||||
{ brand },
|
||||
{ brand: SdkConfig.get().brand },
|
||||
),
|
||||
});
|
||||
break;
|
||||
|
@ -2049,3 +2058,12 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
</ErrorBoundary>;
|
||||
}
|
||||
}
|
||||
|
||||
export function isLoggedIn(): boolean {
|
||||
// JRS: Maybe we should move the step that writes this to the window out of
|
||||
// `element-web` and into this file? Better yet, we should probably create a
|
||||
// store to hold this state.
|
||||
// See also https://github.com/vector-im/element-web/issues/15034.
|
||||
const app = window.matrixChat;
|
||||
return app && (app as MatrixChat).state.view === Views.LOGGED_IN;
|
||||
}
|
||||
|
|
|
@ -30,6 +30,10 @@ import { instanceForInstanceId, protocolNameForInstanceId } from '../../utils/Di
|
|||
import Analytics from '../../Analytics';
|
||||
import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo";
|
||||
import {ALL_ROOMS} from "../views/directory/NetworkDropdown";
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
import TagOrderStore from "../../stores/TagOrderStore";
|
||||
import GroupStore from "../../stores/GroupStore";
|
||||
import FlairStore from "../../stores/FlairStore";
|
||||
|
||||
const MAX_NAME_LENGTH = 80;
|
||||
const MAX_TOPIC_LENGTH = 160;
|
||||
|
@ -46,6 +50,7 @@ export default createReactClass({
|
|||
},
|
||||
|
||||
getInitialState: function() {
|
||||
const selectedCommunityId = TagOrderStore.getSelectedTags()[0];
|
||||
return {
|
||||
publicRooms: [],
|
||||
loading: true,
|
||||
|
@ -54,6 +59,10 @@ export default createReactClass({
|
|||
instanceId: undefined,
|
||||
roomServer: MatrixClientPeg.getHomeserverName(),
|
||||
filterString: null,
|
||||
selectedCommunityId: SettingsStore.getValue("feature_communities_v2_prototypes")
|
||||
? selectedCommunityId
|
||||
: null,
|
||||
communityName: null,
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -71,28 +80,39 @@ export default createReactClass({
|
|||
this.setState({protocolsLoading: false});
|
||||
return;
|
||||
}
|
||||
MatrixClientPeg.get().getThirdpartyProtocols().then((response) => {
|
||||
this.protocols = response;
|
||||
this.setState({protocolsLoading: false});
|
||||
}, (err) => {
|
||||
console.warn(`error loading third party protocols: ${err}`);
|
||||
this.setState({protocolsLoading: false});
|
||||
if (MatrixClientPeg.get().isGuest()) {
|
||||
// Guests currently aren't allowed to use this API, so
|
||||
// ignore this as otherwise this error is literally the
|
||||
// thing you see when loading the client!
|
||||
return;
|
||||
}
|
||||
track('Failed to get protocol list from homeserver');
|
||||
const brand = SdkConfig.get().brand;
|
||||
this.setState({
|
||||
error: _t(
|
||||
'%(brand)s failed to get the protocol list from the homeserver. ' +
|
||||
'The homeserver may be too old to support third party networks.',
|
||||
{ brand },
|
||||
),
|
||||
|
||||
if (!this.state.selectedCommunityId) {
|
||||
MatrixClientPeg.get().getThirdpartyProtocols().then((response) => {
|
||||
this.protocols = response;
|
||||
this.setState({protocolsLoading: false});
|
||||
}, (err) => {
|
||||
console.warn(`error loading third party protocols: ${err}`);
|
||||
this.setState({protocolsLoading: false});
|
||||
if (MatrixClientPeg.get().isGuest()) {
|
||||
// Guests currently aren't allowed to use this API, so
|
||||
// ignore this as otherwise this error is literally the
|
||||
// thing you see when loading the client!
|
||||
return;
|
||||
}
|
||||
track('Failed to get protocol list from homeserver');
|
||||
const brand = SdkConfig.get().brand;
|
||||
this.setState({
|
||||
error: _t(
|
||||
'%(brand)s failed to get the protocol list from the homeserver. ' +
|
||||
'The homeserver may be too old to support third party networks.',
|
||||
{brand},
|
||||
),
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// We don't use the protocols in the communities v2 prototype experience
|
||||
this.setState({protocolsLoading: false});
|
||||
|
||||
// Grab the profile info async
|
||||
FlairStore.getGroupProfileCached(MatrixClientPeg.get(), this.state.selectedCommunityId).then(profile => {
|
||||
this.setState({communityName: profile.name});
|
||||
});
|
||||
}
|
||||
|
||||
this.refreshRoomList();
|
||||
},
|
||||
|
@ -105,6 +125,33 @@ export default createReactClass({
|
|||
},
|
||||
|
||||
refreshRoomList: function() {
|
||||
if (this.state.selectedCommunityId) {
|
||||
this.setState({
|
||||
publicRooms: GroupStore.getGroupRooms(this.state.selectedCommunityId).map(r => {
|
||||
return {
|
||||
// Translate all the group properties to the directory format
|
||||
room_id: r.roomId,
|
||||
name: r.name,
|
||||
topic: r.topic,
|
||||
canonical_alias: r.canonicalAlias,
|
||||
num_joined_members: r.numJoinedMembers,
|
||||
avatarUrl: r.avatarUrl,
|
||||
world_readable: r.worldReadable,
|
||||
guest_can_join: r.guestsCanJoin,
|
||||
};
|
||||
}).filter(r => {
|
||||
const filterString = this.state.filterString;
|
||||
if (filterString) {
|
||||
const containedIn = (s: string) => (s || "").toLowerCase().includes(filterString.toLowerCase());
|
||||
return containedIn(r.name) || containedIn(r.topic) || containedIn(r.canonical_alias);
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
loading: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.nextBatch = null;
|
||||
this.setState({
|
||||
publicRooms: [],
|
||||
|
@ -114,6 +161,7 @@ export default createReactClass({
|
|||
},
|
||||
|
||||
getMoreRooms: function() {
|
||||
if (this.state.selectedCommunityId) return Promise.resolve(); // no more rooms
|
||||
if (!MatrixClientPeg.get()) return Promise.resolve();
|
||||
|
||||
this.setState({
|
||||
|
@ -239,7 +287,7 @@ export default createReactClass({
|
|||
},
|
||||
|
||||
onRoomClicked: function(room, ev) {
|
||||
if (ev.shiftKey) {
|
||||
if (ev.shiftKey && !this.state.selectedCommunityId) {
|
||||
ev.preventDefault();
|
||||
this.removeFromDirectory(room);
|
||||
} else {
|
||||
|
@ -610,6 +658,18 @@ export default createReactClass({
|
|||
}
|
||||
}
|
||||
|
||||
let dropdown = (
|
||||
<NetworkDropdown
|
||||
protocols={this.protocols}
|
||||
onOptionChange={this.onOptionChange}
|
||||
selectedServerName={this.state.roomServer}
|
||||
selectedInstanceId={this.state.instanceId}
|
||||
/>
|
||||
);
|
||||
if (this.state.selectedCommunityId) {
|
||||
dropdown = null;
|
||||
}
|
||||
|
||||
listHeader = <div className="mx_RoomDirectory_listheader">
|
||||
<DirectorySearchBox
|
||||
className="mx_RoomDirectory_searchbox"
|
||||
|
@ -619,12 +679,7 @@ export default createReactClass({
|
|||
placeholder={placeholder}
|
||||
showJoinButton={showJoinButton}
|
||||
/>
|
||||
<NetworkDropdown
|
||||
protocols={this.protocols}
|
||||
onOptionChange={this.onOptionChange}
|
||||
selectedServerName={this.state.roomServer}
|
||||
selectedInstanceId={this.state.instanceId}
|
||||
/>
|
||||
{dropdown}
|
||||
</div>;
|
||||
}
|
||||
const explanation =
|
||||
|
@ -637,12 +692,16 @@ export default createReactClass({
|
|||
}},
|
||||
);
|
||||
|
||||
const title = this.state.selectedCommunityId
|
||||
? _t("Explore rooms in %(communityName)s", {
|
||||
communityName: this.state.communityName || this.state.selectedCommunityId,
|
||||
}) : _t("Explore rooms");
|
||||
return (
|
||||
<BaseDialog
|
||||
className={'mx_RoomDirectory_dialog'}
|
||||
hasCancel={true}
|
||||
onFinished={this.props.onFinished}
|
||||
title={_t("Explore rooms")}
|
||||
title={title}
|
||||
>
|
||||
<div className="mx_RoomDirectory">
|
||||
{explanation}
|
||||
|
|
|
@ -20,7 +20,6 @@ import classNames from "classnames";
|
|||
import defaultDispatcher from "../../dispatcher/dispatcher";
|
||||
import { _t } from "../../languageHandler";
|
||||
import { ActionPayload } from "../../dispatcher/payloads";
|
||||
import { throttle } from 'lodash';
|
||||
import { Key } from "../../Keyboard";
|
||||
import AccessibleButton from "../views/elements/AccessibleButton";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
|
@ -137,7 +136,7 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
|||
});
|
||||
|
||||
let icon = (
|
||||
<div className='mx_RoomSearch_icon'/>
|
||||
<div className='mx_RoomSearch_icon' />
|
||||
);
|
||||
let input = (
|
||||
<input
|
||||
|
|
|
@ -18,7 +18,6 @@ limitations under the License.
|
|||
|
||||
import * as React from "react";
|
||||
import {_t} from '../../languageHandler';
|
||||
import * as PropTypes from "prop-types";
|
||||
import * as sdk from "../../index";
|
||||
import AutoHideScrollbar from './AutoHideScrollbar';
|
||||
import { ReactNode } from "react";
|
||||
|
|
|
@ -29,6 +29,8 @@ import { Droppable } from 'react-beautiful-dnd';
|
|||
import classNames from 'classnames';
|
||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
import UserTagTile from "../views/elements/UserTagTile";
|
||||
|
||||
const TagPanel = createReactClass({
|
||||
displayName: 'TagPanel',
|
||||
|
@ -93,20 +95,24 @@ const TagPanel = createReactClass({
|
|||
}
|
||||
},
|
||||
|
||||
onCreateGroupClick(ev) {
|
||||
ev.stopPropagation();
|
||||
dis.dispatch({action: 'view_create_group'});
|
||||
},
|
||||
|
||||
onClearFilterClick(ev) {
|
||||
dis.dispatch({action: 'deselect_tags'});
|
||||
},
|
||||
|
||||
renderGlobalIcon() {
|
||||
if (!SettingsStore.getValue("feature_communities_v2_prototypes")) return null;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<UserTagTile />
|
||||
<hr className="mx_TagPanel_divider" />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
render() {
|
||||
const DNDTagTile = sdk.getComponent('elements.DNDTagTile');
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
const ActionButton = sdk.getComponent('elements.ActionButton');
|
||||
const TintableSvg = sdk.getComponent('elements.TintableSvg');
|
||||
|
||||
const tags = this.state.orderedTags.map((tag, index) => {
|
||||
return <DNDTagTile
|
||||
|
@ -118,26 +124,29 @@ const TagPanel = createReactClass({
|
|||
});
|
||||
|
||||
const itemsSelected = this.state.selectedTags.length > 0;
|
||||
|
||||
let clearButton;
|
||||
if (itemsSelected) {
|
||||
clearButton = <AccessibleButton className="mx_TagPanel_clearButton" onClick={this.onClearFilterClick}>
|
||||
<TintableSvg src={require("../../../res/img/icons-close.svg")} width="24" height="24"
|
||||
alt={_t("Clear filter")}
|
||||
title={_t("Clear filter")}
|
||||
/>
|
||||
</AccessibleButton>;
|
||||
}
|
||||
|
||||
const classes = classNames('mx_TagPanel', {
|
||||
mx_TagPanel_items_selected: itemsSelected,
|
||||
});
|
||||
|
||||
return <div className={classes}>
|
||||
<div className="mx_TagPanel_clearButton_container">
|
||||
{ clearButton }
|
||||
</div>
|
||||
<div className="mx_TagPanel_divider" />
|
||||
let createButton = (
|
||||
<ActionButton
|
||||
tooltip
|
||||
label={_t("Communities")}
|
||||
action="toggle_my_groups"
|
||||
className="mx_TagTile mx_TagTile_plus" />
|
||||
);
|
||||
|
||||
if (SettingsStore.getValue("feature_communities_v2_prototypes")) {
|
||||
createButton = (
|
||||
<ActionButton
|
||||
tooltip
|
||||
label={_t("Create community")}
|
||||
action="view_create_group"
|
||||
className="mx_TagTile mx_TagTile_plus" />
|
||||
);
|
||||
}
|
||||
|
||||
return <div className={classes} onClick={this.onClearFilterClick}>
|
||||
<AutoHideScrollbar
|
||||
className="mx_TagPanel_scroller"
|
||||
// XXX: Use onMouseDown as a workaround for https://github.com/atlassian/react-beautiful-dnd/issues/273
|
||||
|
@ -153,13 +162,10 @@ const TagPanel = createReactClass({
|
|||
className="mx_TagPanel_tagTileContainer"
|
||||
ref={provided.innerRef}
|
||||
>
|
||||
{ this.renderGlobalIcon() }
|
||||
{ tags }
|
||||
<div>
|
||||
<ActionButton
|
||||
tooltip
|
||||
label={_t("Communities")}
|
||||
action="toggle_my_groups"
|
||||
className="mx_TagTile mx_TagTile_plus" />
|
||||
{createButton}
|
||||
</div>
|
||||
{ provided.placeholder }
|
||||
</div>
|
||||
|
|
|
@ -40,7 +40,7 @@ import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
|
|||
import { SettingLevel } from "../../settings/SettingLevel";
|
||||
import IconizedContextMenu, {
|
||||
IconizedContextMenuOption,
|
||||
IconizedContextMenuOptionList
|
||||
IconizedContextMenuOptionList,
|
||||
} from "../views/context_menus/IconizedContextMenu";
|
||||
|
||||
interface IProps {
|
||||
|
@ -234,12 +234,12 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
|||
>
|
||||
<div className="mx_UserMenu_contextMenu_header">
|
||||
<div className="mx_UserMenu_contextMenu_name">
|
||||
<span className="mx_UserMenu_contextMenu_displayName">
|
||||
{OwnProfileStore.instance.displayName}
|
||||
</span>
|
||||
<span className="mx_UserMenu_contextMenu_displayName">
|
||||
{OwnProfileStore.instance.displayName}
|
||||
</span>
|
||||
<span className="mx_UserMenu_contextMenu_userId">
|
||||
{MatrixClientPeg.get().getUserId()}
|
||||
</span>
|
||||
{MatrixClientPeg.get().getUserId()}
|
||||
</span>
|
||||
</div>
|
||||
<AccessibleTooltipButton
|
||||
className="mx_UserMenu_contextMenu_themeButton"
|
||||
|
@ -295,7 +295,10 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
|||
public render() {
|
||||
const avatarSize = 32; // should match border-radius of the avatar
|
||||
|
||||
let name = <span className="mx_UserMenu_userName">{OwnProfileStore.instance.displayName}</span>;
|
||||
const displayName = OwnProfileStore.instance.displayName || MatrixClientPeg.get().getUserId();
|
||||
const avatarUrl = OwnProfileStore.instance.getHttpAvatarUrl(avatarSize);
|
||||
|
||||
let name = <span className="mx_UserMenu_userName">{displayName}</span>;
|
||||
let buttons = (
|
||||
<span className="mx_UserMenu_headerButtons">
|
||||
{/* masked image in CSS */}
|
||||
|
@ -324,9 +327,9 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
|||
<div className="mx_UserMenu_row">
|
||||
<span className="mx_UserMenu_userAvatarContainer">
|
||||
<BaseAvatar
|
||||
idName={MatrixClientPeg.get().getUserId()}
|
||||
name={OwnProfileStore.instance.displayName || MatrixClientPeg.get().getUserId()}
|
||||
url={OwnProfileStore.instance.getHttpAvatarUrl(avatarSize)}
|
||||
idName={displayName}
|
||||
name={displayName}
|
||||
url={avatarUrl}
|
||||
width={avatarSize}
|
||||
height={avatarSize}
|
||||
resizeMethod="crop"
|
||||
|
|
|
@ -17,7 +17,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, {useCallback, useContext, useEffect, useMemo, useState} from 'react';
|
||||
import React, {useCallback, useContext, useEffect, useState} from 'react';
|
||||
import classNames from 'classnames';
|
||||
import * as AvatarLogic from '../../../Avatar';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
|
@ -42,34 +42,35 @@ interface IProps {
|
|||
className?: string;
|
||||
}
|
||||
|
||||
const calculateUrls = (url, urls) => {
|
||||
// work out the full set of urls to try to load. This is formed like so:
|
||||
// imageUrls: [ props.url, ...props.urls ]
|
||||
|
||||
let _urls = [];
|
||||
if (!SettingsStore.getValue("lowBandwidth")) {
|
||||
_urls = urls || [];
|
||||
|
||||
if (url) {
|
||||
_urls.unshift(url); // put in urls[0]
|
||||
}
|
||||
}
|
||||
|
||||
// deduplicate URLs
|
||||
return Array.from(new Set(_urls));
|
||||
};
|
||||
|
||||
const useImageUrl = ({url, urls}): [string, () => void] => {
|
||||
const [imageUrls, setUrls] = useState<string[]>([]);
|
||||
const [urlsIndex, setIndex] = useState<number>();
|
||||
const [imageUrls, setUrls] = useState<string[]>(calculateUrls(url, urls));
|
||||
const [urlsIndex, setIndex] = useState<number>(0);
|
||||
|
||||
const onError = useCallback(() => {
|
||||
setIndex(i => i + 1); // try the next one
|
||||
}, []);
|
||||
const memoizedUrls = useMemo(() => urls, [JSON.stringify(urls)]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
useEffect(() => {
|
||||
// work out the full set of urls to try to load. This is formed like so:
|
||||
// imageUrls: [ props.url, ...props.urls ]
|
||||
|
||||
let _urls = [];
|
||||
if (!SettingsStore.getValue("lowBandwidth")) {
|
||||
_urls = memoizedUrls || [];
|
||||
|
||||
if (url) {
|
||||
_urls.unshift(url); // put in urls[0]
|
||||
}
|
||||
}
|
||||
|
||||
// deduplicate URLs
|
||||
_urls = Array.from(new Set(_urls));
|
||||
|
||||
setUrls(calculateUrls(url, urls));
|
||||
setIndex(0);
|
||||
setUrls(_urls);
|
||||
}, [url, memoizedUrls]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
}, [url, JSON.stringify(urls)]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const cli = useContext(MatrixClientContext);
|
||||
const onClientSync = useCallback((syncState, prevState) => {
|
||||
|
@ -95,7 +96,7 @@ const BaseAvatar = (props: IProps) => {
|
|||
urls,
|
||||
width = 40,
|
||||
height = 40,
|
||||
resizeMethod = "crop", // eslint-disable-line no-unused-vars
|
||||
resizeMethod = "crop", // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
defaultToInitialLetter = true,
|
||||
onClick,
|
||||
inputRef,
|
||||
|
|
|
@ -126,7 +126,7 @@ export default class DecoratedRoomAvatar extends React.PureComponent<IProps, ISt
|
|||
private onPresenceUpdate = () => {
|
||||
if (this.isUnmounted) return;
|
||||
|
||||
let newIcon = this.getPresenceIcon();
|
||||
const newIcon = this.getPresenceIcon();
|
||||
if (newIcon !== this.state.icon) this.setState({icon: newIcon});
|
||||
};
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ export default class GroupAvatar extends React.Component<IProps> {
|
|||
render() {
|
||||
// extract the props we use from props so we can pass any others through
|
||||
// should consider adding this as a global rule in js-sdk?
|
||||
/*eslint no-unused-vars: ["error", { "ignoreRestSiblings": true }]*/
|
||||
/* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */
|
||||
const {groupId, groupAvatarUrl, groupName, ...otherProps} = this.props;
|
||||
|
||||
return (
|
||||
|
|
|
@ -25,4 +25,4 @@ const PulsedAvatar: React.FC<IProps> = (props) => {
|
|||
</div>;
|
||||
};
|
||||
|
||||
export default PulsedAvatar;
|
||||
export default PulsedAvatar;
|
||||
|
|
|
@ -1,57 +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 from "react";
|
||||
import PropTypes from 'prop-types';
|
||||
import createReactClass from 'create-react-class';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
const Presets = {
|
||||
PrivateChat: "private_chat",
|
||||
PublicChat: "public_chat",
|
||||
Custom: "custom",
|
||||
};
|
||||
|
||||
export default createReactClass({
|
||||
displayName: 'CreateRoomPresets',
|
||||
propTypes: {
|
||||
onChange: PropTypes.func,
|
||||
preset: PropTypes.string,
|
||||
},
|
||||
|
||||
Presets: Presets,
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
onChange: function() {},
|
||||
};
|
||||
},
|
||||
|
||||
onValueChanged: function(ev) {
|
||||
this.props.onChange(ev.target.value);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<select className="mx_Presets" onChange={this.onValueChanged} value={this.props.preset}>
|
||||
<option value={this.Presets.PrivateChat}>{ _t("Private Chat") }</option>
|
||||
<option value={this.Presets.PublicChat}>{ _t("Public Chat") }</option>
|
||||
<option value={this.Presets.Custom}>{ _t("Custom") }</option>
|
||||
</select>
|
||||
);
|
||||
},
|
||||
});
|
|
@ -1,106 +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 from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import createReactClass from 'create-react-class';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
export default createReactClass({
|
||||
displayName: 'RoomAlias',
|
||||
propTypes: {
|
||||
// Specifying a homeserver will make magical things happen when you,
|
||||
// e.g. start typing in the room alias box.
|
||||
homeserver: PropTypes.string,
|
||||
alias: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
onChange: function() {},
|
||||
alias: '',
|
||||
};
|
||||
},
|
||||
|
||||
getAliasLocalpart: function() {
|
||||
let room_alias = this.props.alias;
|
||||
|
||||
if (room_alias && this.props.homeserver) {
|
||||
const suffix = ":" + this.props.homeserver;
|
||||
if (room_alias.startsWith("#") && room_alias.endsWith(suffix)) {
|
||||
room_alias = room_alias.slice(1, -suffix.length);
|
||||
}
|
||||
}
|
||||
|
||||
return room_alias;
|
||||
},
|
||||
|
||||
onValueChanged: function(ev) {
|
||||
this.props.onChange(ev.target.value);
|
||||
},
|
||||
|
||||
onFocus: function(ev) {
|
||||
const target = ev.target;
|
||||
const curr_val = ev.target.value;
|
||||
|
||||
if (this.props.homeserver) {
|
||||
if (curr_val == "") {
|
||||
const self = this;
|
||||
setTimeout(function() {
|
||||
target.value = "#:" + self.props.homeserver;
|
||||
target.setSelectionRange(1, 1);
|
||||
}, 0);
|
||||
} else {
|
||||
const suffix = ":" + this.props.homeserver;
|
||||
setTimeout(function() {
|
||||
target.setSelectionRange(
|
||||
curr_val.startsWith("#") ? 1 : 0,
|
||||
curr_val.endsWith(suffix) ? (target.value.length - suffix.length) : target.value.length,
|
||||
);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onBlur: function(ev) {
|
||||
const curr_val = ev.target.value;
|
||||
|
||||
if (this.props.homeserver) {
|
||||
if (curr_val == "#:" + this.props.homeserver) {
|
||||
ev.target.value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
if (curr_val != "") {
|
||||
let new_val = ev.target.value;
|
||||
const suffix = ":" + this.props.homeserver;
|
||||
if (!curr_val.startsWith("#")) new_val = "#" + new_val;
|
||||
if (!curr_val.endsWith(suffix)) new_val = new_val + suffix;
|
||||
ev.target.value = new_val;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<input type="text" className="mx_RoomAlias" placeholder={_t("Address (optional)")}
|
||||
onChange={this.onValueChanged} onFocus={this.onFocus} onBlur={this.onBlur}
|
||||
value={this.props.alias} />
|
||||
);
|
||||
},
|
||||
});
|
248
src/components/views/dialogs/CommunityPrototypeInviteDialog.tsx
Normal file
248
src/components/views/dialogs/CommunityPrototypeInviteDialog.tsx
Normal file
|
@ -0,0 +1,248 @@
|
|||
/*
|
||||
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, { ChangeEvent, FormEvent } from 'react';
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { IDialogProps } from "./IDialogProps";
|
||||
import Field from "../elements/Field";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import { arrayFastClone } from "../../../utils/arrays";
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||
import InviteDialog from "./InviteDialog";
|
||||
import BaseAvatar from "../avatars/BaseAvatar";
|
||||
import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo";
|
||||
import {inviteMultipleToRoom, showAnyInviteErrors} from "../../../RoomInvite";
|
||||
import StyledCheckbox from "../elements/StyledCheckbox";
|
||||
import Modal from "../../../Modal";
|
||||
import ErrorDialog from "./ErrorDialog";
|
||||
|
||||
interface IProps extends IDialogProps {
|
||||
roomId: string;
|
||||
communityName: string;
|
||||
}
|
||||
|
||||
interface IPerson {
|
||||
userId: string;
|
||||
user: RoomMember;
|
||||
lastActive: number;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
emailTargets: string[];
|
||||
userTargets: string[];
|
||||
showPeople: boolean;
|
||||
people: IPerson[];
|
||||
numPeople: number;
|
||||
busy: boolean;
|
||||
}
|
||||
|
||||
export default class CommunityPrototypeInviteDialog extends React.PureComponent<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
emailTargets: [],
|
||||
userTargets: [],
|
||||
showPeople: false,
|
||||
people: this.buildSuggestions(),
|
||||
numPeople: 5, // arbitrary default
|
||||
busy: false,
|
||||
};
|
||||
}
|
||||
|
||||
private buildSuggestions(): IPerson[] {
|
||||
const alreadyInvited = new Set([MatrixClientPeg.get().getUserId(), SdkConfig.get()['welcomeUserId']]);
|
||||
if (this.props.roomId) {
|
||||
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
|
||||
if (!room) throw new Error("Room ID given to InviteDialog does not look like a room");
|
||||
room.getMembersWithMembership('invite').forEach(m => alreadyInvited.add(m.userId));
|
||||
room.getMembersWithMembership('join').forEach(m => alreadyInvited.add(m.userId));
|
||||
// add banned users, so we don't try to invite them
|
||||
room.getMembersWithMembership('ban').forEach(m => alreadyInvited.add(m.userId));
|
||||
}
|
||||
|
||||
return InviteDialog.buildRecents(alreadyInvited);
|
||||
}
|
||||
|
||||
private onSubmit = async (ev: FormEvent) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
this.setState({busy: true});
|
||||
try {
|
||||
const targets = [...this.state.emailTargets, ...this.state.userTargets];
|
||||
const result = await inviteMultipleToRoom(this.props.roomId, targets);
|
||||
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
|
||||
const success = showAnyInviteErrors(result.states, room, result.inviter);
|
||||
if (success) {
|
||||
this.props.onFinished(true);
|
||||
} else {
|
||||
this.setState({busy: false});
|
||||
}
|
||||
} catch (e) {
|
||||
this.setState({busy: false});
|
||||
console.error(e);
|
||||
Modal.createTrackedDialog('Failed to invite', '', ErrorDialog, {
|
||||
title: _t("Failed to invite"),
|
||||
description: ((e && e.message) ? e.message : _t("Operation failed")),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private onAddressChange = (ev: ChangeEvent<HTMLInputElement>, index: number) => {
|
||||
const targets = arrayFastClone(this.state.emailTargets);
|
||||
if (index >= targets.length) {
|
||||
targets.push(ev.target.value);
|
||||
} else {
|
||||
targets[index] = ev.target.value;
|
||||
}
|
||||
this.setState({emailTargets: targets});
|
||||
};
|
||||
|
||||
private onAddressBlur = (index: number) => {
|
||||
const targets = arrayFastClone(this.state.emailTargets);
|
||||
if (index >= targets.length) return; // not important
|
||||
if (targets[index].trim() === "") {
|
||||
targets.splice(index, 1);
|
||||
this.setState({emailTargets: targets});
|
||||
}
|
||||
};
|
||||
|
||||
private onShowPeopleClick = () => {
|
||||
this.setState({showPeople: !this.state.showPeople});
|
||||
};
|
||||
|
||||
private setPersonToggle = (person: IPerson, selected: boolean) => {
|
||||
const targets = arrayFastClone(this.state.userTargets);
|
||||
if (selected && !targets.includes(person.userId)) {
|
||||
targets.push(person.userId);
|
||||
} else if (!selected && targets.includes(person.userId)) {
|
||||
targets.splice(targets.indexOf(person.userId), 1);
|
||||
}
|
||||
this.setState({userTargets: targets});
|
||||
};
|
||||
|
||||
private renderPerson(person: IPerson, key: any) {
|
||||
const avatarSize = 36;
|
||||
return (
|
||||
<div className="mx_CommunityPrototypeInviteDialog_person" key={key}>
|
||||
<BaseAvatar
|
||||
url={getHttpUriForMxc(
|
||||
MatrixClientPeg.get().getHomeserverUrl(), person.user.getMxcAvatarUrl(),
|
||||
avatarSize, avatarSize, "crop")}
|
||||
name={person.user.name}
|
||||
idName={person.user.userId}
|
||||
width={avatarSize}
|
||||
height={avatarSize}
|
||||
/>
|
||||
<div className="mx_CommunityPrototypeInviteDialog_personIdentifiers">
|
||||
<span className="mx_CommunityPrototypeInviteDialog_personName">{person.user.name}</span>
|
||||
<span className="mx_CommunityPrototypeInviteDialog_personId">{person.userId}</span>
|
||||
</div>
|
||||
<StyledCheckbox onChange={(e) => this.setPersonToggle(person, e.target.checked)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private onShowMorePeople = () => {
|
||||
this.setState({numPeople: this.state.numPeople + 5}); // arbitrary increase
|
||||
};
|
||||
|
||||
public render() {
|
||||
const emailAddresses = [];
|
||||
this.state.emailTargets.forEach((address, i) => {
|
||||
emailAddresses.push((
|
||||
<Field
|
||||
key={i}
|
||||
value={address}
|
||||
onChange={(e) => this.onAddressChange(e, i)}
|
||||
label={_t("Email address")}
|
||||
placeholder={_t("Email address")}
|
||||
onBlur={() => this.onAddressBlur(i)}
|
||||
/>
|
||||
));
|
||||
});
|
||||
|
||||
// Push a clean input
|
||||
emailAddresses.push((
|
||||
<Field
|
||||
key={emailAddresses.length}
|
||||
value={""}
|
||||
onChange={(e) => this.onAddressChange(e, emailAddresses.length)}
|
||||
label={emailAddresses.length > 0 ? _t("Add another email") : _t("Email address")}
|
||||
placeholder={emailAddresses.length > 0 ? _t("Add another email") : _t("Email address")}
|
||||
/>
|
||||
));
|
||||
|
||||
let peopleIntro = null;
|
||||
const people = [];
|
||||
if (this.state.showPeople) {
|
||||
const humansToPresent = this.state.people.slice(0, this.state.numPeople);
|
||||
humansToPresent.forEach((person, i) => {
|
||||
people.push(this.renderPerson(person, i));
|
||||
});
|
||||
if (humansToPresent.length < this.state.people.length) {
|
||||
people.push((
|
||||
<AccessibleButton
|
||||
onClick={this.onShowMorePeople}
|
||||
kind="link" key="more"
|
||||
className="mx_CommunityPrototypeInviteDialog_morePeople"
|
||||
>{_t("Show more")}</AccessibleButton>
|
||||
));
|
||||
}
|
||||
}
|
||||
if (this.state.people.length > 0) {
|
||||
peopleIntro = (
|
||||
<div className="mx_CommunityPrototypeInviteDialog_people">
|
||||
<span>{_t("People you know on %(brand)s", {brand: SdkConfig.get().brand})}</span>
|
||||
<AccessibleButton onClick={this.onShowPeopleClick}>
|
||||
{this.state.showPeople ? _t("Hide") : _t("Show")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let buttonText = _t("Skip");
|
||||
const targetCount = this.state.userTargets.length + this.state.emailTargets.length;
|
||||
if (targetCount > 0) {
|
||||
buttonText = _t("Send %(count)s invites", {count: targetCount});
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseDialog
|
||||
className="mx_CommunityPrototypeInviteDialog"
|
||||
onFinished={this.props.onFinished}
|
||||
title={_t("Invite people to join %(communityName)s", {communityName: this.props.communityName})}
|
||||
>
|
||||
<form onSubmit={this.onSubmit}>
|
||||
<div className="mx_Dialog_content">
|
||||
{emailAddresses}
|
||||
{peopleIntro}
|
||||
{people}
|
||||
<AccessibleButton
|
||||
kind="primary" onClick={this.onSubmit}
|
||||
disabled={this.state.busy}
|
||||
className="mx_CommunityPrototypeInviteDialog_primaryButton"
|
||||
>{buttonText}</AccessibleButton>
|
||||
</div>
|
||||
</form>
|
||||
</BaseDialog>
|
||||
);
|
||||
}
|
||||
}
|
227
src/components/views/dialogs/CreateCommunityPrototypeDialog.tsx
Normal file
227
src/components/views/dialogs/CreateCommunityPrototypeDialog.tsx
Normal file
|
@ -0,0 +1,227 @@
|
|||
/*
|
||||
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, { ChangeEvent } from 'react';
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { IDialogProps } from "./IDialogProps";
|
||||
import Field from "../elements/Field";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import InfoTooltip from "../elements/InfoTooltip";
|
||||
import dis from "../../../dispatcher/dispatcher";
|
||||
import {showCommunityRoomInviteDialog} from "../../../RoomInvite";
|
||||
import GroupStore from "../../../stores/GroupStore";
|
||||
|
||||
interface IProps extends IDialogProps {
|
||||
}
|
||||
|
||||
interface IState {
|
||||
name: string;
|
||||
localpart: string;
|
||||
error: string;
|
||||
busy: boolean;
|
||||
avatarFile: File;
|
||||
avatarPreview: string;
|
||||
}
|
||||
|
||||
export default class CreateCommunityPrototypeDialog extends React.PureComponent<IProps, IState> {
|
||||
private avatarUploadRef: React.RefObject<HTMLInputElement> = React.createRef();
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
name: "",
|
||||
localpart: "",
|
||||
error: null,
|
||||
busy: false,
|
||||
avatarFile: null,
|
||||
avatarPreview: null,
|
||||
};
|
||||
}
|
||||
|
||||
private onNameChange = (ev: ChangeEvent<HTMLInputElement>) => {
|
||||
const localpart = (ev.target.value || "").toLowerCase().replace(/[^a-z0-9.\-_]/g, '-');
|
||||
this.setState({name: ev.target.value, localpart});
|
||||
};
|
||||
|
||||
private onSubmit = async (ev) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
if (this.state.busy) return;
|
||||
|
||||
// We'll create the community now to see if it's taken, leaving it active in
|
||||
// the background for the user to look at while they invite people.
|
||||
this.setState({busy: true});
|
||||
try {
|
||||
let avatarUrl = ''; // must be a string for synapse to accept it
|
||||
if (this.state.avatarFile) {
|
||||
avatarUrl = await MatrixClientPeg.get().uploadContent(this.state.avatarFile);
|
||||
}
|
||||
|
||||
const result = await MatrixClientPeg.get().createGroup({
|
||||
localpart: this.state.localpart,
|
||||
profile: {
|
||||
name: this.state.name,
|
||||
avatar_url: avatarUrl,
|
||||
},
|
||||
});
|
||||
|
||||
// Ensure the tag gets selected now that we've created it
|
||||
dis.dispatch({action: 'deselect_tags'}, true);
|
||||
dis.dispatch({
|
||||
action: 'select_tag',
|
||||
tag: result.group_id,
|
||||
});
|
||||
|
||||
// Close our own dialog before moving much further
|
||||
this.props.onFinished(true);
|
||||
|
||||
if (result.room_id) {
|
||||
// Force the group store to update as it might have missed the general chat
|
||||
await GroupStore.refreshGroupRooms(result.group_id);
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: result.room_id,
|
||||
});
|
||||
showCommunityRoomInviteDialog(result.room_id, this.state.name);
|
||||
} else {
|
||||
dis.dispatch({
|
||||
action: 'view_group',
|
||||
group_id: result.group_id,
|
||||
group_is_new: true,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
this.setState({
|
||||
busy: false,
|
||||
error: _t(
|
||||
"There was an error creating your community. The name may be taken or the " +
|
||||
"server is unable to process your request.",
|
||||
),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private onAvatarChanged = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
if (!e.target.files || !e.target.files.length) {
|
||||
this.setState({avatarFile: null});
|
||||
} else {
|
||||
this.setState({busy: true});
|
||||
const file = e.target.files[0];
|
||||
const reader = new FileReader();
|
||||
reader.onload = (ev: ProgressEvent<FileReader>) => {
|
||||
this.setState({avatarFile: file, busy: false, avatarPreview: ev.target.result as string});
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
};
|
||||
|
||||
private onChangeAvatar = () => {
|
||||
if (this.avatarUploadRef.current) this.avatarUploadRef.current.click();
|
||||
};
|
||||
|
||||
public render() {
|
||||
let communityId = null;
|
||||
if (this.state.localpart) {
|
||||
communityId = (
|
||||
<span className="mx_CreateCommunityPrototypeDialog_communityId">
|
||||
{_t("Community ID: +<localpart />:%(domain)s", {
|
||||
domain: MatrixClientPeg.getHomeserverName(),
|
||||
}, {
|
||||
localpart: () => <u>{this.state.localpart}</u>,
|
||||
})}
|
||||
<InfoTooltip
|
||||
tooltip={_t(
|
||||
"Use this when referencing your community to others. The community ID " +
|
||||
"cannot be changed.",
|
||||
)}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
let helpText = (
|
||||
<span className="mx_CreateCommunityPrototypeDialog_subtext">
|
||||
{_t("You can change this later if needed.")}
|
||||
</span>
|
||||
);
|
||||
if (this.state.error) {
|
||||
const classes = "mx_CreateCommunityPrototypeDialog_subtext mx_CreateCommunityPrototypeDialog_subtext_error";
|
||||
helpText = (
|
||||
<span className={classes}>
|
||||
{this.state.error}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
let preview = <img src={this.state.avatarPreview} className="mx_CreateCommunityPrototypeDialog_avatar" />;
|
||||
if (!this.state.avatarPreview) {
|
||||
preview = <div className="mx_CreateCommunityPrototypeDialog_placeholderAvatar" />
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseDialog
|
||||
className="mx_CreateCommunityPrototypeDialog"
|
||||
onFinished={this.props.onFinished}
|
||||
title={_t("What's the name of your community or team?")}
|
||||
>
|
||||
<form onSubmit={this.onSubmit}>
|
||||
<div className="mx_Dialog_content">
|
||||
<div className="mx_CreateCommunityPrototypeDialog_colName">
|
||||
<Field
|
||||
value={this.state.name}
|
||||
onChange={this.onNameChange}
|
||||
placeholder={_t("Enter name")}
|
||||
label={_t("Enter name")}
|
||||
/>
|
||||
{helpText}
|
||||
<span className="mx_CreateCommunityPrototypeDialog_subtext">
|
||||
{/*nbsp is to reserve the height of this element when there's nothing*/}
|
||||
{communityId}
|
||||
</span>
|
||||
<AccessibleButton kind="primary" onClick={this.onSubmit} disabled={this.state.busy}>
|
||||
{_t("Create")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
<div className="mx_CreateCommunityPrototypeDialog_colAvatar">
|
||||
<input
|
||||
type="file" style={{display: "none"}}
|
||||
ref={this.avatarUploadRef} accept="image/*"
|
||||
onChange={this.onAvatarChanged}
|
||||
/>
|
||||
<AccessibleButton
|
||||
onClick={this.onChangeAvatar}
|
||||
className="mx_CreateCommunityPrototypeDialog_avatarContainer"
|
||||
>
|
||||
{preview}
|
||||
</AccessibleButton>
|
||||
<div className="mx_CreateCommunityPrototypeDialog_tip">
|
||||
<b>{_t("Add image (optional)")}</b>
|
||||
<span>
|
||||
{_t("An image will help people identify your community.")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</BaseDialog>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -25,6 +25,8 @@ import { _t } from '../../../languageHandler';
|
|||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||
import {Key} from "../../../Keyboard";
|
||||
import {privateShouldBeEncrypted} from "../../../createRoom";
|
||||
import TagOrderStore from "../../../stores/TagOrderStore";
|
||||
import GroupStore from "../../../stores/GroupStore";
|
||||
|
||||
export default createReactClass({
|
||||
displayName: 'CreateRoomDialog',
|
||||
|
@ -70,6 +72,10 @@ export default createReactClass({
|
|||
opts.encryption = this.state.isEncrypted;
|
||||
}
|
||||
|
||||
if (TagOrderStore.getSelectedPrototypeTag()) {
|
||||
opts.associatedWithCommunity = TagOrderStore.getSelectedPrototypeTag();
|
||||
}
|
||||
|
||||
return opts;
|
||||
},
|
||||
|
||||
|
@ -178,18 +184,25 @@ export default createReactClass({
|
|||
const LabelledToggleSwitch = sdk.getComponent('views.elements.LabelledToggleSwitch');
|
||||
const RoomAliasField = sdk.getComponent('views.elements.RoomAliasField');
|
||||
|
||||
let publicPrivateLabel;
|
||||
let aliasField;
|
||||
if (this.state.isPublic) {
|
||||
publicPrivateLabel = (<p>{_t("Set a room address to easily share your room with other people.")}</p>);
|
||||
const domain = MatrixClientPeg.get().getDomain();
|
||||
aliasField = (
|
||||
<div className="mx_CreateRoomDialog_aliasContainer">
|
||||
<RoomAliasField ref={ref => this._aliasFieldRef = ref} onChange={this.onAliasChange} domain={domain} value={this.state.alias} />
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
publicPrivateLabel = (<p>{_t("This room is private, and can only be joined by invitation.")}</p>);
|
||||
}
|
||||
|
||||
let publicPrivateLabel = <p>{_t(
|
||||
"Private rooms can be found and joined by invitation only. Public rooms can be " +
|
||||
"found and joined by anyone.",
|
||||
)}</p>;
|
||||
if (TagOrderStore.getSelectedPrototypeTag()) {
|
||||
publicPrivateLabel = <p>{_t(
|
||||
"Private rooms can be found and joined by invitation only. Public rooms can be " +
|
||||
"found and joined by anyone in this community.",
|
||||
)}</p>;
|
||||
}
|
||||
|
||||
let e2eeSection;
|
||||
|
@ -212,7 +225,25 @@ export default createReactClass({
|
|||
</React.Fragment>;
|
||||
}
|
||||
|
||||
const title = this.state.isPublic ? _t('Create a public room') : _t('Create a private room');
|
||||
let federateLabel = _t(
|
||||
"You might enable this if the room will only be used for collaborating with internal " +
|
||||
"teams on your homeserver. This cannot be changed later.",
|
||||
);
|
||||
if (SdkConfig.get().default_federate === false) {
|
||||
// We only change the label if the default setting is different to avoid jarring text changes to the
|
||||
// user. They will have read the implications of turning this off/on, so no need to rephrase for them.
|
||||
federateLabel = _t(
|
||||
"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.",
|
||||
);
|
||||
}
|
||||
|
||||
let title = this.state.isPublic ? _t('Create a public room') : _t('Create a private room');
|
||||
if (TagOrderStore.getSelectedPrototypeTag()) {
|
||||
const summary = GroupStore.getSummary(TagOrderStore.getSelectedPrototypeTag());
|
||||
const name = summary?.profile?.name || TagOrderStore.getSelectedPrototypeTag();
|
||||
title = _t("Create a room in %(communityName)s", {communityName: name});
|
||||
}
|
||||
return (
|
||||
<BaseDialog className="mx_CreateRoomDialog" onFinished={this.props.onFinished}
|
||||
title={title}
|
||||
|
@ -227,7 +258,15 @@ export default createReactClass({
|
|||
{ aliasField }
|
||||
<details ref={this.collectDetailsRef} className="mx_CreateRoomDialog_details">
|
||||
<summary className="mx_CreateRoomDialog_details_summary">{ this.state.detailsOpen ? _t('Hide advanced') : _t('Show advanced') }</summary>
|
||||
<LabelledToggleSwitch label={ _t('Block users on other matrix homeservers from joining this room (This setting cannot be changed later!)')} onChange={this.onNoFederateChange} value={this.state.noFederate} />
|
||||
<LabelledToggleSwitch
|
||||
label={_t(
|
||||
"Block anyone not part of %(serverName)s from ever joining this room.",
|
||||
{serverName: MatrixClientPeg.getHomeserverName()},
|
||||
)}
|
||||
onChange={this.onNoFederateChange}
|
||||
value={this.state.noFederate}
|
||||
/>
|
||||
<p>{federateLabel}</p>
|
||||
</details>
|
||||
</div>
|
||||
</form>
|
||||
|
|
19
src/components/views/dialogs/IDialogProps.ts
Normal file
19
src/components/views/dialogs/IDialogProps.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
export interface IDialogProps {
|
||||
onFinished: (bool) => void;
|
||||
}
|
|
@ -327,7 +327,7 @@ export default class InviteDialog extends React.PureComponent {
|
|||
this.state = {
|
||||
targets: [], // array of Member objects (see interface above)
|
||||
filterText: "",
|
||||
recents: this._buildRecents(alreadyInvited),
|
||||
recents: InviteDialog.buildRecents(alreadyInvited),
|
||||
numRecentsShown: INITIAL_ROOMS_SHOWN,
|
||||
suggestions: this._buildSuggestions(alreadyInvited),
|
||||
numSuggestionsShown: INITIAL_ROOMS_SHOWN,
|
||||
|
@ -344,7 +344,7 @@ export default class InviteDialog extends React.PureComponent {
|
|||
this._editorRef = createRef();
|
||||
}
|
||||
|
||||
_buildRecents(excludedTargetIds: Set<string>): {userId: string, user: RoomMember, lastActive: number} {
|
||||
static buildRecents(excludedTargetIds: Set<string>): {userId: string, user: RoomMember, lastActive: number} {
|
||||
const rooms = DMRoomMap.shared().getUniqueRoomsWithIndividuals(); // map of userId => js-sdk Room
|
||||
|
||||
// Also pull in all the rooms tagged as DefaultTagID.DM so we don't miss anything. Sometimes the
|
||||
|
|
|
@ -27,9 +27,9 @@ import Spinner from "../elements/Spinner";
|
|||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import { IDialogProps } from "./IDialogProps";
|
||||
|
||||
interface IProps {
|
||||
onFinished: (bool) => void;
|
||||
interface IProps extends IDialogProps {
|
||||
}
|
||||
|
||||
export default class ServerOfflineDialog extends React.PureComponent<IProps> {
|
||||
|
|
|
@ -31,6 +31,7 @@ import {toRightOf} from "../../structures/ContextMenu";
|
|||
import {copyPlaintext, selectText} from "../../../utils/strings";
|
||||
import StyledCheckbox from '../elements/StyledCheckbox';
|
||||
import AccessibleTooltipButton from '../elements/AccessibleTooltipButton';
|
||||
import { IDialogProps } from "./IDialogProps";
|
||||
|
||||
const socials = [
|
||||
{
|
||||
|
@ -60,8 +61,7 @@ const socials = [
|
|||
},
|
||||
];
|
||||
|
||||
interface IProps {
|
||||
onFinished: () => void;
|
||||
interface IProps extends IDialogProps {
|
||||
target: Room | User | Group | RoomMember | MatrixEvent;
|
||||
permalinkCreator: RoomPermalinkCreator;
|
||||
}
|
||||
|
@ -186,8 +186,8 @@ export default class ShareDialog extends React.PureComponent<IProps, IState> {
|
|||
title = _t('Share Room Message');
|
||||
checkbox = <div>
|
||||
<StyledCheckbox
|
||||
checked={this.state.linkSpecificEvent}
|
||||
onClick={this.onLinkSpecificEventCheckboxClick}
|
||||
checked={this.state.linkSpecificEvent}
|
||||
onClick={this.onLinkSpecificEventCheckboxClick}
|
||||
>
|
||||
{ _t('Link to selected message') }
|
||||
</StyledCheckbox>
|
||||
|
@ -198,16 +198,18 @@ export default class ShareDialog extends React.PureComponent<IProps, IState> {
|
|||
const encodedUrl = encodeURIComponent(matrixToUrl);
|
||||
|
||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||
return <BaseDialog title={title}
|
||||
className='mx_ShareDialog'
|
||||
contentId='mx_Dialog_content'
|
||||
onFinished={this.props.onFinished}
|
||||
return <BaseDialog
|
||||
title={title}
|
||||
className='mx_ShareDialog'
|
||||
contentId='mx_Dialog_content'
|
||||
onFinished={this.props.onFinished}
|
||||
>
|
||||
<div className="mx_ShareDialog_content">
|
||||
<div className="mx_ShareDialog_matrixto">
|
||||
<a href={matrixToUrl}
|
||||
onClick={ShareDialog.onLinkClick}
|
||||
className="mx_ShareDialog_matrixto_link"
|
||||
<a
|
||||
href={matrixToUrl}
|
||||
onClick={ShareDialog.onLinkClick}
|
||||
className="mx_ShareDialog_matrixto_link"
|
||||
>
|
||||
{ matrixToUrl }
|
||||
</a>
|
||||
|
|
|
@ -735,7 +735,7 @@ export default class AppTile extends React.Component {
|
|||
|
||||
// Additional iframe feature pemissions
|
||||
// (see - https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-permissions-in-cross-origin-iframes and https://wicg.github.io/feature-policy/)
|
||||
const iframeFeatures = "microphone; camera; encrypted-media; autoplay;";
|
||||
const iframeFeatures = "microphone; camera; encrypted-media; autoplay; display-capture;";
|
||||
|
||||
const appTileBodyClass = 'mx_AppTileBody' + (this.props.miniMode ? '_mini ' : ' ');
|
||||
|
||||
|
|
|
@ -34,7 +34,6 @@ export interface ILocationState {
|
|||
}
|
||||
|
||||
export default class Draggable extends React.Component<IProps, IState> {
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
|
@ -77,5 +76,4 @@ export default class Draggable extends React.Component<IProps, IState> {
|
|||
render() {
|
||||
return <div className={this.props.className} onMouseDown={this.onMouseDown.bind(this)} />;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,11 +39,13 @@ interface IProps {
|
|||
className: string;
|
||||
}
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
interface IState {
|
||||
userId: string;
|
||||
displayname: string;
|
||||
avatar_url: string;
|
||||
}
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
const AVATAR_SIZE = 32;
|
||||
|
||||
|
@ -63,19 +65,18 @@ export default class EventTilePreview extends React.Component<IProps, IState> {
|
|||
const client = MatrixClientPeg.get();
|
||||
const userId = client.getUserId();
|
||||
const profileInfo = await client.getProfileInfo(userId);
|
||||
const avatar_url = Avatar.avatarUrlForUser(
|
||||
const avatarUrl = Avatar.avatarUrlForUser(
|
||||
{avatarUrl: profileInfo.avatar_url},
|
||||
AVATAR_SIZE, AVATAR_SIZE, "crop");
|
||||
|
||||
this.setState({
|
||||
userId,
|
||||
displayname: profileInfo.displayname,
|
||||
avatar_url,
|
||||
avatar_url: avatarUrl,
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private fakeEvent({userId, displayname, avatar_url}: IState) {
|
||||
private fakeEvent({userId, displayname, avatar_url: avatarUrl}: IState) {
|
||||
// Fake it till we make it
|
||||
const event = new MatrixEvent(JSON.parse(`{
|
||||
"type": "m.room.message",
|
||||
|
@ -85,12 +86,12 @@ export default class EventTilePreview extends React.Component<IProps, IState> {
|
|||
"msgtype": "m.text",
|
||||
"body": "${this.props.message}",
|
||||
"displayname": "${displayname}",
|
||||
"avatar_url": "${avatar_url}"
|
||||
"avatar_url": "${avatarUrl}"
|
||||
},
|
||||
"msgtype": "m.text",
|
||||
"body": "${this.props.message}",
|
||||
"displayname": "${displayname}",
|
||||
"avatar_url": "${avatar_url}"
|
||||
"avatar_url": "${avatarUrl}"
|
||||
},
|
||||
"unsigned": {
|
||||
"age": 97
|
||||
|
@ -104,7 +105,7 @@ export default class EventTilePreview extends React.Component<IProps, IState> {
|
|||
name: displayname,
|
||||
userId: userId,
|
||||
getAvatarUrl: (..._) => {
|
||||
return avatar_url;
|
||||
return avatarUrl;
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -114,13 +115,10 @@ export default class EventTilePreview extends React.Component<IProps, IState> {
|
|||
public render() {
|
||||
const event = this.fakeEvent(this.state);
|
||||
|
||||
let className = classnames(
|
||||
this.props.className,
|
||||
{
|
||||
"mx_IRCLayout": this.props.useIRCLayout,
|
||||
"mx_GroupLayout": !this.props.useIRCLayout,
|
||||
}
|
||||
);
|
||||
const className = classnames(this.props.className, {
|
||||
"mx_IRCLayout": this.props.useIRCLayout,
|
||||
"mx_GroupLayout": !this.props.useIRCLayout,
|
||||
});
|
||||
|
||||
return <div className={className}>
|
||||
<EventTile mxEvent={event} useIRCLayout={this.props.useIRCLayout} />
|
||||
|
|
|
@ -198,11 +198,9 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public render() {
|
||||
const {
|
||||
element, prefixComponent, postfixComponent, className, onValidate, children,
|
||||
/* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */
|
||||
const { element, prefixComponent, postfixComponent, className, onValidate, children,
|
||||
tooltipContent, forceValidity, tooltipClassName, list, ...inputProps} = this.props;
|
||||
|
||||
// Set some defaults for the <input> element
|
||||
|
|
|
@ -78,7 +78,12 @@ export default class IRCTimelineProfileResizer extends React.Component<IProps, I
|
|||
|
||||
private onMoueUp(event: MouseEvent) {
|
||||
if (this.props.roomId) {
|
||||
SettingsStore.setValue("ircDisplayNameWidth", this.props.roomId, SettingLevel.ROOM_DEVICE, this.state.width);
|
||||
SettingsStore.setValue(
|
||||
"ircDisplayNameWidth",
|
||||
this.props.roomId,
|
||||
SettingLevel.ROOM_DEVICE,
|
||||
this.state.width,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
72
src/components/views/elements/InfoTooltip.tsx
Normal file
72
src/components/views/elements/InfoTooltip.tsx
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
||||
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 classNames from 'classnames';
|
||||
|
||||
import Tooltip from './Tooltip';
|
||||
import { _t } from "../../../languageHandler";
|
||||
|
||||
interface ITooltipProps {
|
||||
tooltip?: React.ReactNode;
|
||||
tooltipClassName?: string;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
hover: boolean;
|
||||
}
|
||||
|
||||
export default class InfoTooltip extends React.PureComponent<ITooltipProps, IState> {
|
||||
constructor(props: ITooltipProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
hover: false,
|
||||
};
|
||||
}
|
||||
|
||||
onMouseOver = () => {
|
||||
this.setState({
|
||||
hover: true,
|
||||
});
|
||||
};
|
||||
|
||||
onMouseLeave = () => {
|
||||
this.setState({
|
||||
hover: false,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const {tooltip, children, tooltipClassName} = this.props;
|
||||
const title = _t("Information");
|
||||
|
||||
// Tooltip are forced on the right for a more natural feel to them on info icons
|
||||
const tip = this.state.hover ? <Tooltip
|
||||
className="mx_InfoTooltip_container"
|
||||
tooltipClassName={classNames("mx_InfoTooltip_tooltip", tooltipClassName)}
|
||||
label={tooltip || title}
|
||||
forceOnRight={true}
|
||||
/> : <div />;
|
||||
return (
|
||||
<div onMouseOver={this.onMouseOver} onMouseLeave={this.onMouseLeave} className="mx_InfoTooltip">
|
||||
<span className="mx_InfoTooltip_icon" aria-label={title} />
|
||||
{children}
|
||||
{tip}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -41,7 +41,7 @@ const QRCode: React.FC<IProps> = ({data, className, ...options}) => {
|
|||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [JSON.stringify(data), options]);
|
||||
}, [JSON.stringify(data), options]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return <div className={classNames("mx_QRCode", className)}>
|
||||
{ dataUri ? <img src={dataUri} className="mx_VerificationQRCode" alt={_t("QR Code")} /> : <Spinner /> }
|
||||
|
|
|
@ -45,7 +45,7 @@ export default class Slider extends React.Component<IProps> {
|
|||
// non linear slider.
|
||||
private offset(values: number[], value: number): number {
|
||||
// the index of the first number greater than value.
|
||||
let closest = values.reduce((prev, curr) => {
|
||||
const closest = values.reduce((prev, curr) => {
|
||||
return (value > curr ? prev + 1 : prev);
|
||||
}, 0);
|
||||
|
||||
|
@ -68,17 +68,16 @@ export default class Slider extends React.Component<IProps> {
|
|||
const linearInterpolation = (value - closestLessValue) / (closestGreaterValue - closestLessValue);
|
||||
|
||||
return 100 * (closest - 1 + linearInterpolation) * intervalWidth;
|
||||
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
const dots = this.props.values.map(v =>
|
||||
<Dot active={v <= this.props.value}
|
||||
label={this.props.displayFunc(v)}
|
||||
onClick={this.props.disabled ? () => {} : () => this.props.onSelectionChange(v)}
|
||||
key={v}
|
||||
disabled={this.props.disabled}
|
||||
/>);
|
||||
const dots = this.props.values.map(v => <Dot
|
||||
active={v <= this.props.value}
|
||||
label={this.props.displayFunc(v)}
|
||||
onClick={this.props.disabled ? () => {} : () => this.props.onSelectionChange(v)}
|
||||
key={v}
|
||||
disabled={this.props.disabled}
|
||||
/>);
|
||||
|
||||
let selection = null;
|
||||
|
||||
|
@ -93,7 +92,7 @@ export default class Slider extends React.Component<IProps> {
|
|||
return <div className="mx_Slider">
|
||||
<div>
|
||||
<div className="mx_Slider_bar">
|
||||
<hr onClick={this.props.disabled ? () => {} : this.onClick.bind(this)}/>
|
||||
<hr onClick={this.props.disabled ? () => {} : this.onClick.bind(this)} />
|
||||
{ selection }
|
||||
</div>
|
||||
<div className="mx_Slider_dotContainer">
|
||||
|
|
|
@ -17,8 +17,6 @@ limitations under the License.
|
|||
import React from "react";
|
||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||
|
||||
const CHECK_BOX_SVG = require("../../../../res/img/feather-customised/check.svg");
|
||||
|
||||
interface IProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
}
|
||||
|
||||
|
@ -39,13 +37,14 @@ export default class StyledCheckbox extends React.PureComponent<IProps, IState>
|
|||
}
|
||||
|
||||
public render() {
|
||||
/* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */
|
||||
const { children, className, ...otherProps } = this.props;
|
||||
return <span className={"mx_Checkbox " + className}>
|
||||
<input id={this.id} {...otherProps} type="checkbox" />
|
||||
<label htmlFor={this.id}>
|
||||
{/* Using the div to center the image */}
|
||||
<div className="mx_Checkbox_background">
|
||||
<img src={CHECK_BOX_SVG}/>
|
||||
<img src={require("../../../../res/img/feather-customised/check.svg")} />
|
||||
</div>
|
||||
<div>
|
||||
{ this.props.children }
|
||||
|
@ -53,4 +52,4 @@ export default class StyledCheckbox extends React.PureComponent<IProps, IState>
|
|||
</label>
|
||||
</span>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ import GroupStore from '../../../stores/GroupStore';
|
|||
import TagOrderStore from '../../../stores/TagOrderStore';
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import AccessibleButton from "./AccessibleButton";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
|
||||
// A class for a child of TagPanel (possibly wrapped in a DNDTagTile) that represents
|
||||
// a thing to click on for the user to filter the visible rooms in the RoomList to:
|
||||
|
@ -46,6 +47,7 @@ export default createReactClass({
|
|||
contextMenuButtonRef: PropTypes.object,
|
||||
openMenu: PropTypes.func,
|
||||
menuDisplayed: PropTypes.bool,
|
||||
selected: PropTypes.bool,
|
||||
},
|
||||
|
||||
statics: {
|
||||
|
@ -112,6 +114,7 @@ export default createReactClass({
|
|||
},
|
||||
|
||||
onMouseOver: function() {
|
||||
if (SettingsStore.getValue("feature_communities_v2_prototypes")) return;
|
||||
this.setState({ hover: true });
|
||||
},
|
||||
|
||||
|
@ -123,6 +126,7 @@ export default createReactClass({
|
|||
// Prevent the TagTile onClick event firing as well
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
if (SettingsStore.getValue("feature_communities_v2_prototypes")) return;
|
||||
this.setState({ hover: false });
|
||||
this.props.openMenu();
|
||||
},
|
||||
|
@ -137,9 +141,12 @@ export default createReactClass({
|
|||
profile.avatarUrl, avatarHeight, avatarHeight, "crop",
|
||||
) : null;
|
||||
|
||||
const isPrototype = SettingsStore.getValue("feature_communities_v2_prototypes");
|
||||
const className = classNames({
|
||||
mx_TagTile: true,
|
||||
mx_TagTile_selected: this.props.selected,
|
||||
mx_TagTile_prototype: isPrototype,
|
||||
mx_TagTile_selected: this.props.selected && !isPrototype,
|
||||
mx_TagTile_selected_prototype: this.props.selected && isPrototype,
|
||||
});
|
||||
|
||||
const badge = TagOrderStore.getGroupBadge(this.props.tag);
|
||||
|
|
|
@ -1,76 +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';
|
||||
import createReactClass from 'create-react-class';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
export default createReactClass({
|
||||
displayName: 'UserSelector',
|
||||
|
||||
propTypes: {
|
||||
onChange: PropTypes.func,
|
||||
selected_users: PropTypes.arrayOf(PropTypes.string),
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
onChange: function() {},
|
||||
selected: [],
|
||||
};
|
||||
},
|
||||
|
||||
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
|
||||
UNSAFE_componentWillMount: function() {
|
||||
this._user_id_input = createRef();
|
||||
},
|
||||
|
||||
addUser: function(user_id) {
|
||||
if (this.props.selected_users.indexOf(user_id == -1)) {
|
||||
this.props.onChange(this.props.selected_users.concat([user_id]));
|
||||
}
|
||||
},
|
||||
|
||||
removeUser: function(user_id) {
|
||||
this.props.onChange(this.props.selected_users.filter(function(e) {
|
||||
return e != user_id;
|
||||
}));
|
||||
},
|
||||
|
||||
onAddUserId: function() {
|
||||
this.addUser(this._user_id_input.current.value);
|
||||
this._user_id_input.current.value = "";
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const self = this;
|
||||
return (
|
||||
<div>
|
||||
<ul className="mx_UserSelector_UserIdList">
|
||||
{ this.props.selected_users.map(function(user_id, i) {
|
||||
return <li key={user_id}>{ user_id } - <span onClick={function() {self.removeUser(user_id);}}>X</span></li>;
|
||||
}) }
|
||||
</ul>
|
||||
<input type="text" ref={this._user_id_input} defaultValue="" className="mx_UserSelector_userIdInput" placeholder={_t("ex. @bob:example.com")} />
|
||||
<button onClick={this.onAddUserId} className="mx_UserSelector_AddUserId">
|
||||
{ _t("Add User") }
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
85
src/components/views/elements/UserTagTile.tsx
Normal file
85
src/components/views/elements/UserTagTile.tsx
Normal file
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
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 defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||
import * as fbEmitter from "fbemitter";
|
||||
import TagOrderStore from "../../../stores/TagOrderStore";
|
||||
import AccessibleTooltipButton from "./AccessibleTooltipButton";
|
||||
import classNames from "classnames";
|
||||
import { _t } from "../../../languageHandler";
|
||||
|
||||
interface IProps {
|
||||
}
|
||||
|
||||
interface IState {
|
||||
selected: boolean;
|
||||
}
|
||||
|
||||
export default class UserTagTile extends React.PureComponent<IProps, IState> {
|
||||
private tagStoreRef: fbEmitter.EventSubscription;
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
selected: TagOrderStore.getSelectedTags().length === 0,
|
||||
};
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
this.tagStoreRef = TagOrderStore.addListener(this.onTagStoreUpdate);
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
this.tagStoreRef.remove();
|
||||
}
|
||||
|
||||
private onTagStoreUpdate = () => {
|
||||
const selected = TagOrderStore.getSelectedTags().length === 0;
|
||||
this.setState({selected});
|
||||
};
|
||||
|
||||
private onTileClick = (ev) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
// Deselect all tags
|
||||
defaultDispatcher.dispatch({action: "deselect_tags"});
|
||||
};
|
||||
|
||||
public render() {
|
||||
// XXX: We reuse TagTile classes for ease of demonstration - we should probably generify
|
||||
// TagTile instead if we continue to use this component.
|
||||
const className = classNames({
|
||||
mx_TagTile: true,
|
||||
mx_TagTile_prototype: true,
|
||||
mx_TagTile_selected_prototype: this.state.selected,
|
||||
mx_TagTile_home: true,
|
||||
});
|
||||
return (
|
||||
<AccessibleTooltipButton
|
||||
className={className}
|
||||
onClick={this.onTileClick}
|
||||
title={_t("Home")}
|
||||
>
|
||||
<div className="mx_TagTile_avatar">
|
||||
<div className="mx_TagTile_homeIcon" />
|
||||
</div>
|
||||
</AccessibleTooltipButton>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -15,13 +15,13 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, {forwardRef} from "react";
|
||||
|
||||
export default ({mxEvent}) => {
|
||||
export default forwardRef(({mxEvent}, ref) => {
|
||||
const text = mxEvent.getContent().body;
|
||||
return (
|
||||
<span className="mx_UnknownBody">
|
||||
<span className="mx_UnknownBody" ref={ref}>
|
||||
{ text }
|
||||
</span>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
|
|
@ -76,14 +76,16 @@ const EncryptionInfo: React.FC<IProps> = ({
|
|||
description = (
|
||||
<div>
|
||||
<p>{_t("Messages in this room are end-to-end encrypted.")}</p>
|
||||
<p>{_t("Your messages are secured and only you and the recipient have the unique keys to unlock them.")}</p>
|
||||
<p>{_t("Your messages are secured and only you and the recipient have " +
|
||||
"the unique keys to unlock them.")}</p>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
description = (
|
||||
<div>
|
||||
<p>{_t("Messages in this room are not end-to-end encrypted.")}</p>
|
||||
<p>{_t("In encrypted rooms, your messages are secured and only you and the recipient have the unique keys to unlock them.")}</p>
|
||||
<p>{_t("In encrypted rooms, your messages are secured and only you and the recipient have " +
|
||||
"the unique keys to unlock them.")}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -23,7 +23,10 @@ import dis from '../../../dispatcher/dispatcher';
|
|||
import RightPanelStore from "../../../stores/RightPanelStore";
|
||||
import {RightPanelPhases} from "../../../stores/RightPanelStorePhases";
|
||||
import {Action} from '../../../dispatcher/actions';
|
||||
import {SetRightPanelPhasePayload, SetRightPanelPhaseRefireParams} from '../../../dispatcher/payloads/SetRightPanelPhasePayload';
|
||||
import {
|
||||
SetRightPanelPhasePayload,
|
||||
SetRightPanelPhaseRefireParams,
|
||||
} from '../../../dispatcher/payloads/SetRightPanelPhasePayload';
|
||||
import {EventSubscription} from "fbemitter";
|
||||
|
||||
export enum HeaderKind {
|
||||
|
@ -38,7 +41,7 @@ interface IState {
|
|||
|
||||
interface IProps {}
|
||||
|
||||
export default class HeaderButtons extends React.Component<IProps, IState> {
|
||||
export default abstract class HeaderButtons extends React.Component<IProps, IState> {
|
||||
private storeToken: EventSubscription;
|
||||
private dispatcherRef: string;
|
||||
|
||||
|
@ -92,14 +95,7 @@ export default class HeaderButtons extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
// XXX: Make renderButtons a prop
|
||||
public renderButtons(): JSX.Element[] {
|
||||
// Ignore - intended to be overridden by subclasses
|
||||
// Return empty fragment to satisfy the type
|
||||
return [
|
||||
<React.Fragment>
|
||||
</React.Fragment>
|
||||
];
|
||||
}
|
||||
public abstract renderButtons(): JSX.Element[];
|
||||
|
||||
public render() {
|
||||
// inline style as this will be swapped around in future commits
|
||||
|
|
|
@ -30,8 +30,6 @@ import {_t} from "../../../languageHandler";
|
|||
import SdkConfig from "../../../SdkConfig";
|
||||
import E2EIcon from "../rooms/E2EIcon";
|
||||
import {
|
||||
PHASE_UNSENT,
|
||||
PHASE_REQUESTED,
|
||||
PHASE_READY,
|
||||
PHASE_DONE,
|
||||
PHASE_STARTED,
|
||||
|
@ -104,14 +102,15 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
|
|||
</div>;
|
||||
}
|
||||
if (showSAS) {
|
||||
sasBlockDialog =
|
||||
<div className='mx_VerificationPanel_QRPhase_startOption'>
|
||||
<p>{_t("Compare unique emoji")}</p>
|
||||
<span className='mx_VerificationPanel_QRPhase_helpText'>{_t("Compare a unique set of emoji if you don't have a camera on either device")}</span>
|
||||
<AccessibleButton disabled={this.state.emojiButtonClicked} onClick={this.startSAS} kind='primary'>
|
||||
{_t("Start")}
|
||||
</AccessibleButton>
|
||||
</div>;
|
||||
sasBlockDialog = <div className='mx_VerificationPanel_QRPhase_startOption'>
|
||||
<p>{_t("Compare unique emoji")}</p>
|
||||
<span className='mx_VerificationPanel_QRPhase_helpText'>
|
||||
{_t("Compare a unique set of emoji if you don't have a camera on either device")}
|
||||
</span>
|
||||
<AccessibleButton disabled={this.state.emojiButtonClicked} onClick={this.startSAS} kind='primary'>
|
||||
{_t("Start")}
|
||||
</AccessibleButton>
|
||||
</div>;
|
||||
}
|
||||
const or = qrBlockDialog && sasBlockDialog ?
|
||||
<div className='mx_VerificationPanel_QRPhase_betweenText'>{_t("or")}</div> : null;
|
||||
|
@ -165,8 +164,8 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
|
|||
}
|
||||
|
||||
const noCommonMethodBlock = noCommonMethodError ?
|
||||
<div className="mx_UserInfo_container">{noCommonMethodError}</div> :
|
||||
null;
|
||||
<div className="mx_UserInfo_container">{noCommonMethodError}</div> :
|
||||
null;
|
||||
|
||||
// TODO: add way to open camera to scan a QR code
|
||||
return <React.Fragment>
|
||||
|
|
|
@ -92,6 +92,7 @@ export default class NotificationBadge extends React.PureComponent<XOR<IProps, I
|
|||
};
|
||||
|
||||
public render(): React.ReactElement {
|
||||
/* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */
|
||||
const {notification, forceCount, roomId, onClick, ...props} = this.props;
|
||||
|
||||
// Don't show a badge if we don't need to
|
||||
|
|
|
@ -45,6 +45,7 @@ import { arrayFastClone, arrayHasDiff } from "../../../utils/arrays";
|
|||
import { objectShallowClone, objectWithOnly } from "../../../utils/objects";
|
||||
import { IconizedContextMenuOption, IconizedContextMenuOptionList } from "../context_menus/IconizedContextMenu";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import TagOrderStore from "../../../stores/TagOrderStore";
|
||||
|
||||
interface IProps {
|
||||
onKeyDown: (ev: React.KeyboardEvent) => void;
|
||||
|
@ -129,7 +130,9 @@ const TAG_AESTHETICS: {
|
|||
}}
|
||||
/>
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Explore public rooms")}
|
||||
label={TagOrderStore.getSelectedPrototypeTag()
|
||||
? _t("Explore community rooms")
|
||||
: _t("Explore public rooms")}
|
||||
iconClassName="mx_RoomList_iconExplore"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
|
@ -215,7 +218,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
|||
|
||||
private getRoomDelta = (roomId: string, delta: number, unread = false) => {
|
||||
const lists = RoomListStore.instance.orderedLists;
|
||||
let rooms: Room = [];
|
||||
const rooms: Room = [];
|
||||
TAG_ORDER.forEach(t => {
|
||||
let listRooms = lists[t];
|
||||
|
||||
|
@ -287,7 +290,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
|||
// TODO: Put community invites in a more sensible place (not in the room list)
|
||||
// See https://github.com/vector-im/element-web/issues/14456
|
||||
return MatrixClientPeg.get().getGroups().filter(g => {
|
||||
return g.myMembership === 'invite';
|
||||
return g.myMembership === 'invite';
|
||||
}).map(g => {
|
||||
const avatar = (
|
||||
<GroupAvatar
|
||||
|
@ -343,21 +346,19 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
|||
: TAG_AESTHETICS[orderedTagId];
|
||||
if (!aesthetics) throw new Error(`Tag ${orderedTagId} does not have aesthetics`);
|
||||
|
||||
components.push(
|
||||
<RoomSublist
|
||||
key={`sublist-${orderedTagId}`}
|
||||
tagId={orderedTagId}
|
||||
forRooms={true}
|
||||
startAsHidden={aesthetics.defaultHidden}
|
||||
label={aesthetics.sectionLabelRaw ? aesthetics.sectionLabelRaw : _t(aesthetics.sectionLabel)}
|
||||
onAddRoom={aesthetics.onAddRoom}
|
||||
addRoomLabel={aesthetics.addRoomLabel ? _t(aesthetics.addRoomLabel) : aesthetics.addRoomLabel}
|
||||
addRoomContextMenu={aesthetics.addRoomContextMenu}
|
||||
isMinimized={this.props.isMinimized}
|
||||
onResize={this.props.onResize}
|
||||
extraBadTilesThatShouldntExist={extraTiles}
|
||||
/>
|
||||
);
|
||||
components.push(<RoomSublist
|
||||
key={`sublist-${orderedTagId}`}
|
||||
tagId={orderedTagId}
|
||||
forRooms={true}
|
||||
startAsHidden={aesthetics.defaultHidden}
|
||||
label={aesthetics.sectionLabelRaw ? aesthetics.sectionLabelRaw : _t(aesthetics.sectionLabel)}
|
||||
onAddRoom={aesthetics.onAddRoom}
|
||||
addRoomLabel={aesthetics.addRoomLabel ? _t(aesthetics.addRoomLabel) : aesthetics.addRoomLabel}
|
||||
addRoomContextMenu={aesthetics.addRoomContextMenu}
|
||||
isMinimized={this.props.isMinimized}
|
||||
onResize={this.props.onResize}
|
||||
extraBadTilesThatShouldntExist={extraTiles}
|
||||
/>);
|
||||
}
|
||||
|
||||
return components;
|
||||
|
|
|
@ -1,80 +0,0 @@
|
|||
/*
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
|
||||
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 createReactClass from 'create-react-class';
|
||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||
import * as sdk from "../../../index";
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
export default createReactClass({
|
||||
displayName: 'RoomNameEditor',
|
||||
|
||||
propTypes: {
|
||||
room: PropTypes.object.isRequired,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
name: null,
|
||||
};
|
||||
},
|
||||
|
||||
// TODO: [REACT-WARNING] Move this to constructor
|
||||
UNSAFE_componentWillMount: function() {
|
||||
const room = this.props.room;
|
||||
const name = room.currentState.getStateEvents('m.room.name', '');
|
||||
const myId = MatrixClientPeg.get().credentials.userId;
|
||||
const defaultName = room.getDefaultRoomName(myId);
|
||||
|
||||
this.setState({
|
||||
name: name ? name.getContent().name : '',
|
||||
});
|
||||
|
||||
this._placeholderName = _t("Unnamed Room");
|
||||
if (defaultName && defaultName !== 'Empty room') { // default name from JS SDK, needs no translation as we don't ever show it.
|
||||
this._placeholderName += " (" + defaultName + ")";
|
||||
}
|
||||
},
|
||||
|
||||
getRoomName: function() {
|
||||
return this.state.name;
|
||||
},
|
||||
|
||||
_onValueChanged: function(value, shouldSubmit) {
|
||||
this.setState({
|
||||
name: value,
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const EditableText = sdk.getComponent("elements.EditableText");
|
||||
|
||||
return (
|
||||
<div className="mx_RoomHeader_name">
|
||||
<EditableText
|
||||
className="mx_RoomHeader_nametext mx_RoomHeader_editable"
|
||||
placeholderClassName="mx_RoomHeader_placeholder"
|
||||
placeholder={this._placeholderName}
|
||||
blurToCancel={false}
|
||||
initialValue={this.state.name}
|
||||
onValueChanged={this._onValueChanged}
|
||||
dir="auto" />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
|
@ -26,6 +26,8 @@ import classNames from 'classnames';
|
|||
import { _t } from '../../../languageHandler';
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import IdentityAuthClient from '../../../IdentityAuthClient';
|
||||
import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore";
|
||||
import {UPDATE_EVENT} from "../../../stores/AsyncStore";
|
||||
|
||||
const MessageCase = Object.freeze({
|
||||
NotLoggedIn: "NotLoggedIn",
|
||||
|
@ -100,6 +102,7 @@ export default createReactClass({
|
|||
|
||||
componentDidMount: function() {
|
||||
this._checkInvitedEmail();
|
||||
CommunityPrototypeStore.instance.on(UPDATE_EVENT, this._onCommunityUpdate);
|
||||
},
|
||||
|
||||
componentDidUpdate: function(prevProps, prevState) {
|
||||
|
@ -108,6 +111,10 @@ export default createReactClass({
|
|||
}
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
CommunityPrototypeStore.instance.off(UPDATE_EVENT, this._onCommunityUpdate);
|
||||
},
|
||||
|
||||
_checkInvitedEmail: async function() {
|
||||
// If this is an invite and we've been told what email address was
|
||||
// invited, fetch the user's account emails and discovery bindings so we
|
||||
|
@ -143,6 +150,13 @@ export default createReactClass({
|
|||
}
|
||||
},
|
||||
|
||||
_onCommunityUpdate: function (roomId) {
|
||||
if (this.props.room && this.props.room.roomId !== roomId) {
|
||||
return;
|
||||
}
|
||||
this.forceUpdate(); // we have nothing to update
|
||||
},
|
||||
|
||||
_getMessageCase() {
|
||||
const isGuest = MatrixClientPeg.get().isGuest();
|
||||
|
||||
|
@ -219,8 +233,15 @@ export default createReactClass({
|
|||
}
|
||||
},
|
||||
|
||||
_communityProfile: function() {
|
||||
if (this.props.room) return CommunityPrototypeStore.instance.getInviteProfile(this.props.room.roomId);
|
||||
return {displayName: null, avatarMxc: null};
|
||||
},
|
||||
|
||||
_roomName: function(atStart = false) {
|
||||
const name = this.props.room ? this.props.room.name : this.props.roomAlias;
|
||||
let name = this.props.room ? this.props.room.name : this.props.roomAlias;
|
||||
const profile = this._communityProfile();
|
||||
if (profile.displayName) name = profile.displayName;
|
||||
if (name) {
|
||||
return name;
|
||||
} else if (atStart) {
|
||||
|
@ -439,7 +460,10 @@ export default createReactClass({
|
|||
}
|
||||
case MessageCase.Invite: {
|
||||
const RoomAvatar = sdk.getComponent("views.avatars.RoomAvatar");
|
||||
const avatar = <RoomAvatar room={this.props.room} oobData={this.props.oobData} />;
|
||||
const oobData = Object.assign({}, this.props.oobData, {
|
||||
avatarUrl: this._communityProfile().avatarMxc,
|
||||
});
|
||||
const avatar = <RoomAvatar room={this.props.room} oobData={oobData} />;
|
||||
|
||||
const inviteMember = this._getInviteMember();
|
||||
let inviterElement;
|
||||
|
|
|
@ -517,15 +517,13 @@ export default class RoomSublist extends React.Component<IProps, IState> {
|
|||
if (this.state.rooms) {
|
||||
const visibleRooms = this.state.rooms.slice(0, this.numVisibleTiles);
|
||||
for (const room of visibleRooms) {
|
||||
tiles.push(
|
||||
<RoomTile
|
||||
room={room}
|
||||
key={`room-${room.roomId}`}
|
||||
showMessagePreview={this.layout.showPreviews}
|
||||
isMinimized={this.props.isMinimized}
|
||||
tag={this.props.tagId}
|
||||
/>
|
||||
);
|
||||
tiles.push(<RoomTile
|
||||
room={room}
|
||||
key={`room-${room.roomId}`}
|
||||
showMessagePreview={this.layout.showPreviews}
|
||||
isMinimized={this.props.isMinimized}
|
||||
tag={this.props.tagId}
|
||||
/>);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -710,7 +708,12 @@ export default class RoomSublist extends React.Component<IProps, IState> {
|
|||
// doesn't become sticky.
|
||||
// The same applies to the notification badge.
|
||||
return (
|
||||
<div className={classes} onKeyDown={this.onHeaderKeyDown} onFocus={onFocus} aria-label={this.props.label}>
|
||||
<div
|
||||
className={classes}
|
||||
onKeyDown={this.onHeaderKeyDown}
|
||||
onFocus={onFocus}
|
||||
aria-label={this.props.label}
|
||||
>
|
||||
<div className="mx_RoomSublist_stickable">
|
||||
<Button
|
||||
onFocus={onFocus}
|
||||
|
@ -762,7 +765,7 @@ export default class RoomSublist extends React.Component<IProps, IState> {
|
|||
const showMoreAtMinHeight = minTiles < this.numTiles;
|
||||
const minHeightPadding = RESIZE_HANDLE_HEIGHT + (showMoreAtMinHeight ? SHOW_N_BUTTON_HEIGHT : 0);
|
||||
const minTilesPx = layout.tilesToPixelsWithPadding(minTiles, minHeightPadding);
|
||||
let maxTilesPx = layout.tilesToPixelsWithPadding(this.numTiles, this.padding);
|
||||
const maxTilesPx = layout.tilesToPixelsWithPadding(this.numTiles, this.padding);
|
||||
const showMoreBtnClasses = classNames({
|
||||
'mx_RoomSublist_showNButton': true,
|
||||
});
|
||||
|
|
|
@ -27,11 +27,11 @@ import defaultDispatcher from '../../../dispatcher/dispatcher';
|
|||
import { Key } from "../../../Keyboard";
|
||||
import ActiveRoomObserver from "../../../ActiveRoomObserver";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { ChevronFace, ContextMenuTooltipButton, MenuItemRadio } from "../../structures/ContextMenu";
|
||||
import { ChevronFace, ContextMenuTooltipButton } from "../../structures/ContextMenu";
|
||||
import { DefaultTagID, TagID } from "../../../stores/room-list/models";
|
||||
import { MessagePreviewStore, ROOM_PREVIEW_CHANGED } from "../../../stores/room-list/MessagePreviewStore";
|
||||
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
|
||||
import { ALL_MESSAGES, ALL_MESSAGES_LOUD, MENTIONS_ONLY, MUTE, } from "../../../RoomNotifs";
|
||||
import { ALL_MESSAGES, ALL_MESSAGES_LOUD, MENTIONS_ONLY, MUTE } from "../../../RoomNotifs";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import NotificationBadge from "./NotificationBadge";
|
||||
import { Volume } from "../../../RoomNotifsTypes";
|
||||
|
@ -47,8 +47,11 @@ import { PROPERTY_UPDATED } from "../../../stores/local-echo/GenericEchoChamber"
|
|||
import IconizedContextMenu, {
|
||||
IconizedContextMenuCheckbox,
|
||||
IconizedContextMenuOption,
|
||||
IconizedContextMenuOptionList, IconizedContextMenuRadio
|
||||
IconizedContextMenuOptionList,
|
||||
IconizedContextMenuRadio,
|
||||
} from "../context_menus/IconizedContextMenu";
|
||||
import { CommunityPrototypeStore, IRoomProfile } from "../../../stores/CommunityPrototypeStore";
|
||||
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
|
||||
|
||||
interface IProps {
|
||||
room: Room;
|
||||
|
@ -101,6 +104,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
this.notificationState.on(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate);
|
||||
this.roomProps = EchoChamber.forRoom(this.props.room);
|
||||
this.roomProps.on(PROPERTY_UPDATED, this.onRoomPropertyUpdate);
|
||||
CommunityPrototypeStore.instance.on(UPDATE_EVENT, this.onCommunityUpdate);
|
||||
}
|
||||
|
||||
private onNotificationUpdate = () => {
|
||||
|
@ -140,6 +144,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
defaultDispatcher.unregister(this.dispatcherRef);
|
||||
MessagePreviewStore.instance.off(ROOM_PREVIEW_CHANGED, this.onRoomPreviewChanged);
|
||||
this.notificationState.off(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate);
|
||||
CommunityPrototypeStore.instance.off(UPDATE_EVENT, this.onCommunityUpdate);
|
||||
}
|
||||
|
||||
private onAction = (payload: ActionPayload) => {
|
||||
|
@ -150,6 +155,11 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
}
|
||||
};
|
||||
|
||||
private onCommunityUpdate = (roomId: string) => {
|
||||
if (roomId !== this.props.room.roomId) return;
|
||||
this.forceUpdate(); // we don't have anything to actually update
|
||||
};
|
||||
|
||||
private onRoomPreviewChanged = (room: Room) => {
|
||||
if (this.props.room && room.roomId === this.props.room.roomId) {
|
||||
// generatePreview() will return nothing if the user has previews disabled
|
||||
|
@ -239,7 +249,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
removeTag,
|
||||
addTag,
|
||||
undefined,
|
||||
0
|
||||
0,
|
||||
));
|
||||
} else {
|
||||
console.warn(`Unexpected tag ${tagId} applied to ${this.props.room.room_id}`);
|
||||
|
@ -461,11 +471,21 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
'mx_RoomTile_minimized': this.props.isMinimized,
|
||||
});
|
||||
|
||||
let roomProfile: IRoomProfile = {displayName: null, avatarMxc: null};
|
||||
if (this.props.tag === DefaultTagID.Invite) {
|
||||
roomProfile = CommunityPrototypeStore.instance.getInviteProfile(this.props.room.roomId);
|
||||
}
|
||||
|
||||
let name = roomProfile.displayName || this.props.room.name;
|
||||
if (typeof name !== 'string') name = '';
|
||||
name = name.replace(":", ":\u200b"); // add a zero-width space to allow linewrapping after the colon
|
||||
|
||||
const roomAvatar = <DecoratedRoomAvatar
|
||||
room={this.props.room}
|
||||
avatarSize={32}
|
||||
tag={this.props.tag}
|
||||
displayBadge={this.props.isMinimized}
|
||||
oobData={({avatarUrl: roomProfile.avatarMxc})}
|
||||
/>;
|
||||
|
||||
let badge: React.ReactNode;
|
||||
|
@ -482,10 +502,6 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
);
|
||||
}
|
||||
|
||||
let name = this.props.room.name;
|
||||
if (typeof name !== 'string') name = '';
|
||||
name = name.replace(":", ":\u200b"); // add a zero-width space to allow linewrapping after the colon
|
||||
|
||||
let messagePreview = null;
|
||||
if (this.showMessagePreview && this.state.messagePreview) {
|
||||
messagePreview = (
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
|
||||
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 createReactClass from 'create-react-class';
|
||||
import * as sdk from '../../../index';
|
||||
import { _t } from "../../../languageHandler";
|
||||
|
||||
export default createReactClass({
|
||||
displayName: 'RoomTopicEditor',
|
||||
|
||||
propTypes: {
|
||||
room: PropTypes.object.isRequired,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
topic: null,
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
const room = this.props.room;
|
||||
const topic = room.currentState.getStateEvents('m.room.topic', '');
|
||||
this.setState({
|
||||
topic: topic ? topic.getContent().topic : '',
|
||||
});
|
||||
},
|
||||
|
||||
getTopic: function() {
|
||||
return this.state.topic;
|
||||
},
|
||||
|
||||
_onValueChanged: function(value) {
|
||||
this.setState({
|
||||
topic: value,
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const EditableText = sdk.getComponent("elements.EditableText");
|
||||
|
||||
return (
|
||||
<EditableText
|
||||
className="mx_RoomHeader_topic mx_RoomHeader_editable"
|
||||
placeholderClassName="mx_RoomHeader_placeholder"
|
||||
placeholder={_t("Add a topic")}
|
||||
blurToCancel={false}
|
||||
initialValue={this.state.topic}
|
||||
onValueChanged={this._onValueChanged}
|
||||
dir="auto" />
|
||||
);
|
||||
},
|
||||
});
|
|
@ -19,9 +19,7 @@ import classNames from "classnames";
|
|||
import {
|
||||
RovingAccessibleButton,
|
||||
RovingAccessibleTooltipButton,
|
||||
RovingTabIndexWrapper
|
||||
} from "../../../accessibility/RovingTabIndex";
|
||||
import AccessibleButton from "../../views/elements/AccessibleButton";
|
||||
import NotificationBadge from "./NotificationBadge";
|
||||
import { NotificationState } from "../../../stores/notifications/NotificationState";
|
||||
|
||||
|
|
|
@ -89,6 +89,7 @@ export default class CrossSigningPanel extends React.PureComponent {
|
|||
const homeserverSupportsCrossSigning =
|
||||
await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing");
|
||||
const crossSigningReady = await cli.isCrossSigningReady();
|
||||
const secretStorageReady = await cli.isSecretStorageReady();
|
||||
|
||||
this.setState({
|
||||
crossSigningPublicKeysOnDevice,
|
||||
|
@ -101,6 +102,7 @@ export default class CrossSigningPanel extends React.PureComponent {
|
|||
secretStorageKeyInAccount,
|
||||
homeserverSupportsCrossSigning,
|
||||
crossSigningReady,
|
||||
secretStorageReady,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -151,6 +153,7 @@ export default class CrossSigningPanel extends React.PureComponent {
|
|||
secretStorageKeyInAccount,
|
||||
homeserverSupportsCrossSigning,
|
||||
crossSigningReady,
|
||||
secretStorageReady,
|
||||
} = this.state;
|
||||
|
||||
let errorSection;
|
||||
|
@ -166,14 +169,19 @@ export default class CrossSigningPanel extends React.PureComponent {
|
|||
summarisedStatus = <p>{_t(
|
||||
"Your homeserver does not support cross-signing.",
|
||||
)}</p>;
|
||||
} else if (crossSigningReady) {
|
||||
} else if (crossSigningReady && secretStorageReady) {
|
||||
summarisedStatus = <p>✅ {_t(
|
||||
"Cross-signing and secret storage are enabled.",
|
||||
"Cross-signing and secret storage are ready for use.",
|
||||
)}</p>;
|
||||
} else if (crossSigningReady && !secretStorageReady) {
|
||||
summarisedStatus = <p>✅ {_t(
|
||||
"Cross-signing is ready for use, but secret storage is " +
|
||||
"currently not being used to backup your keys.",
|
||||
)}</p>;
|
||||
} else if (crossSigningPrivateKeysInStorage) {
|
||||
summarisedStatus = <p>{_t(
|
||||
"Your account has a cross-signing identity in secret storage, but it " +
|
||||
"is not yet trusted by this session.",
|
||||
"Your account has a cross-signing identity in secret storage, " +
|
||||
"but it is not yet trusted by this session.",
|
||||
)}</p>;
|
||||
} else {
|
||||
summarisedStatus = <p>{_t(
|
||||
|
|
|
@ -21,6 +21,7 @@ import * as sdk from '../../../index';
|
|||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import Modal from '../../../Modal';
|
||||
import { isSecureBackupRequired } from '../../../utils/WellKnownUtils';
|
||||
|
||||
export default class KeyBackupPanel extends React.PureComponent {
|
||||
constructor(props) {
|
||||
|
@ -315,14 +316,19 @@ export default class KeyBackupPanel extends React.PureComponent {
|
|||
trustedLocally = _t("This backup is trusted because it has been restored on this session");
|
||||
}
|
||||
|
||||
let deleteBackupButton;
|
||||
if (!isSecureBackupRequired()) {
|
||||
deleteBackupButton = <AccessibleButton kind="danger" onClick={this._deleteBackup}>
|
||||
{_t("Delete Backup")}
|
||||
</AccessibleButton>;
|
||||
}
|
||||
|
||||
const buttonRow = (
|
||||
<div className="mx_KeyBackupPanel_buttonRow">
|
||||
<AccessibleButton kind="primary" onClick={this._restoreBackup}>
|
||||
{restoreButtonCaption}
|
||||
</AccessibleButton>
|
||||
<AccessibleButton kind="danger" onClick={this._deleteBackup}>
|
||||
{_t("Delete Backup")}
|
||||
</AccessibleButton>
|
||||
{deleteBackupButton}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ function getStatusText(status: UpdateCheckStatus, errorDetail?: string) {
|
|||
return _t('Downloading update...');
|
||||
case UpdateCheckStatus.Ready:
|
||||
return _t("New version available. <a>Update now.</a>", {}, {
|
||||
a: sub => <AccessibleButton kind="link" onClick={installUpdate}>{sub}</AccessibleButton>
|
||||
a: sub => <AccessibleButton kind="link" onClick={installUpdate}>{sub}</AccessibleButton>,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -170,7 +170,7 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
|
|||
"baseFontSize",
|
||||
null,
|
||||
SettingLevel.DEVICE,
|
||||
parseInt(value, 10) - FontWatcher.SIZE_DIFF
|
||||
parseInt(value, 10) - FontWatcher.SIZE_DIFF,
|
||||
);
|
||||
|
||||
return {valid: true, feedback: _t('Use between %(min)s pt and %(max)s pt', {min, max})};
|
||||
|
@ -294,7 +294,7 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
|
|||
/>
|
||||
</div>
|
||||
{customThemeForm}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,15 @@ interface IProps extends IGenericToastProps {
|
|||
|
||||
const SECOND = 1000;
|
||||
|
||||
const GenericExpiringToast: React.FC<IProps> = ({description, acceptLabel, dismissLabel, onAccept, onDismiss, toastKey, numSeconds}) => {
|
||||
const GenericExpiringToast: React.FC<IProps> = ({
|
||||
description,
|
||||
acceptLabel,
|
||||
dismissLabel,
|
||||
onAccept,
|
||||
onDismiss,
|
||||
toastKey,
|
||||
numSeconds,
|
||||
}) => {
|
||||
const onReject = () => {
|
||||
if (onDismiss) onDismiss();
|
||||
ToastStore.sharedInstance().dismissToast(toastKey);
|
||||
|
|
|
@ -31,7 +31,13 @@ interface IPropsExtended extends IProps {
|
|||
onReject();
|
||||
}
|
||||
|
||||
const GenericToast: React.FC<XOR<IPropsExtended, IProps>> = ({description, acceptLabel, rejectLabel, onAccept, onReject}) => {
|
||||
const GenericToast: React.FC<XOR<IPropsExtended, IProps>> = ({
|
||||
description,
|
||||
acceptLabel,
|
||||
rejectLabel,
|
||||
onAccept,
|
||||
onReject,
|
||||
}) => {
|
||||
return <div>
|
||||
<div className="mx_Toast_description">
|
||||
{ description }
|
||||
|
|
|
@ -97,10 +97,7 @@ export default class CallView extends React.Component<IProps, IState> {
|
|||
if (this.props.room) {
|
||||
const roomId = this.props.room.roomId;
|
||||
call = CallHandler.getCallForRoom(roomId) ||
|
||||
(this.props.ConferenceHandler ?
|
||||
this.props.ConferenceHandler.getConferenceCallForRoom(roomId) :
|
||||
null
|
||||
);
|
||||
(this.props.ConferenceHandler ? this.props.ConferenceHandler.getConferenceCallForRoom(roomId) : null);
|
||||
|
||||
if (this.call) {
|
||||
this.setState({ call: call });
|
||||
|
|
|
@ -51,7 +51,7 @@ export default class IncomingCallBox extends React.Component<IProps, IState> {
|
|||
|
||||
private onAction = (payload: ActionPayload) => {
|
||||
switch (payload.action) {
|
||||
case 'call_state':
|
||||
case 'call_state': {
|
||||
const call = CallHandler.getCall(payload.room_id);
|
||||
if (call && call.call_state === 'ringing') {
|
||||
this.setState({
|
||||
|
@ -62,6 +62,7 @@ export default class IncomingCallBox extends React.Component<IProps, IState> {
|
|||
incomingCall: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -26,8 +26,8 @@ import dis from "./dispatcher/dispatcher";
|
|||
import * as Rooms from "./Rooms";
|
||||
import DMRoomMap from "./utils/DMRoomMap";
|
||||
import {getAddressType} from "./UserAddress";
|
||||
|
||||
const E2EE_WK_KEY = "im.vector.riot.e2ee";
|
||||
import { getE2EEWellKnown } from "./utils/WellKnownUtils";
|
||||
import GroupStore from "./stores/GroupStore";
|
||||
|
||||
// we define a number of interfaces which take their names from the js-sdk
|
||||
/* eslint-disable camelcase */
|
||||
|
@ -80,6 +80,7 @@ interface IOpts {
|
|||
encryption?: boolean;
|
||||
inlineErrors?: boolean;
|
||||
andView?: boolean;
|
||||
associatedWithCommunity?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -182,6 +183,10 @@ export default function createRoom(opts: IOpts): Promise<string | null> {
|
|||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}).then(() => {
|
||||
if (opts.associatedWithCommunity) {
|
||||
return GroupStore.addRoomToGroup(opts.associatedWithCommunity, roomId, false);
|
||||
}
|
||||
}).then(function() {
|
||||
// NB createRoom doesn't block on the client seeing the echo that the
|
||||
// room has been created, so we race here with the client knowing that
|
||||
|
@ -294,12 +299,11 @@ export async function ensureDMExists(client: MatrixClient, userId: string): Prom
|
|||
return roomId;
|
||||
}
|
||||
|
||||
export function privateShouldBeEncrypted() {
|
||||
const clientWellKnown = MatrixClientPeg.get().getClientWellKnown();
|
||||
if (clientWellKnown && clientWellKnown[E2EE_WK_KEY]) {
|
||||
const defaultDisabled = clientWellKnown[E2EE_WK_KEY]["default"] === false;
|
||||
export function privateShouldBeEncrypted(): boolean {
|
||||
const e2eeWellKnown = getE2EEWellKnown();
|
||||
if (e2eeWellKnown) {
|
||||
const defaultDisabled = e2eeWellKnown["default"] === false;
|
||||
return !defaultDisabled;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -2164,5 +2164,54 @@
|
|||
"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.": "Sem přídejte servery a uživatele, které chcete ignorovat. Hvězdička pro %(brand)s zastupuje libovolný počet kterýchkoliv znaků. Např. <code>@bot:*</code> bude ignorovat všechny uživatele se jménem „bot“ na kterémkoliv serveru.",
|
||||
"Signature upload success": "Podpis úspěšně nahrán",
|
||||
"Signature upload failed": "Podpis se nepodařilo nahrát",
|
||||
"If the other version of %(brand)s is still open in another tab, please close it as using %(brand)s on the same host with both lazy loading enabled and disabled simultaneously will cause issues.": "Je-li jiná verze programu %(brand)s stále otevřená na jiné kartě, tak ji prosím zavřete, neboť užívání programu %(brand)s stejným hostitelem se zpožděným nahráváním současně povoleným i zakázaným bude působit problémy."
|
||||
"If the other version of %(brand)s is still open in another tab, please close it as using %(brand)s on the same host with both lazy loading enabled and disabled simultaneously will cause issues.": "Je-li jiná verze programu %(brand)s stále otevřená na jiné kartě, tak ji prosím zavřete, neboť užívání programu %(brand)s stejným hostitelem se zpožděným nahráváním současně povoleným i zakázaným bude působit problémy.",
|
||||
"Unexpected server error trying to leave the room": "Neočekávaná chyba serveru při odcházení z místnosti",
|
||||
"The person who invited you already left the room.": "Uživatel který vás pozval už místnosti není.",
|
||||
"The person who invited you already left the room, or their server is offline.": "Uživatel který vás pozvat už odešel z místnosti a nebo je jeho server offline.",
|
||||
"You left the call": "Odešli jste z hovoru",
|
||||
"%(senderName)s left the call": "%(senderName)s opustil/a hovor",
|
||||
"Call ended": "Hovor skončil",
|
||||
"You started a call": "Začali jste hovor",
|
||||
"%(senderName)s started a call": "%(senderName)s začal/a hovor",
|
||||
"Waiting for answer": "Čekání na odpověď",
|
||||
"%(senderName)s is calling": "%(senderName)s volá",
|
||||
"* %(senderName)s %(emote)s": "* %(senderName)s %(emote)s",
|
||||
"%(senderName)s: %(message)s": "%(senderName)s: %(message)s",
|
||||
"%(senderName)s: %(reaction)s": "%(senderName)s: %(reaction)s",
|
||||
"%(senderName)s: %(stickerName)s": "%(senderName)s: %(stickerName)s",
|
||||
"Change notification settings": "Upravit nastavení oznámení",
|
||||
"Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Prototyp komunit verze 2. Vyžaduje kompatibilní domovský server. Experimentální - používejte opatrně.",
|
||||
"Use custom size": "Použít vlastní velikost",
|
||||
"Use a more compact ‘Modern’ layout": "Používat kompaktní ‘Moderní’ vzhled",
|
||||
"Use a system font": "Používat systémové nastavení písma",
|
||||
"System font name": "Jméno systémového písma",
|
||||
"Uploading logs": "Nahrávání záznamů",
|
||||
"Downloading logs": "Stahování záznamů",
|
||||
"Unknown caller": "Neznámý volající",
|
||||
"Incoming voice call": "Příchozí hovor",
|
||||
"Incoming video call": "Příchozí videohovor",
|
||||
"Incoming call": "Příchozí hovor",
|
||||
"Your server isn't responding to some <a>requests</a>.": "Váš server neodpovídá na některé <a>požadavky</a>.",
|
||||
"Master private key:": "Hlavní soukromý klíč:",
|
||||
"%(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ůže v prohlížeči lokálně bezpečně uložit zprávy. Použijte <desktopLink>%(brand)s Desktop</desktopLink> aby fungovalo vyhledávání v šifrovaných zprávách.",
|
||||
"Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "Váš administrátor vypnul šifrování ve výchozím nastavení soukromých místností a přímých chatů.",
|
||||
"To link to this room, please add an address.": "Přidejte prosím místnosti adresu aby na ní šlo odkazovat.",
|
||||
"The authenticity of this encrypted message can't be guaranteed on this device.": "Pravost této šifrované zprávy nelze na tomto zařízení ověřit.",
|
||||
"Emoji picker": "Výběr emoji",
|
||||
"No recently visited rooms": "Žádné nedávno navštívené místnosti",
|
||||
"People": "Lidé",
|
||||
"Explore public rooms": "Prozkoumat veřejné místnosti",
|
||||
"Custom Tag": "Vlastní štítek",
|
||||
"Can't see what you’re looking for?": "Nikde nevidíte co hledáte?",
|
||||
"Explore all public rooms": "Prozkoumat všechny veřejné místnosti",
|
||||
"%(count)s results|other": "%(count)s výsledků",
|
||||
"Preparing to download logs": "Příprava na stažení záznamů",
|
||||
"Download logs": "Stáhnout záznamy",
|
||||
"a new cross-signing key signature": "nový podpis klíče pro cross-signing",
|
||||
"a key signature": "podpis klíče",
|
||||
"%(brand)s encountered an error during upload of:": "%(brand)s narazil na chybu při nahrávání:",
|
||||
"Upload completed": "Nahrávání dokončeno",
|
||||
"Cancelled signature upload": "Nahrávání podpisu zrušeno",
|
||||
"Unable to upload": "Nelze nahrát",
|
||||
"Server isn't responding": "Server neodpovídá"
|
||||
}
|
||||
|
|
|
@ -572,7 +572,7 @@
|
|||
"was unbanned %(count)s times|one": "wurde entbannt",
|
||||
"%(oneUser)schanged their name %(count)s times|one": "%(oneUser)shat den Namen geändert",
|
||||
"%(items)s and %(count)s others|other": "%(items)s und %(count)s andere",
|
||||
"%(items)s and %(count)s others|one": "%(items)s und noch jemand",
|
||||
"%(items)s and %(count)s others|one": "%(items)s und ein weiteres Raummitglied",
|
||||
"An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.": "Eine E-Mail wurde an %(emailAddress)s gesendet. Folge dem in der E-Mail enthaltenen Link und klicke dann unten.",
|
||||
"The visibility of '%(roomName)s' in %(groupId)s could not be updated.": "Die Sichtbarkeit von '%(roomName)s' in %(groupId)s konnte nicht aktualisiert werden.",
|
||||
"Visibility in Room List": "Sichtbarkeit in Raum-Liste",
|
||||
|
@ -784,7 +784,7 @@
|
|||
"Checking for an update...": "Nach Updates suchen...",
|
||||
"Missing roomId.": "Fehlende Raum-ID.",
|
||||
"Every page you use in the app": "Jede Seite, die du in der App benutzt",
|
||||
"e.g. <CurrentPageURL>": "z.B. <CurrentPageURL>",
|
||||
"e.g. <CurrentPageURL>": "z. B. <CurrentPageURL>",
|
||||
"Your device resolution": "Deine Bildschirmauflösung",
|
||||
"Popout widget": "Widget ausklinken",
|
||||
"Always show encryption icons": "Immer Verschlüsselungssymbole zeigen",
|
||||
|
@ -1034,7 +1034,7 @@
|
|||
"%(senderDisplayName)s made the room invite only.": "%(senderDisplayName)s hat den Raum auf eingeladene User beschränkt.",
|
||||
"%(senderDisplayName)s changed the join rule to %(rule)s": "%(senderDisplayName)s änderte die Zutrittsregel auf '%(rule)s'",
|
||||
"%(senderDisplayName)s has allowed guests to join the room.": "%(senderDisplayName)s erlaubte Gäste diesem Raum beizutreten.",
|
||||
"%(senderDisplayName)s has prevented guests from joining the room.": "%(senderDisplayName)s hat verboten, dass Gäste diesem Raum beitreten.",
|
||||
"%(senderDisplayName)s has prevented guests from joining the room.": "%(senderDisplayName)s hat Gästen verboten diesem Raum beizutreten.",
|
||||
"%(senderDisplayName)s changed guest access to %(rule)s": "%(senderDisplayName)s änderte den Gastzugriff auf '%(rule)s'",
|
||||
"Group & filter rooms by custom tags (refresh to apply changes)": "Gruppiere & filtere Räume nach eigenen Tags (neu laden um Änderungen zu übernehmen)",
|
||||
"Unable to find a supported verification method.": "Konnte kein unterstützte Verifikationsmethode finden.",
|
||||
|
@ -1328,7 +1328,7 @@
|
|||
"Find a room…": "Suche einen Raum…",
|
||||
"Find a room… (e.g. %(exampleRoom)s)": "Suche einen Raum… (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 teilen. Du kannst dies auch in den Einstellungen konfigurieren.",
|
||||
"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.",
|
||||
"Only continue if you trust the owner of the server.": "Fahre nur fort, wenn du den Inhaber*innen des Servers vertraust.",
|
||||
"Trust": "Vertrauen",
|
||||
|
@ -1337,7 +1337,7 @@
|
|||
"Use an identity server to invite by email. Manage in Settings.": "Nutze einen Identitätsserver, um über E-Mail Einladungen zu verschicken. Verwalte es in den Einstellungen.",
|
||||
"%(name)s (%(userId)s)": "%(name)s (%(userId)s)",
|
||||
"Try out new ways to ignore people (experimental)": "Versuche neue Möglichkeiten, um Menschen zu ignorieren (experimentell)",
|
||||
"Send read receipts for messages (requires compatible homeserver to disable)": "Schicke Lesebestätigungen für Nachrichten (erfordert kompatiblen Heimserver zum Deaktivieren)",
|
||||
"Send read receipts for messages (requires compatible homeserver to disable)": "Lesebestätigungen für Nachrichten senden (Deaktivieren erfordert einen kompatiblen Heimserver)",
|
||||
"My Ban List": "Meine Bannliste",
|
||||
"This is your list of users/servers you have blocked - don't leave the room!": "Dies ist die Liste von Benutzer*innen/Servern, die du blockiert hast - verlasse den Raum nicht!",
|
||||
"Accept <policyLink /> to continue:": "Akzeptiere <policyLink />, um fortzufahren:",
|
||||
|
@ -1587,8 +1587,8 @@
|
|||
"Reject & Ignore user": "Ablehnen & Nutzer ignorieren",
|
||||
"%(senderName)s changed a rule that was banning users matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s ändert eine Ausschluss-Regel von %(oldGlob)s nach %(newGlob)s, wegen %(reason)s",
|
||||
"%(senderName)s changed a rule that was banning rooms matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s ändert eine Ausschluss-Regel für Räume von %(oldGlob)s nach %(newGlob)s, wegen %(reason)s",
|
||||
"Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)": "Erlaube den Standard-Server zur Anrufunterstützung (turn.matrix.org) zu verwenden wenn dein Heimserver keinen eigenen anbietet (deine IP Adresse wird bei dem Anruf übermittelt)",
|
||||
"Show more": "mehr",
|
||||
"Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)": "Auf den Server turn.matrix.org zurückgreifen, falls deine Heimserver keine Anruf-Assistenz anbietet (deine IP-Adresse wird während eines Anrufs geteilt)",
|
||||
"Show more": "Mehr zeigen",
|
||||
"This session is <b>not backing up your keys</b>, but you do have an existing backup you can restore from and add to going forward.": "Diese Sitzung <b>speichert deine Schlüssel nicht</b>, du kannst sie aber an die Schlüsselsicherung anschließen.",
|
||||
"Connect this session to key backup before signing out to avoid losing any keys that may only be on this session.": "Verbinde diese Sitzung mit deiner Schlüsselsicherung bevor du dich abmeldest, um den Verlust von Schlüsseln zu vermeiden.",
|
||||
"This backup is trusted because it has been restored on this session": "Dieser Sicherung wird vertraut, da sie während dieser Sitzung wiederhergestellt wurde",
|
||||
|
@ -1632,12 +1632,12 @@
|
|||
"Make a copy of your recovery key": "Speichere deinen Wiederherstellungsschlüssel",
|
||||
"Sends a message as html, without interpreting it as markdown": "Verschickt eine Nachricht im html-Format, ohne sie in Markdown zu formatieren",
|
||||
"Show rooms with unread notifications first": "Räume mit nicht gelesenen Benachrichtungen zuerst zeigen",
|
||||
"Show shortcuts to recently viewed rooms above the room list": "Kurzbefehlezu den kürzlich gesichteteten Räumen über der Raumliste anzeigen",
|
||||
"Use Single Sign On to continue": "Nutze „Single Sign-On“ (Einmal-Anmeldung) um fortzufahren",
|
||||
"Confirm adding this email address by using Single Sign On to prove your identity.": "Bestätige das Hinzufügen dieser E-Mail-Adresse mit „Single Sign-On“, um deine Identität nachzuweisen.",
|
||||
"Show shortcuts to recently viewed rooms above the room list": "Kurzbefehle zu den kürzlich gesichteteten Räumen über der Raumliste anzeigen",
|
||||
"Use Single Sign On to continue": "Verwende Single Sign on um fortzufahren",
|
||||
"Confirm adding this email address by using Single Sign On to prove your identity.": "Bestätige die hinzugefügte E-Mail-Adresse mit Single Sign-On, um deine Identität nachzuweisen.",
|
||||
"Single Sign On": "Single Sign-On",
|
||||
"Confirm adding email": "Hinzufügen der E-Mail-Adresse bestätigen",
|
||||
"Confirm adding this phone number by using Single Sign On to prove your identity.": "Bestätige das Hinzufügen dieser Telefonnummer, indem du deine Identität mittels „Single Sign-On“ nachweist.",
|
||||
"Confirm adding email": "Bestätige hinzugefügte E-Mail-Addresse",
|
||||
"Confirm adding this phone number by using Single Sign On to prove your identity.": "Bestätige die hinzugefügte Telefonnummer, indem du deine Identität mittels Single Sign-On nachweist.",
|
||||
"Click the button below to confirm adding this phone number.": "Klicke unten die Schaltfläche, um die hinzugefügte Telefonnummer zu bestätigen.",
|
||||
"If you cancel now, you won't complete your operation.": "Wenn du jetzt abbrichst, wirst du deinen Vorgang nicht fertigstellen.",
|
||||
"%(name)s is requesting verification": "%(name)s fordert eine Verifizierung an",
|
||||
|
@ -1645,14 +1645,14 @@
|
|||
"Command failed": "Befehl fehlgeschlagen",
|
||||
"Could not find user in room": "Der Benutzer konnte im Raum nicht gefunden werden",
|
||||
"Click the button below to confirm adding this email address.": "Klicke unten auf die Schaltfläche, um die hinzugefügte E-Mail-Adresse zu bestätigen.",
|
||||
"Confirm adding phone number": "Hinzufügen der Telefonnummer bestätigen",
|
||||
"Confirm adding phone number": "Bestätige hinzugefügte Telefonnummer",
|
||||
"%(senderName)s changed a rule that was banning servers matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s ändert eine Ausschluss-Regel für Server von %(oldGlob)s nach %(newGlob)s wegen %(reason)s",
|
||||
"%(senderName)s updated a ban rule that was matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s erneuert eine Ausschluss-Regel von %(oldGlob)s nach %(newGlob)s wegen %(reason)s",
|
||||
"Not Trusted": "Nicht vertraut",
|
||||
"Manually Verify by Text": "Verifiziere manuell mit einem Text",
|
||||
"Interactively verify by Emoji": "Verifiziere interaktiv mit Emojis",
|
||||
"Support adding custom themes": "Unterstütze das Hinzufügen von benutzerdefinierten Designs",
|
||||
"Ask this user to verify their session, or manually verify it below.": "Bitte diese/n Nutzer!n, seine/ihre Sitzung zu verifizieren, oder verifiziere diese unten manuell.",
|
||||
"Ask this user to verify their session, or manually verify it below.": "Bitte diesen Nutzer, seine Sitzung zu verifizieren, oder verifiziere diesen unten manuell.",
|
||||
"a few seconds from now": "in ein paar Sekunden",
|
||||
"Manually verify all remote sessions": "Verifiziere alle Remotesitzungen",
|
||||
"Confirm the emoji below are displayed on both sessions, in the same order:": "Bestätige, dass die unten angezeigten Emojis auf beiden Sitzungen in der selben Reihenfolge angezeigt werden:",
|
||||
|
@ -1724,7 +1724,7 @@
|
|||
"Upgrade this room to the recommended room version": "Aktualisiere diesen Raum auf die empfohlene Raumversion",
|
||||
"this room": "Dieser Raum",
|
||||
"View older messages in %(roomName)s.": "Zeige alte Nachrichten in %(roomName)s.",
|
||||
"Send a bug report with logs": "Sende Fehlermeldung mit Protokoll",
|
||||
"Send a bug report with logs": "Sende einen Fehlerbericht mit Logs",
|
||||
"Verify all your sessions to ensure your account & messages are safe": "Verifiziere alle deine Sitzungen, um dein Konto und deine Nachrichten zu schützen",
|
||||
"Verify your other session using one of the options below.": "Verifiziere deine andere Sitzung mit einer der folgenden Optionen.",
|
||||
"You signed in to a new session without verifying it:": "Du hast dich in einer neuen Sitzung angemeldet ohne sie zu verifizieren:",
|
||||
|
@ -1732,16 +1732,16 @@
|
|||
"Upgrade": "Hochstufen",
|
||||
"Verify the new login accessing your account: %(name)s": "Verifiziere die neue Anmeldung an deinem Konto: %(name)s",
|
||||
"From %(deviceName)s (%(deviceId)s)": "Von %(deviceName)s (%(deviceId)s)",
|
||||
"Your homeserver does not support cross-signing.": "Dein Heimserver unterstützt cross-signing nicht.",
|
||||
"Your homeserver does not support cross-signing.": "Dein Heimserver unterstützt Cross-Signing nicht.",
|
||||
"Cross-signing and secret storage are enabled.": "Cross-signing und der sichere Speicher wurden eingerichtet.",
|
||||
"Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.": "Dein Konto hat eine cross-signing Identität im sicheren Speicher aber diese Sitzung wird noch nicht vertraut.",
|
||||
"Cross-signing and secret storage are not yet set up.": "Cross-signing und der sichere Speicher wurden noch nicht eingerichtet.",
|
||||
"Reset cross-signing and secret storage": "Setze cross-signing und den sicheren Speicher zurück",
|
||||
"Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.": "Dein Konto hat eine Cross-Signing Identität im sicheren Speicher, aber dieser wird von dieser Sitzung noch nicht vertraut.",
|
||||
"Cross-signing and secret storage are not yet set up.": "Cross-Signing und der sichere Speicher sind noch nicht eingerichtet.",
|
||||
"Reset cross-signing and secret storage": "Setze Cross-Signing und den sicheren Speicher zurück",
|
||||
"Bootstrap cross-signing and secret storage": "Richte cross-signing und den sicheren Speicher ein",
|
||||
"unexpected type": "unbekannter Typ",
|
||||
"Cross-signing public keys:": "Öffentliche Cross-signing Schlüssel:",
|
||||
"Cross-signing public keys:": "Öffentliche Cross-Signing-Schlüssel:",
|
||||
"in memory": "im Speicher",
|
||||
"Cross-signing private keys:": "Private Cross-signing Schlüssel:",
|
||||
"Cross-signing private keys:": "Private Cross-Signing-Schlüssel:",
|
||||
"in secret storage": "im sicheren Speicher",
|
||||
"Self signing private key:": "Selbst signierter privater Schlüssel:",
|
||||
"cached locally": "lokal zwischengespeichert",
|
||||
|
@ -1754,7 +1754,7 @@
|
|||
"exists": "existiert",
|
||||
"Delete sessions|other": "Lösche Sitzungen",
|
||||
"Delete sessions|one": "Lösche Sitzung",
|
||||
"Individually verify each session used by a user to mark it as trusted, not trusting cross-signed devices.": "Sitzungen eines Benutzers einzeln verifizieren. Geräten, die ein Benutzer als vertrauenswürdig markiert hat, wird nicht automatisch vertraut (cross-signing).",
|
||||
"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 aus ",
|
||||
"%(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>.",
|
||||
|
@ -2408,5 +2408,20 @@
|
|||
"Store your Security Key somewhere safe, like a password manager or a safe, as it’s used to safeguard your encrypted data.": "Bewahre deinen Sicherheitsschlüssel an einem sicheren Ort wie z. B. in einem Passwort-Manager oder einem Safe auf. Er wird zum Schutz deiner verschlüsselten Daten verwendet.",
|
||||
"If you cancel now, you may lose encrypted messages & data if you lose access to your logins.": "Wenn du jetzt abbrichst, kannst du verschlüsselte Nachrichten und Daten verlieren, wenn du den Zugriff auf deine Logins verlierst.",
|
||||
"You can also set up Secure Backup & manage your keys in Settings.": "Du kannst auch in den Einstellungen eine Sicherung erstellen & deine Schlüssel verwalten.",
|
||||
"Set up Secure backup": "Sicheres Backup einrichten"
|
||||
"Set up Secure backup": "Sicheres Backup einrichten",
|
||||
"Show message previews for reactions in DMs": "Anzeigen einer Nachrichtenvorschau für Reaktionen in DMs",
|
||||
"Show message previews for reactions in all rooms": "Zeigen Sie 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",
|
||||
"Can't see what you’re looking for?": "Kannst du nicht finden wonach du suchst?",
|
||||
"Explore all public rooms": "Erkunde alle öffentlichen Räume",
|
||||
"%(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",
|
||||
"Set up Secure Backup": "Sicherung einrichten"
|
||||
}
|
||||
|
|
|
@ -444,6 +444,7 @@
|
|||
"%(senderName)s: %(reaction)s": "%(senderName)s: %(reaction)s",
|
||||
"%(senderName)s: %(stickerName)s": "%(senderName)s: %(stickerName)s",
|
||||
"Change notification settings": "Change notification settings",
|
||||
"Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.",
|
||||
"New spinner design": "New spinner design",
|
||||
"Message Pinning": "Message Pinning",
|
||||
"Custom user status messages": "Custom user status messages",
|
||||
|
@ -644,7 +645,8 @@
|
|||
"Confirm password": "Confirm password",
|
||||
"Change Password": "Change Password",
|
||||
"Your homeserver does not support cross-signing.": "Your homeserver does not support cross-signing.",
|
||||
"Cross-signing and secret storage are enabled.": "Cross-signing and secret storage are enabled.",
|
||||
"Cross-signing and secret storage are ready for use.": "Cross-signing and secret storage are ready for use.",
|
||||
"Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.": "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.",
|
||||
"Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.": "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.",
|
||||
"Cross-signing and secret storage are not yet set up.": "Cross-signing and secret storage are not yet set up.",
|
||||
"Reset cross-signing and secret storage": "Reset cross-signing and secret storage",
|
||||
|
@ -1120,6 +1122,7 @@
|
|||
"Rooms": "Rooms",
|
||||
"Add room": "Add room",
|
||||
"Create new room": "Create new room",
|
||||
"Explore community rooms": "Explore community rooms",
|
||||
"Explore public rooms": "Explore public rooms",
|
||||
"Low priority": "Low priority",
|
||||
"System Alerts": "System Alerts",
|
||||
|
@ -1197,7 +1200,6 @@
|
|||
"%(count)s unread messages.|other": "%(count)s unread messages.",
|
||||
"%(count)s unread messages.|one": "1 unread message.",
|
||||
"Unread messages.": "Unread messages.",
|
||||
"Add a topic": "Add a topic",
|
||||
"Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.",
|
||||
"This room has already been upgraded.": "This room has already been upgraded.",
|
||||
"This room is running room version <roomVersion />, which this homeserver has marked as <i>unstable</i>.": "This room is running room version <roomVersion />, which this homeserver has marked as <i>unstable</i>.",
|
||||
|
@ -1483,6 +1485,7 @@
|
|||
"Rotate Right": "Rotate Right",
|
||||
"Rotate clockwise": "Rotate clockwise",
|
||||
"Download this file": "Download this file",
|
||||
"Information": "Information",
|
||||
"Language Dropdown": "Language Dropdown",
|
||||
"Manage Integrations": "Manage Integrations",
|
||||
"%(nameList)s %(transitionList)s": "%(nameList)s %(transitionList)s",
|
||||
|
@ -1552,8 +1555,7 @@
|
|||
"Room directory": "Room directory",
|
||||
"Sign in with single sign-on": "Sign in with single sign-on",
|
||||
"And %(count)s more...|other": "And %(count)s more...",
|
||||
"ex. @bob:example.com": "ex. @bob:example.com",
|
||||
"Add User": "Add User",
|
||||
"Home": "Home",
|
||||
"Enter a server name": "Enter a server name",
|
||||
"Looks good": "Looks good",
|
||||
"Can't find this server or its room list": "Can't find this server or its room list",
|
||||
|
@ -1596,6 +1598,15 @@
|
|||
"Unable to load commit detail: %(msg)s": "Unable to load commit detail: %(msg)s",
|
||||
"Unavailable": "Unavailable",
|
||||
"Changelog": "Changelog",
|
||||
"Email address": "Email address",
|
||||
"Add another email": "Add another email",
|
||||
"People you know on %(brand)s": "People you know on %(brand)s",
|
||||
"Hide": "Hide",
|
||||
"Show": "Show",
|
||||
"Skip": "Skip",
|
||||
"Send %(count)s invites|other": "Send %(count)s invites",
|
||||
"Send %(count)s invites|one": "Send %(count)s invite",
|
||||
"Invite people to join %(communityName)s": "Invite people to join %(communityName)s",
|
||||
"You cannot delete this message. (%(code)s)": "You cannot delete this message. (%(code)s)",
|
||||
"Removing…": "Removing…",
|
||||
"Destroy cross-signing keys?": "Destroy cross-signing keys?",
|
||||
|
@ -1606,6 +1617,15 @@
|
|||
"Clear all data in this session?": "Clear all data in this session?",
|
||||
"Clearing all data from this session is permanent. Encrypted messages will be lost unless their keys have been backed up.": "Clearing all data from this session is permanent. Encrypted messages will be lost unless their keys have been backed up.",
|
||||
"Clear all data": "Clear all data",
|
||||
"There was an error creating your community. The name may be taken or the server is unable to process your request.": "There was an error creating your community. The name may be taken or the server is unable to process your request.",
|
||||
"Community ID: +<localpart />:%(domain)s": "Community ID: +<localpart />:%(domain)s",
|
||||
"Use this when referencing your community to others. The community ID cannot be changed.": "Use this when referencing your community to others. The community ID cannot be changed.",
|
||||
"You can change this later if needed.": "You can change this later if needed.",
|
||||
"What's the name of your community or team?": "What's the name of your community or team?",
|
||||
"Enter name": "Enter name",
|
||||
"Create": "Create",
|
||||
"Add image (optional)": "Add image (optional)",
|
||||
"An image will help people identify your community.": "An image will help people identify your community.",
|
||||
"Community IDs cannot be empty.": "Community IDs cannot be empty.",
|
||||
"Community IDs may only contain characters a-z, 0-9, or '=_-./'": "Community IDs may only contain characters a-z, 0-9, or '=_-./'",
|
||||
"Something went wrong whilst creating your community": "Something went wrong whilst creating your community",
|
||||
|
@ -1614,20 +1634,22 @@
|
|||
"Example": "Example",
|
||||
"Community ID": "Community ID",
|
||||
"example": "example",
|
||||
"Create": "Create",
|
||||
"Please enter a name for the room": "Please enter a name for the room",
|
||||
"Set a room address to easily share your room with other people.": "Set a room address to easily share your room with other people.",
|
||||
"This room is private, and can only be joined by invitation.": "This room is private, and can only be joined by invitation.",
|
||||
"Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.": "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.",
|
||||
"Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.",
|
||||
"You can’t disable this later. Bridges & most bots won’t work yet.": "You can’t disable this later. Bridges & most bots won’t work yet.",
|
||||
"Enable end-to-end encryption": "Enable end-to-end encryption",
|
||||
"You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.",
|
||||
"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.": "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.",
|
||||
"Create a public room": "Create a public room",
|
||||
"Create a private room": "Create a private room",
|
||||
"Create a room in %(communityName)s": "Create a room in %(communityName)s",
|
||||
"Name": "Name",
|
||||
"Topic (optional)": "Topic (optional)",
|
||||
"Make this room public": "Make this room public",
|
||||
"Hide advanced": "Hide advanced",
|
||||
"Show advanced": "Show advanced",
|
||||
"Block users on other matrix homeservers from joining this room (This setting cannot be changed later!)": "Block users on other matrix homeservers from joining this room (This setting cannot be changed later!)",
|
||||
"Block anyone not part of %(serverName)s from ever joining this room.": "Block anyone not part of %(serverName)s from ever joining this room.",
|
||||
"Create Room": "Create Room",
|
||||
"Sign out": "Sign out",
|
||||
"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": "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",
|
||||
|
@ -1773,9 +1795,7 @@
|
|||
"Clearing your browser's storage may fix the problem, but will sign you out and cause any encrypted chat history to become unreadable.": "Clearing your browser's storage may fix the problem, but will sign you out and cause any encrypted chat history to become unreadable.",
|
||||
"Verification Pending": "Verification Pending",
|
||||
"Please check your email and click on the link it contains. Once this is done, click continue.": "Please check your email and click on the link it contains. Once this is done, click continue.",
|
||||
"Email address": "Email address",
|
||||
"This will allow you to reset your password and receive notifications.": "This will allow you to reset your password and receive notifications.",
|
||||
"Skip": "Skip",
|
||||
"A username can only contain lower case letters, numbers and '=_-./'": "A username can only contain lower case letters, numbers and '=_-./'",
|
||||
"Username not available": "Username not available",
|
||||
"Username invalid: %(errMessage)s": "Username invalid: %(errMessage)s",
|
||||
|
@ -1863,10 +1883,6 @@
|
|||
"<b>Warning</b>: You should only set up key backup from a trusted computer.": "<b>Warning</b>: You should only set up key backup from a trusted computer.",
|
||||
"Access your secure message history and set up secure messaging by entering your recovery key.": "Access your secure message history and set up secure messaging by entering your recovery key.",
|
||||
"If you've forgotten your recovery key you can <button>set up new recovery options</button>": "If you've forgotten your recovery key you can <button>set up new recovery options</button>",
|
||||
"Private Chat": "Private Chat",
|
||||
"Public Chat": "Public Chat",
|
||||
"Custom": "Custom",
|
||||
"Address (optional)": "Address (optional)",
|
||||
"Reject invitation": "Reject invitation",
|
||||
"Are you sure you want to reject the invitation?": "Are you sure you want to reject the invitation?",
|
||||
"Unable to reject invite": "Unable to reject invite",
|
||||
|
@ -1888,7 +1904,6 @@
|
|||
"Set status": "Set status",
|
||||
"Set a new status...": "Set a new status...",
|
||||
"View Community": "View Community",
|
||||
"Hide": "Hide",
|
||||
"Reload": "Reload",
|
||||
"Take picture": "Take picture",
|
||||
"Remove for everyone": "Remove for everyone",
|
||||
|
@ -1961,11 +1976,6 @@
|
|||
"Sign in to your Matrix account on %(serverName)s": "Sign in to your Matrix account on %(serverName)s",
|
||||
"Sign in to your Matrix account on <underlinedServerName />": "Sign in to your Matrix account on <underlinedServerName />",
|
||||
"Sign in with SSO": "Sign in with SSO",
|
||||
"Sorry, your browser is <b>not</b> able to run %(brand)s.": "Sorry, your browser is <b>not</b> able to run %(brand)s.",
|
||||
"%(brand)s uses many advanced browser features, some of which are not available or experimental in your current browser.": "%(brand)s uses many advanced browser features, some of which are not available or experimental in your current browser.",
|
||||
"Please install <chromeLink>Chrome</chromeLink>, <firefoxLink>Firefox</firefoxLink>, or <safariLink>Safari</safariLink> for the best experience.": "Please install <chromeLink>Chrome</chromeLink>, <firefoxLink>Firefox</firefoxLink>, or <safariLink>Safari</safariLink> for the best experience.",
|
||||
"With your current browser, the look and feel of the application may be completely incorrect, and some or all features may not function. If you want to try it anyway you can continue, but you are on your own in terms of any issues you may encounter!": "With your current browser, the look and feel of the application may be completely incorrect, and some or all features may not function. If you want to try it anyway you can continue, but you are on your own in terms of any issues you may encounter!",
|
||||
"I understand the risks and wish to continue": "I understand the risks and wish to continue",
|
||||
"Couldn't load page": "Couldn't load page",
|
||||
"You must <a>register</a> to use this functionality": "You must <a>register</a> to use this functionality",
|
||||
"You must join the room to see its files": "You must join the room to see its files",
|
||||
|
@ -2063,6 +2073,7 @@
|
|||
"Find a room…": "Find a room…",
|
||||
"Find a room… (e.g. %(exampleRoom)s)": "Find a room… (e.g. %(exampleRoom)s)",
|
||||
"If you can't find the room you're looking for, ask for an invite or <a>Create a new room</a>.": "If you can't find the room you're looking for, ask for an invite or <a>Create a new room</a>.",
|
||||
"Explore rooms in %(communityName)s": "Explore rooms in %(communityName)s",
|
||||
"Clear filter": "Clear filter",
|
||||
"Search rooms": "Search rooms",
|
||||
"You can't send any messages until you review and agree to <consentLink>our terms and conditions</consentLink>.": "You can't send any messages until you review and agree to <consentLink>our terms and conditions</consentLink>.",
|
||||
|
@ -2089,13 +2100,13 @@
|
|||
"Click to mute video": "Click to mute video",
|
||||
"Click to unmute audio": "Click to unmute audio",
|
||||
"Click to mute audio": "Click to mute audio",
|
||||
"Create community": "Create community",
|
||||
"Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.",
|
||||
"Tried to load a specific point in this room's timeline, but was unable to find it.": "Tried to load a specific point in this room's timeline, but was unable to find it.",
|
||||
"Failed to load timeline position": "Failed to load timeline position",
|
||||
"Uploading %(filename)s and %(count)s others|other": "Uploading %(filename)s and %(count)s others",
|
||||
"Uploading %(filename)s and %(count)s others|zero": "Uploading %(filename)s",
|
||||
"Uploading %(filename)s and %(count)s others|one": "Uploading %(filename)s and %(count)s other",
|
||||
"Home": "Home",
|
||||
"Switch to light mode": "Switch to light mode",
|
||||
"Switch to dark mode": "Switch to dark mode",
|
||||
"Switch theme": "Switch theme",
|
||||
|
@ -2236,7 +2247,7 @@
|
|||
"Retry": "Retry",
|
||||
"If you cancel now, you may lose encrypted messages & data if you lose access to your logins.": "If you cancel now, you may lose encrypted messages & data if you lose access to your logins.",
|
||||
"You can also set up Secure Backup & manage your keys in Settings.": "You can also set up Secure Backup & manage your keys in Settings.",
|
||||
"Set up Secure backup": "Set up Secure backup",
|
||||
"Set up Secure Backup": "Set up Secure Backup",
|
||||
"Upgrade your encryption": "Upgrade your encryption",
|
||||
"Set a Security Phrase": "Set a Security Phrase",
|
||||
"Confirm Security Phrase": "Confirm Security Phrase",
|
||||
|
|
|
@ -858,7 +858,7 @@
|
|||
"Gift": "Donaco",
|
||||
"Light bulb": "Lampo",
|
||||
"Book": "Libro",
|
||||
"Pencil": "Grifelo",
|
||||
"Pencil": "Krajono",
|
||||
"Scissors": "Tondilo",
|
||||
"Key": "Ŝlosilo",
|
||||
"Hammer": "Martelo",
|
||||
|
@ -2387,5 +2387,25 @@
|
|||
"Enable advanced debugging for the room list": "Ŝalti altnivelan erarserĉadon por la ĉambrobreto",
|
||||
"* %(senderName)s %(emote)s": "* %(senderName)s %(emote)s",
|
||||
"Custom Tag": "Propra etikedo",
|
||||
"Feedback": "Prikomenti"
|
||||
"Feedback": "Prikomenti",
|
||||
"The person who invited you already left the room.": "La persono, kiu vin invitis, jam foriris de la ĉambro.",
|
||||
"The person who invited you already left the room, or their server is offline.": "Aŭ la persono, kiu vin invitis, jam foriris de la ĉambro, aŭ ĝia servilo estas eksterreta.",
|
||||
"Change notification settings": "Ŝanĝi agordojn pri sciigoj",
|
||||
"Show message previews for reactions in DMs": "Montri antaŭrigardojn al mesaĝoj ĉe reagoj en rektaj ĉambroj",
|
||||
"Show message previews for reactions in all rooms": "Montri antaŭrigardojn al mesaĝoj ĉe reagoj en ĉiuj ĉambroj",
|
||||
"Your server isn't responding to some <a>requests</a>.": "Via servilo ne respondas al iuj <a>petoj</a>.",
|
||||
"Server isn't responding": "Servilo ne respondas",
|
||||
"Your server isn't responding to some of your requests. Below are some of the most likely reasons.": "Via servilo ne respondas al iuj el viaj petoj. Vidu sube kelkon de la plej probablaj kialoj.",
|
||||
"The server (%(serverName)s) took too long to respond.": "La servilo (%(serverName)s) tro longe ne respondis.",
|
||||
"Your firewall or anti-virus is blocking the request.": "Via fajroŝirmilo aŭ kontraŭvirusilo blokas la peton.",
|
||||
"A browser extension is preventing the request.": "Kromprogramo de la foliumilo malhelpas la peton.",
|
||||
"The server is offline.": "La servilo estas eksterreta.",
|
||||
"The server has denied your request.": "La servilo rifuzis vian peton.",
|
||||
"Your area is experiencing difficulties connecting to the internet.": "Via loko nun havas problemojn pri interreta konekto.",
|
||||
"A connection error occurred while trying to contact the server.": "Eraris konekto dum provo kontakti la servilon.",
|
||||
"The server is not configured to indicate what the problem is (CORS).": "La servilo ne estas agordita por indiki la problemon (CORS).",
|
||||
"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"
|
||||
}
|
||||
|
|
|
@ -170,7 +170,7 @@
|
|||
"Are you sure you want to remove <b>%(serverName)s</b>": "Kas sa oled kindel et soovid eemadlada <b>%(serverName)s</b>",
|
||||
"Remove server": "Eemalda server",
|
||||
"%(networkName)s rooms": "%(networkName)s jututoad",
|
||||
"Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "Kas sa oled kindel et soovid eemaldada (kustutada) seda sündmust? Pane tähele, et see muutus võib tagasi pöörduda, kui muudad hiljem jututoa nime või teemanime.",
|
||||
"Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "Kas sa oled kindel et soovid eemaldada (kustutada) seda sündmust? Pane tähele, et see muutus võib tagasi pöörduda, kui muudad hiljem jututoa nime või teema nime.",
|
||||
"Sign out and remove encryption keys?": "Logi välja ja eemalda krüptimisvõtmed?",
|
||||
"Upload files (%(current)s of %(total)s)": "Lae failid üles (%(current)s / %(total)s)",
|
||||
"Upload files": "Lae failid üles",
|
||||
|
@ -650,7 +650,7 @@
|
|||
"Where you’re logged in": "Kus sa oled võrku loginud",
|
||||
"Manage the names of and sign out of your sessions below or <a>verify them in your User Profile</a>.": "Halda alljärgnevas oma sessioonide nimesid, logi neist välja või <a>verifitseeri neid oma kasutajaprofiilis</a>.",
|
||||
"A session's public name is visible to people you communicate with": "Sessiooni avalik nimi on nähtav neile, kellega sa suhtled",
|
||||
"%(brand)s collects anonymous analytics to allow us to improve the application.": "Võimaldamaks meil rakendust parandada kogub %(brand)s anonüümset analüütikat.",
|
||||
"%(brand)s collects anonymous analytics to allow us to improve the application.": "Võimaldamaks meil rakendust parandada kogub %(brand)s anonüümset teavet rakenduse kasutuse kohta.",
|
||||
"Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.": "Privaatsus on meile oluline ning seega me ei kogu ei isiklikke ega isikustatavaid andmeid.",
|
||||
"Learn more about how we use analytics.": "Loe lisaks kuidas me kasutama analüütikat.",
|
||||
"No media permissions": "Meediaõigused puuduvad",
|
||||
|
@ -839,7 +839,7 @@
|
|||
"Heart": "Süda",
|
||||
"Smiley": "Smaili",
|
||||
"Robot": "Robot",
|
||||
"Hat": "Müts",
|
||||
"Hat": "Kübar",
|
||||
"Glasses": "Prillid",
|
||||
"Spanner": "Mutrivõti",
|
||||
"Santa": "Jõuluvana",
|
||||
|
@ -870,7 +870,7 @@
|
|||
"Anchor": "Ankur",
|
||||
"Headphones": "Kõrvaklapid",
|
||||
"Folder": "Kaust",
|
||||
"Pin": "Knopka",
|
||||
"Pin": "Nööpnõel",
|
||||
"Verify all your sessions to ensure your account & messages are safe": "Selleks et sinu konto ja sõnumid oleks turvatud, verifitseeri kõik oma sessioonid",
|
||||
"Later": "Hiljem",
|
||||
"Review": "Vaata üle",
|
||||
|
@ -1025,7 +1025,7 @@
|
|||
"Permissions": "Õigused",
|
||||
"Select the roles required to change various parts of the room": "Vali rollid, mis on vajalikud jututoa eri osade muutmiseks",
|
||||
"Enable encryption?": "Kas võtame krüptimise kasutusele?",
|
||||
"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>": "Kui kord juba kasutusele võetud, siis krüptimist enam hiljem ära lõpetada ei saa. Krüptitud sõnumeid ei saa lugeda ei vaheapealses veebiliikluses ega serveris ja vaid jututoa liikmed saavad neid lugeda. Krüptimise kasutusele võtmine takistada nii robotite kui sõnumisildade tööd. <a>Lisateave krüptimise kohta.</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>": "Kui kord juba kasutusele võetud, siis krüptimist enam hiljem ära lõpetada ei saa. Krüptitud sõnumeid ei saa lugeda ei vaheapealses veebiliikluses ega serveris ja vaid jututoa liikmed saavad neid lugeda. Krüptimise kasutusele võtmine võib takistada nii robotite kui sõnumisildade tööd. <a>Lisateave krüptimise kohta.</a>",
|
||||
"Guests cannot join this room even if explicitly invited.": "Külalised ei saa selle jututoaga liituda ka siis, kui neid on otseselt kutsutud.",
|
||||
"Click here to fix": "Parandamiseks klõpsi siia",
|
||||
"Server error": "Serveri viga",
|
||||
|
@ -1334,7 +1334,7 @@
|
|||
"If you cancel now, you won't complete your operation.": "Kui sa katkestad nüüd, siis sul jääb pooleliolev tegevus lõpetamata.",
|
||||
"Cancel entering passphrase?": "Kas katkestame paroolifraasi sisestamise?",
|
||||
"Enter passphrase": "Sisesta paroolifraas",
|
||||
"Setting up keys": "Võtamevõtmed kasutusele",
|
||||
"Setting up keys": "Võtame krüptovõtmed kasutusele",
|
||||
"Room name or address": "Jututoa nimi või aadress",
|
||||
"Unable to enable Notifications": "Teavituste kasutusele võtmine ei õnnestunud",
|
||||
"This email address was not found": "Seda e-posti aadressi ei leidunud",
|
||||
|
@ -2289,8 +2289,8 @@
|
|||
"All keys backed up": "Kõik krüptovõtmed on varundatud",
|
||||
"Backup has a <validity>valid</validity> signature from this user": "Varukoopial on selle kasutaja <validity>kehtiv</validity> allkiri",
|
||||
"Backup has a <validity>invalid</validity> signature from this user": "Varukoopial on selle kasutaja <validity>vigane</validity> allkiri",
|
||||
"Backup has a signature from <verify>unknown</verify> user with ID %(deviceId)s": "Varukoopial <verify>tundmatu</verify> kasutaja allkiri seadme tunnusega %(deviceId)s",
|
||||
"Backup has a signature from <verify>unknown</verify> session with ID %(deviceId)s": "Varukoopial <verify>tundmatu</verify> sessiooni allkiri seadme tunnusega %(deviceId)s",
|
||||
"Backup has a signature from <verify>unknown</verify> user with ID %(deviceId)s": "Varukoopial on <verify>tundmatu</verify> kasutaja allkiri seadme tunnusega %(deviceId)s",
|
||||
"Backup has a signature from <verify>unknown</verify> session with ID %(deviceId)s": "Varukoopial on <verify>tundmatu</verify> sessiooni allkiri seadme tunnusega %(deviceId)s",
|
||||
"Backup has a <validity>valid</validity> signature from this session": "Varukoopial on selle sessiooni <validity>kehtiv</validity> allkiri",
|
||||
"Backup has an <validity>invalid</validity> signature from this session": "Varukoopial on selle sessiooni <validity>vigane</validity> allkiri",
|
||||
"Backup is not signed by any of your sessions": "Varunduse andmetel pole mitte ühegi sinu sessiooni allkirja",
|
||||
|
@ -2403,5 +2403,27 @@
|
|||
"You have no visible notifications in this room.": "Jututoas pole nähtavaid teavitusi.",
|
||||
"You're all caught up.": "Ei tea... kõik vist on nüüd tehtud.",
|
||||
"You’re all caught up": "Ei tea... kõik vist on nüüd tehtud",
|
||||
"%(brand)s Android": "%(brand)s Android"
|
||||
"%(brand)s Android": "%(brand)s Android",
|
||||
"Show message previews for reactions in DMs": "Näita eelvaates otsesõnumitele regeerimisi",
|
||||
"Show message previews for reactions in all rooms": "Näita kõikides jututubades eelvaadetes sõnumitele regeerimisi",
|
||||
"Master private key:": "Üldine privaatvõti:",
|
||||
"Recent changes that have not yet been received": "Hiljutised muudatused, mis pole veel alla laetud või saabunud",
|
||||
"Explore public rooms": "Sirvi avalikke jututubasid",
|
||||
"Forces the current outbound group session in an encrypted room to be discarded": "Sunnib loobuma praeguse krüptitud jututoa rühmavestluse seansist",
|
||||
"You've previously used %(brand)s on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, %(brand)s needs to resync your account.": "Oled varem kasutanud %(brand)s serveriga %(host)s ja lubanud andmete laisa laadimise. Selles versioonis on laisk laadimine keelatud. Kuna kohalik vahemälu nende kahe seadistuse vahel ei ühildu, peab %(brand)s sinu konto uuesti sünkroonima.",
|
||||
"If the other version of %(brand)s is still open in another tab, please close it as using %(brand)s on the same host with both lazy loading enabled and disabled simultaneously will cause issues.": "Kui %(brand)s teine versioon on mõnel teisel vahekaardil endiselt avatud, palun sulge see. %(brand)s kasutamine samal serveril põhjustab vigu olukorras, kus laisk laadimine on samal ajal lubatud ja keelatud.",
|
||||
"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.": "%(brand)s vanema versiooni andmed on tuvastatud. See kindlasti põhjustab läbiva krüptimise tõrke vanemas versioonis. Läbivalt krüptitud sõnumid, mida on vanema versiooni kasutamise ajal hiljuti vahetatud, ei pruugi selles versioonis olla dekrüptitavad. See võib põhjustada vigu ka selle versiooniga saadetud sõnumite lugemisel. Kui teil tekib probleeme, logige välja ja uuesti sisse. Sõnumite ajaloo säilitamiseks eksportige ja uuesti importige oma krüptovõtmed.",
|
||||
"Navigation": "Navigeerimine",
|
||||
"Uploading logs": "Laen üles logisid",
|
||||
"Downloading logs": "Laen alla logisid",
|
||||
"Can't see what you’re looking for?": "Kas sa ei leia seda, mida otsisid?",
|
||||
"Explore all public rooms": "Sirvi kõiki avalikke jututubasid",
|
||||
"%(count)s results|other": "%(count)s tulemust",
|
||||
"Preparing to download logs": "Valmistun logikirjete allalaadimiseks",
|
||||
"Download logs": "Lae logikirjed alla",
|
||||
"Unexpected server error trying to leave the room": "Jututoast lahkumisel tekkis serveris ootamatu viga",
|
||||
"Error leaving room": "Viga jututoast lahkumisel",
|
||||
"Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Kogukondade v2 prototüüp. Eeldab, et koduserver toetab sellist funktsionaalsust. Lahendus on esialgne ja katseline - kui kasutad, siis väga ettevaatlikult.",
|
||||
"Explore rooms in %(communityName)s": "Uuri jututubasid %(communityName)s kogukonnas",
|
||||
"Set up Secure Backup": "Võta kasutusele turvaline varundus"
|
||||
}
|
||||
|
|
|
@ -2233,7 +2233,7 @@
|
|||
"Alt": "Alt",
|
||||
"Alt Gr": "Alt Gr",
|
||||
"Shift": "Maiús",
|
||||
"Super": "Super",
|
||||
"Super": "Súper",
|
||||
"Ctrl": "Ctrl",
|
||||
"Toggle Bold": "Activa Resaltar",
|
||||
"Toggle Italics": "Activa Cursiva",
|
||||
|
@ -2410,5 +2410,20 @@
|
|||
"You’re all caught up": "Xa estás ó día",
|
||||
"You have no visible notifications in this room.": "Non tes notificacións visibles nesta sala.",
|
||||
"%(brand)s Android": "%(brand)s Android",
|
||||
"Master private key:": "Chave mestra principal:"
|
||||
"Master private key:": "Chave mestra principal:",
|
||||
"Show message previews for reactions in DMs": "Mostrar vista previa das mensaxes para reaccións en MDs",
|
||||
"Show message previews for reactions in all rooms": "Mostrar vista previa das mensaxes para reaccións en todas as salas",
|
||||
"Explore public rooms": "Explorar salas públicas",
|
||||
"Uploading logs": "Subindo o rexistro",
|
||||
"Downloading logs": "Descargando o rexistro",
|
||||
"Can't see what you’re looking for?": "¿Non atopas o que buscas?",
|
||||
"Explore all public rooms": "Explora todas as salas públicas",
|
||||
"%(count)s results|other": "%(count)s resultados",
|
||||
"Preparing to download logs": "Preparándose para descargar rexistro",
|
||||
"Download logs": "Descargar rexistro",
|
||||
"Unexpected server error trying to leave the room": "Fallo non agardado no servidor ó intentar saír da sala",
|
||||
"Error leaving room": "Erro ó saír da sala",
|
||||
"Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Prototipos de Comunidades v2. Require un servidor compatible. Característica experimental - usa con tino.",
|
||||
"Explore rooms in %(communityName)s": "Explorar salas en %(communityName)s",
|
||||
"Set up Secure Backup": "Configurar Copia de apoio Segura"
|
||||
}
|
||||
|
|
|
@ -811,7 +811,7 @@
|
|||
"This room is used for important messages from the Homeserver, so you cannot leave it.": "Ez a szoba fontos szerverüzenetek közlésére jött létre, nem tudsz kilépni belőle.",
|
||||
"No Audio Outputs detected": "Nem található hang kimenet",
|
||||
"Audio Output": "Hang kimenet",
|
||||
"Share Link to User": "Hivatkozás megosztása felhasználóval",
|
||||
"Share Link to User": "A felhasználóra mutató hivatkozás",
|
||||
"Share room": "Szoba megosztása",
|
||||
"Share Room": "Szoba megosztása",
|
||||
"Link to most recent message": "A legfrissebb üzenetre hivatkozás",
|
||||
|
@ -987,7 +987,7 @@
|
|||
"Send typing notifications": "Gépelés visszajelzés küldése",
|
||||
"Enable Community Filter Panel": "Közösségi szűrő panel bekapcsolása",
|
||||
"Messages containing my username": "Üzenetek amik a nevemet tartalmazzák",
|
||||
"The other party cancelled the verification.": "A másik fél törölte az ellenőrzést.",
|
||||
"The other party cancelled the verification.": "A másik fél megszakította az ellenőrzést.",
|
||||
"Verified!": "Ellenőrizve!",
|
||||
"You've successfully verified this user.": "Sikeresen ellenőrizted ezt a felhasználót.",
|
||||
"Secure messages with this user are end-to-end encrypted and not able to be read by third parties.": "Az üzenetek a felhasználóval végponttól végpontig titkosítva vannak és azt más nem tudja elolvasni.",
|
||||
|
@ -1630,7 +1630,7 @@
|
|||
"<strong>%(role)s</strong> in %(roomName)s": "<strong>%(role)s</strong> a szobában: %(roomName)s",
|
||||
"Messages in this room are end-to-end encrypted.": "Az üzenetek a szobában végponttól végpontig titkosítottak.",
|
||||
"Security": "Biztonság",
|
||||
"Verify": "Ellenőriz",
|
||||
"Verify": "Ellenőrzés",
|
||||
"Any of the following data may be shared:": "Az alábbi adatok közül bármelyik megosztásra kerülhet:",
|
||||
"Your display name": "Megjelenítési neved",
|
||||
"Your avatar URL": "Profilképed URL-je",
|
||||
|
@ -1888,7 +1888,7 @@
|
|||
"You've successfully verified %(displayName)s!": "Sikeresen ellenőrizted a felhasználót: %(displayName)s!",
|
||||
"Got it": "Értem",
|
||||
"Encryption enabled": "Titkosítás bekapcsolva",
|
||||
"Messages in this room are end-to-end encrypted. Learn more & verify this user in their user profile.": "Ebben a szobában az üzenetek végpontok között titkosítottak. További információkért és ellenőrzéshez nyisd meg a felhasználó profilját.",
|
||||
"Messages in this room are end-to-end encrypted. Learn more & verify this user in their user profile.": "Ebben a szobában az üzenetek végpontok között titkosítottak. További információkért és az ellenőrzéshez nyisd meg a felhasználók profiljait!",
|
||||
"Encryption not enabled": "Titkosítás nincs engedélyezve",
|
||||
"The encryption used by this room isn't supported.": "A szobában használt titkosítás nem támogatott.",
|
||||
"Clear all data in this session?": "Minden adat törlése ebben a munkamenetben?",
|
||||
|
@ -2109,7 +2109,7 @@
|
|||
"Server did not return valid authentication information.": "A szerver semmilyen érvényes azonosítási információt sem küldött vissza.",
|
||||
"There was a problem communicating with the server. Please try again.": "A szerverrel való kommunikációval probléma történt. Kérlek próbáld újra.",
|
||||
"Sign in with SSO": "Belépés SSO-val",
|
||||
"Welcome to %(appName)s": "Üdv itt: %(appName)s",
|
||||
"Welcome to %(appName)s": "Üdvözöl az %(appName)s",
|
||||
"Liberate your communication": "Szabadítsd fel a kommunikációdat",
|
||||
"Send a Direct Message": "Közvetlen üzenet küldése",
|
||||
"Explore Public Rooms": "Nyilvános szobák felderítése",
|
||||
|
@ -2177,7 +2177,7 @@
|
|||
"Waiting for your other session to verify…": "A másik munkameneted ellenőrzésére várunk…",
|
||||
"You've successfully verified your device!": "Sikeresen ellenőrizted az eszközödet!",
|
||||
"Message deleted": "Üzenet törölve",
|
||||
"Message deleted by %(name)s": "%(name)s törölte az üzenetet",
|
||||
"Message deleted by %(name)s": "%(name)s törölte ezt az üzenetet",
|
||||
"QR Code": "QR kód",
|
||||
"To continue, use Single Sign On to prove your identity.": "A folytatáshoz a személyazonosságod megerősítéséhez használd az egyszeri bejelentkezést.",
|
||||
"Confirm to continue": "Erősítsd meg a továbblépéshez",
|
||||
|
@ -2234,12 +2234,12 @@
|
|||
"Emoji picker": "Emodzsi választó",
|
||||
"No recently visited rooms": "Nincsenek nemrégiben meglátogatott szobák",
|
||||
"People": "Emberek",
|
||||
"Sort by": "Rendezve:",
|
||||
"Sort by": "Rendezés",
|
||||
"Unread rooms": "Olvasatlan szobák",
|
||||
"Always show first": "Mindig az elsőt mutatja",
|
||||
"Show": "Mutat",
|
||||
"Message preview": "Üzenet előnézet",
|
||||
"List options": "Lista beállítások",
|
||||
"List options": "Lista beállításai",
|
||||
"Show %(count)s more|other": "Még %(count)s megjelenítése",
|
||||
"Show %(count)s more|one": "Még %(count)s megjelenítése",
|
||||
"Leave Room": "Szoba elhagyása",
|
||||
|
@ -2404,5 +2404,14 @@
|
|||
"Your area is experiencing difficulties connecting to the internet.": "Probléma az Internet elérésben.",
|
||||
"A connection error occurred while trying to contact the server.": "Kapcsolati hiba lépett fel miközben a szervert próbáltad elérni.",
|
||||
"The server is not configured to indicate what the problem is (CORS).": "A szerver nincs beállítva, hogy megmutassa mi okozhatta a hibát (CORS).",
|
||||
"Recent changes that have not yet been received": "Legutóbbi változások amik még nem érkeztek meg"
|
||||
"Recent changes that have not yet been received": "Legutóbbi változások amik még nem érkeztek meg",
|
||||
"Show message previews for reactions in DMs": "Üzenet előnézet mutatása a reakcióhoz a közvetlen beszélgetéseknél",
|
||||
"Show message previews for reactions in all rooms": "Üzenet előnézet mutatása a reakciókhoz minden szobában",
|
||||
"Master private key:": "Privát elsődleges kulcs:",
|
||||
"No files visible in this room": "Ebben a szobában nincsenek fájlok",
|
||||
"Attach files from chat or just drag and drop them anywhere in a room.": "Csatolj fájlt a csevegésből vagy húzd és ejtsd bárhova a szobában.",
|
||||
"You’re all caught up": "Mindent elolvastál",
|
||||
"You have no visible notifications in this room.": "Nincsenek látható értesítéseid ebben a szobában.",
|
||||
"%(brand)s Android": "%(brand)s Android",
|
||||
"Explore public rooms": "Nyilvános szobák felderítése"
|
||||
}
|
||||
|
|
|
@ -2413,5 +2413,15 @@
|
|||
"Attach files from chat or just drag and drop them anywhere in a room.": "Allega file dalla chat o trascinali in qualsiasi punto in una stanza.",
|
||||
"You’re all caught up": "Non hai nulla di nuovo da vedere",
|
||||
"You have no visible notifications in this room.": "Non hai alcuna notifica visibile in questa stanza.",
|
||||
"%(brand)s Android": "%(brand)s Android"
|
||||
"%(brand)s Android": "%(brand)s Android",
|
||||
"Show message previews for reactions in DMs": "Mostra anteprime messaggi per le reazioni nei messaggi diretti",
|
||||
"Show message previews for reactions in all rooms": "Mostra anteprime messaggi per le reazioni in tutte le stanze",
|
||||
"Explore public rooms": "Esplora stanze pubbliche",
|
||||
"Uploading logs": "Invio dei log",
|
||||
"Downloading logs": "Scaricamento dei log",
|
||||
"Can't see what you’re looking for?": "Non vedi quello che cerchi?",
|
||||
"Explore all public rooms": "Esplora tutte le stanze pubbliche",
|
||||
"%(count)s results|other": "%(count)s risultati",
|
||||
"Preparing to download logs": "Preparazione al download dei log",
|
||||
"Download logs": "Scarica i log"
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue