mirror of
https://github.com/element-hq/element-web
synced 2024-11-29 04:48:50 +03:00
Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into t3chguy/feat/widgets
This commit is contained in:
commit
b8080a7d2d
40 changed files with 450 additions and 1101 deletions
|
@ -1,50 +1,31 @@
|
||||||
# autogenerated file: run scripts/generate-eslint-error-ignore-file to update.
|
# autogenerated file: run scripts/generate-eslint-error-ignore-file to update.
|
||||||
|
|
||||||
src/components/structures/RoomDirectory.js
|
|
||||||
src/components/structures/RoomStatusBar.js
|
|
||||||
src/components/structures/ScrollPanel.js
|
|
||||||
src/components/structures/SearchBox.js
|
|
||||||
src/components/structures/UploadBar.js
|
|
||||||
src/components/views/avatars/MemberAvatar.js
|
|
||||||
src/components/views/create_room/RoomAlias.js
|
|
||||||
src/components/views/dialogs/SetPasswordDialog.js
|
|
||||||
src/components/views/elements/AddressSelector.js
|
|
||||||
src/components/views/elements/DirectorySearchBox.js
|
|
||||||
src/components/views/elements/MemberEventListSummary.js
|
|
||||||
src/components/views/elements/UserSelector.js
|
|
||||||
src/components/views/globals/NewVersionBar.js
|
|
||||||
src/components/views/messages/MFileBody.js
|
|
||||||
src/components/views/messages/TextualBody.js
|
|
||||||
src/components/views/room_settings/ColorSettings.js
|
|
||||||
src/components/views/rooms/Autocomplete.js
|
|
||||||
src/components/views/rooms/AuxPanel.js
|
|
||||||
src/components/views/rooms/LinkPreviewWidget.js
|
|
||||||
src/components/views/rooms/MemberInfo.js
|
|
||||||
src/components/views/rooms/MemberList.js
|
|
||||||
src/components/views/rooms/RoomList.js
|
|
||||||
src/components/views/rooms/RoomPreviewBar.js
|
|
||||||
src/components/views/rooms/SearchResultTile.js
|
|
||||||
src/components/views/settings/ChangeAvatar.js
|
|
||||||
src/components/views/settings/ChangePassword.js
|
|
||||||
src/components/views/settings/DevicesPanel.js
|
|
||||||
src/components/views/settings/Notifications.js
|
|
||||||
src/HtmlUtils.js
|
|
||||||
src/ImageUtils.js
|
src/ImageUtils.js
|
||||||
src/Markdown.js
|
src/Markdown.js
|
||||||
src/notifications/ContentRules.js
|
|
||||||
src/notifications/PushRuleVectorState.js
|
|
||||||
src/PlatformPeg.js
|
|
||||||
src/rageshake/rageshake.js
|
|
||||||
src/ratelimitedfunc.js
|
|
||||||
src/Rooms.js
|
src/Rooms.js
|
||||||
src/Unread.js
|
src/Unread.js
|
||||||
|
src/Velociraptor.js
|
||||||
|
src/components/structures/RoomDirectory.js
|
||||||
|
src/components/structures/ScrollPanel.js
|
||||||
|
src/components/structures/UploadBar.js
|
||||||
|
src/components/views/elements/AddressSelector.js
|
||||||
|
src/components/views/elements/DirectorySearchBox.js
|
||||||
|
src/components/views/messages/MFileBody.js
|
||||||
|
src/components/views/messages/TextualBody.js
|
||||||
|
src/components/views/rooms/AuxPanel.js
|
||||||
|
src/components/views/rooms/LinkPreviewWidget.js
|
||||||
|
src/components/views/rooms/MemberList.js
|
||||||
|
src/components/views/rooms/RoomPreviewBar.js
|
||||||
|
src/components/views/settings/ChangeAvatar.js
|
||||||
|
src/components/views/settings/DevicesPanel.js
|
||||||
|
src/components/views/settings/Notifications.js
|
||||||
|
src/rageshake/rageshake.js
|
||||||
|
src/ratelimitedfunc.js
|
||||||
|
src/utils/DMRoomMap.js
|
||||||
src/utils/DecryptFile.js
|
src/utils/DecryptFile.js
|
||||||
src/utils/DirectoryUtils.js
|
src/utils/DirectoryUtils.js
|
||||||
src/utils/DMRoomMap.js
|
|
||||||
src/utils/FormattingUtils.js
|
|
||||||
src/utils/MultiInviter.js
|
src/utils/MultiInviter.js
|
||||||
src/utils/Receipt.js
|
src/utils/Receipt.js
|
||||||
src/Velociraptor.js
|
|
||||||
test/components/structures/MessagePanel-test.js
|
test/components/structures/MessagePanel-test.js
|
||||||
test/components/views/dialogs/InteractiveAuthDialog-test.js
|
test/components/views/dialogs/InteractiveAuthDialog-test.js
|
||||||
test/mock-clock.js
|
test/mock-clock.js
|
||||||
|
|
|
@ -262,7 +262,7 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus {
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
font-size: $font-15px;
|
font-size: $font-15px;
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 25px 30px 30px 30px;
|
padding: 24px;
|
||||||
max-height: 80%;
|
max-height: 80%;
|
||||||
box-shadow: 2px 15px 30px 0 $dialog-shadow-color;
|
box-shadow: 2px 15px 30px 0 $dialog-shadow-color;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
|
|
@ -81,8 +81,6 @@
|
||||||
@import "./views/dialogs/_RoomUpgradeWarningDialog.scss";
|
@import "./views/dialogs/_RoomUpgradeWarningDialog.scss";
|
||||||
@import "./views/dialogs/_ServerOfflineDialog.scss";
|
@import "./views/dialogs/_ServerOfflineDialog.scss";
|
||||||
@import "./views/dialogs/_SetEmailDialog.scss";
|
@import "./views/dialogs/_SetEmailDialog.scss";
|
||||||
@import "./views/dialogs/_SetMxIdDialog.scss";
|
|
||||||
@import "./views/dialogs/_SetPasswordDialog.scss";
|
|
||||||
@import "./views/dialogs/_SettingsDialog.scss";
|
@import "./views/dialogs/_SettingsDialog.scss";
|
||||||
@import "./views/dialogs/_ShareDialog.scss";
|
@import "./views/dialogs/_ShareDialog.scss";
|
||||||
@import "./views/dialogs/_SlashCommandHelpDialog.scss";
|
@import "./views/dialogs/_SlashCommandHelpDialog.scss";
|
||||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_TabbedView {
|
.mx_TabbedView {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0 0 0 58px;
|
padding: 0 0 0 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -25,6 +25,7 @@ limitations under the License.
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_TabbedView_tabLabels {
|
.mx_TabbedView_tabLabels {
|
||||||
|
@ -35,13 +36,13 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_TabbedView_tabLabel {
|
.mx_TabbedView_tabLabel {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
vertical-align: text-top;
|
vertical-align: text-top;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: block;
|
padding: 8px 0;
|
||||||
border-radius: 3px;
|
border-radius: 8px;
|
||||||
font-size: $font-14px;
|
font-size: $font-13px;
|
||||||
min-height: 24px; // use min-height instead of height to allow the label to overflow a bit
|
|
||||||
margin-bottom: 6px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,9 +52,8 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_TabbedView_maskedIcon {
|
.mx_TabbedView_maskedIcon {
|
||||||
margin-left: 6px;
|
margin-left: 8px;
|
||||||
margin-right: 9px;
|
margin-right: 16px;
|
||||||
margin-top: 1px;
|
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -65,10 +65,9 @@ limitations under the License.
|
||||||
mask-repeat: no-repeat;
|
mask-repeat: no-repeat;
|
||||||
mask-size: 16px;
|
mask-size: 16px;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 22px;
|
height: 16px;
|
||||||
mask-position: center;
|
mask-position: center;
|
||||||
content: '';
|
content: '';
|
||||||
vertical-align: middle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_TabbedView_tabLabel_active .mx_TabbedView_maskedIcon::before {
|
.mx_TabbedView_tabLabel_active .mx_TabbedView_maskedIcon::before {
|
||||||
|
|
|
@ -48,7 +48,6 @@ limitations under the License.
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding-left: 40px;
|
|
||||||
padding-right: 80px;
|
padding-right: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
|
||||||
Copyright 2017 Vector Creations 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
.mx_SetMxIdDialog .mx_Dialog_title {
|
|
||||||
padding-right: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_SetMxIdDialog_input_group {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_SetMxIdDialog_input {
|
|
||||||
border-radius: 3px;
|
|
||||||
border: 1px solid $input-border-color;
|
|
||||||
padding: 9px;
|
|
||||||
color: $primary-fg-color;
|
|
||||||
background-color: $primary-bg-color;
|
|
||||||
font-size: $font-15px;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 280px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_SetMxIdDialog_input.error,
|
|
||||||
.mx_SetMxIdDialog_input.error:focus {
|
|
||||||
border: 1px solid $warning-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_SetMxIdDialog_input_group .mx_Spinner {
|
|
||||||
height: 37px;
|
|
||||||
padding-left: 10px;
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_SetMxIdDialog .success {
|
|
||||||
color: $accent-color;
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 Vector Creations 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
.mx_SetPasswordDialog_change_password input {
|
|
||||||
border-radius: 3px;
|
|
||||||
border: 1px solid $input-border-color;
|
|
||||||
padding: 9px;
|
|
||||||
color: $primary-fg-color;
|
|
||||||
background-color: $primary-bg-color;
|
|
||||||
font-size: $font-15px;
|
|
||||||
max-width: 280px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_SetPasswordDialog_change_password_button {
|
|
||||||
margin-top: 68px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_SetPasswordDialog .mx_Dialog_content {
|
|
||||||
margin-bottom: 0px;
|
|
||||||
}
|
|
|
@ -36,7 +36,6 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_Dialog_title {
|
.mx_Dialog_title {
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,7 @@ limitations under the License.
|
||||||
width: 20px;
|
width: 20px;
|
||||||
margin: 12px;
|
margin: 12px;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
border-radius: 10px;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: "";
|
content: "";
|
||||||
|
@ -55,7 +56,6 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_BaseCard_back {
|
.mx_BaseCard_back {
|
||||||
border-radius: 4px;
|
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
|
@ -66,7 +66,6 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_BaseCard_close {
|
.mx_BaseCard_close {
|
||||||
border-radius: 10px;
|
|
||||||
right: 0;
|
right: 0;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
|
|
|
@ -96,11 +96,21 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MemberList_invite span {
|
.mx_MemberList_invite span {
|
||||||
background-image: url('$(res)/img/element-icons/room/invite.svg');
|
padding: 8px 0;
|
||||||
background-repeat: no-repeat;
|
display: inline-flex;
|
||||||
background-position: center left;
|
|
||||||
background-size: 20px;
|
&::before {
|
||||||
padding: 8px 0 8px 25px;
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
background-color: $button-fg-color;
|
||||||
|
mask-image: url('$(res)/img/element-icons/room/invite.svg');
|
||||||
|
mask-position: center;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-size: 20px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MemberList_inviteCommunity span {
|
.mx_MemberList_inviteCommunity span {
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M28 8.5C28 12.0899 25.0899 15 21.5 15C17.9101 15 15 12.0899 15 8.5C15 4.91015 17.9101 2 21.5 2C25.0899 2 28 4.91015 28 8.5ZM22.5 5C22.5 4.44772 22.0523 4 21.5 4C20.9477 4 20.5 4.44772 20.5 5V7.6286H17.9C17.4029 7.6286 17 8.02089 17 8.5048C17 8.98871 17.4029 9.381 17.9 9.381H20.5V12.0096C20.5 12.5619 20.9477 13.0096 21.5 13.0096C22.0523 13.0096 22.5 12.5619 22.5 12.0096V9.381H25.1C25.5971 9.381 26 8.98871 26 8.5048C26 8.02089 25.5971 7.6286 25.1 7.6286H22.5V5ZM21.5 16C23.6351 16 25.5619 15.1078 26.9278 13.6759C26.9755 14.1107 27 14.5525 27 15C27 18.9261 25.1146 22.4117 22.1998 24.601V24.6009C20.348 25.9918 18.0808 26.8595 15.6175 26.9844C15.413 26.9948 15.2071 27 15 27C8.37258 27 3 21.6274 3 15C3 8.37258 8.37258 3 15 3C15.4475 3 15.8893 3.0245 16.3241 3.07223C14.929 4.40304 14.0462 6.26631 14.0018 8.336C12.8183 8.89737 12 10.1031 12 11.5C12 13.433 13.567 15 15.5 15C16.0892 15 16.6445 14.8544 17.1316 14.5972C18.3618 15.4802 19.8702 16 21.5 16ZM14.9998 24.6C17.5942 24.6 19.9482 23.5709 21.6759 21.8986C20.6074 19.2607 18.0209 17.4 14.9998 17.4C11.9787 17.4 9.39221 19.2607 8.32376 21.8986C10.0514 23.5709 12.4054 24.6 14.9998 24.6Z" fill="white"/>
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.1001 9C18.7779 9 18.5168 8.73883 18.5168 8.41667V6.08333H16.1834C15.8613 6.08333 15.6001 5.82217 15.6001 5.5C15.6001 5.17783 15.8613 4.91667 16.1834 4.91667H18.5168V2.58333C18.5168 2.26117 18.7779 2 19.1001 2C19.4223 2 19.6834 2.26117 19.6834 2.58333V4.91667H22.0168C22.3389 4.91667 22.6001 5.17783 22.6001 5.5C22.6001 5.82217 22.3389 6.08333 22.0168 6.08333H19.6834V8.41667C19.6834 8.73883 19.4223 9 19.1001 9ZM19.6001 11C20.0669 11 20.5212 10.9467 20.9574 10.8458C21.1161 11.5383 21.2 12.2594 21.2 13C21.2 16.1409 19.6917 18.9294 17.3598 20.6808V20.6807C16.0014 21.7011 14.3635 22.3695 12.5815 22.5505C12.2588 22.5832 11.9314 22.6 11.6 22.6C6.29807 22.6 2 18.302 2 13C2 7.69809 6.29807 3.40002 11.6 3.40002C12.3407 3.40002 13.0618 3.48391 13.7543 3.64268C13.6534 4.07884 13.6001 4.53319 13.6001 5C13.6001 8.31371 16.2864 11 19.6001 11ZM11.5999 20.68C13.6754 20.68 15.5585 19.8567 16.9407 18.5189C16.0859 16.4086 14.0167 14.92 11.5998 14.92C9.18298 14.92 7.11378 16.4086 6.25901 18.5189C7.64115 19.8567 9.52436 20.68 11.5999 20.68ZM11.7426 7.41172C10.3168 7.54168 9.2 8.74043 9.2 10.2C9.2 11.7464 10.4536 13 12 13C13.0308 13 13.9315 12.443 14.4176 11.6135C13.0673 10.6058 12.0929 9.12248 11.7426 7.41172Z" fill="black"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
@ -17,9 +17,12 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// @ts-ignore - XXX: tsc doesn't like this: our js-sdk imports are complex so this isn't surprising
|
||||||
import Matrix from 'matrix-js-sdk';
|
import Matrix from 'matrix-js-sdk';
|
||||||
|
import { InvalidStoreError } from "matrix-js-sdk/src/errors";
|
||||||
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
|
|
||||||
import {MatrixClientPeg} from './MatrixClientPeg';
|
import {IMatrixClientCreds, MatrixClientPeg} from './MatrixClientPeg';
|
||||||
import EventIndexPeg from './indexing/EventIndexPeg';
|
import EventIndexPeg from './indexing/EventIndexPeg';
|
||||||
import createMatrixClient from './utils/createMatrixClient';
|
import createMatrixClient from './utils/createMatrixClient';
|
||||||
import Analytics from './Analytics';
|
import Analytics from './Analytics';
|
||||||
|
@ -47,44 +50,46 @@ import ThreepidInviteStore from "./stores/ThreepidInviteStore";
|
||||||
const HOMESERVER_URL_KEY = "mx_hs_url";
|
const HOMESERVER_URL_KEY = "mx_hs_url";
|
||||||
const ID_SERVER_URL_KEY = "mx_is_url";
|
const ID_SERVER_URL_KEY = "mx_is_url";
|
||||||
|
|
||||||
|
interface ILoadSessionOpts {
|
||||||
|
enableGuest?: boolean;
|
||||||
|
guestHsUrl?: string;
|
||||||
|
guestIsUrl?: string;
|
||||||
|
ignoreGuest?: boolean;
|
||||||
|
defaultDeviceDisplayName?: string;
|
||||||
|
fragmentQueryParams?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called at startup, to attempt to build a logged-in Matrix session. It tries
|
* Called at startup, to attempt to build a logged-in Matrix session. It tries
|
||||||
* a number of things:
|
* a number of things:
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* 1. if we have a guest access token in the fragment query params, it uses
|
* 1. if we have a guest access token in the fragment query params, it uses
|
||||||
* that.
|
* that.
|
||||||
*
|
|
||||||
* 2. if an access token is stored in local storage (from a previous session),
|
* 2. if an access token is stored in local storage (from a previous session),
|
||||||
* it uses that.
|
* it uses that.
|
||||||
*
|
|
||||||
* 3. it attempts to auto-register as a guest user.
|
* 3. it attempts to auto-register as a guest user.
|
||||||
*
|
*
|
||||||
* If any of steps 1-4 are successful, it will call {_doSetLoggedIn}, which in
|
* If any of steps 1-4 are successful, it will call {_doSetLoggedIn}, which in
|
||||||
* turn will raise on_logged_in and will_start_client events.
|
* turn will raise on_logged_in and will_start_client events.
|
||||||
*
|
*
|
||||||
* @param {object} opts
|
* @param {object} [opts]
|
||||||
*
|
* @param {object} [opts.fragmentQueryParams]: string->string map of the
|
||||||
* @param {object} opts.fragmentQueryParams: string->string map of the
|
|
||||||
* query-parameters extracted from the #-fragment of the starting URI.
|
* query-parameters extracted from the #-fragment of the starting URI.
|
||||||
*
|
* @param {boolean} [opts.enableGuest]: set to true to enable guest access
|
||||||
* @param {boolean} opts.enableGuest: set to true to enable guest access tokens
|
* tokens and auto-guest registrations.
|
||||||
* and auto-guest registrations.
|
* @param {string} [opts.guestHsUrl]: homeserver URL. Only used if enableGuest
|
||||||
*
|
* is true; defines the HS to register against.
|
||||||
* @params {string} opts.guestHsUrl: homeserver URL. Only used if enableGuest is
|
* @param {string} [opts.guestIsUrl]: homeserver URL. Only used if enableGuest
|
||||||
* true; defines the HS to register against.
|
* is true; defines the IS to use.
|
||||||
*
|
* @param {bool} [opts.ignoreGuest]: If the stored session is a guest account,
|
||||||
* @params {string} opts.guestIsUrl: homeserver URL. Only used if enableGuest is
|
* ignore it and don't load it.
|
||||||
* true; defines the IS to use.
|
* @param {string} [opts.defaultDeviceDisplayName]: Default display name to use
|
||||||
*
|
* when registering as a guest.
|
||||||
* @params {bool} opts.ignoreGuest: If the stored session is a guest account, ignore
|
|
||||||
* it and don't load it.
|
|
||||||
*
|
|
||||||
* @returns {Promise} a promise which resolves when the above process completes.
|
* @returns {Promise} a promise which resolves when the above process completes.
|
||||||
* Resolves to `true` if we ended up starting a session, or `false` if we
|
* Resolves to `true` if we ended up starting a session, or `false` if we
|
||||||
* failed.
|
* failed.
|
||||||
*/
|
*/
|
||||||
export async function loadSession(opts) {
|
export async function loadSession(opts: ILoadSessionOpts = {}): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
let enableGuest = opts.enableGuest || false;
|
let enableGuest = opts.enableGuest || false;
|
||||||
const guestHsUrl = opts.guestHsUrl;
|
const guestHsUrl = opts.guestHsUrl;
|
||||||
|
@ -97,12 +102,13 @@ export async function loadSession(opts) {
|
||||||
enableGuest = false;
|
enableGuest = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enableGuest &&
|
if (
|
||||||
|
enableGuest &&
|
||||||
fragmentQueryParams.guest_user_id &&
|
fragmentQueryParams.guest_user_id &&
|
||||||
fragmentQueryParams.guest_access_token
|
fragmentQueryParams.guest_access_token
|
||||||
) {
|
) {
|
||||||
console.log("Using guest access credentials");
|
console.log("Using guest access credentials");
|
||||||
return _doSetLoggedIn({
|
return doSetLoggedIn({
|
||||||
userId: fragmentQueryParams.guest_user_id,
|
userId: fragmentQueryParams.guest_user_id,
|
||||||
accessToken: fragmentQueryParams.guest_access_token,
|
accessToken: fragmentQueryParams.guest_access_token,
|
||||||
homeserverUrl: guestHsUrl,
|
homeserverUrl: guestHsUrl,
|
||||||
|
@ -110,7 +116,7 @@ export async function loadSession(opts) {
|
||||||
guest: true,
|
guest: true,
|
||||||
}, true).then(() => true);
|
}, true).then(() => true);
|
||||||
}
|
}
|
||||||
const success = await _restoreFromLocalStorage({
|
const success = await restoreFromLocalStorage({
|
||||||
ignoreGuest: Boolean(opts.ignoreGuest),
|
ignoreGuest: Boolean(opts.ignoreGuest),
|
||||||
});
|
});
|
||||||
if (success) {
|
if (success) {
|
||||||
|
@ -118,7 +124,7 @@ export async function loadSession(opts) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enableGuest) {
|
if (enableGuest) {
|
||||||
return _registerAsGuest(guestHsUrl, guestIsUrl, defaultDeviceDisplayName);
|
return registerAsGuest(guestHsUrl, guestIsUrl, defaultDeviceDisplayName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// fall back to welcome screen
|
// fall back to welcome screen
|
||||||
|
@ -129,7 +135,7 @@ export async function loadSession(opts) {
|
||||||
// need to show the general failure dialog. Instead, just go back to welcome.
|
// need to show the general failure dialog. Instead, just go back to welcome.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return _handleLoadSessionFailure(e);
|
return handleLoadSessionFailure(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,7 +145,7 @@ export async function loadSession(opts) {
|
||||||
* is associated with them. The session is not loaded.
|
* is associated with them. The session is not loaded.
|
||||||
* @returns {String} The persisted session's owner, if an owner exists. Null otherwise.
|
* @returns {String} The persisted session's owner, if an owner exists. Null otherwise.
|
||||||
*/
|
*/
|
||||||
export function getStoredSessionOwner() {
|
export function getStoredSessionOwner(): string {
|
||||||
const {hsUrl, userId, accessToken} = getLocalStorageSessionVars();
|
const {hsUrl, userId, accessToken} = getLocalStorageSessionVars();
|
||||||
return hsUrl && userId && accessToken ? userId : null;
|
return hsUrl && userId && accessToken ? userId : null;
|
||||||
}
|
}
|
||||||
|
@ -148,7 +154,7 @@ export function getStoredSessionOwner() {
|
||||||
* @returns {bool} True if the stored session is for a guest user or false if it is
|
* @returns {bool} True if the stored session is for a guest user or false if it is
|
||||||
* for a real user. If there is no stored session, return null.
|
* for a real user. If there is no stored session, return null.
|
||||||
*/
|
*/
|
||||||
export function getStoredSessionIsGuest() {
|
export function getStoredSessionIsGuest(): boolean {
|
||||||
const sessVars = getLocalStorageSessionVars();
|
const sessVars = getLocalStorageSessionVars();
|
||||||
return sessVars.hsUrl && sessVars.userId && sessVars.accessToken ? sessVars.isGuest : null;
|
return sessVars.hsUrl && sessVars.userId && sessVars.accessToken ? sessVars.isGuest : null;
|
||||||
}
|
}
|
||||||
|
@ -163,7 +169,10 @@ export function getStoredSessionIsGuest() {
|
||||||
* @returns {Promise} promise which resolves to true if we completed the token
|
* @returns {Promise} promise which resolves to true if we completed the token
|
||||||
* login, else false
|
* login, else false
|
||||||
*/
|
*/
|
||||||
export function attemptTokenLogin(queryParams, defaultDeviceDisplayName) {
|
export function attemptTokenLogin(
|
||||||
|
queryParams: Record<string, string>,
|
||||||
|
defaultDeviceDisplayName?: string,
|
||||||
|
): Promise<boolean> {
|
||||||
if (!queryParams.loginToken) {
|
if (!queryParams.loginToken) {
|
||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
|
@ -184,10 +193,10 @@ export function attemptTokenLogin(queryParams, defaultDeviceDisplayName) {
|
||||||
},
|
},
|
||||||
).then(function(creds) {
|
).then(function(creds) {
|
||||||
console.log("Logged in with token");
|
console.log("Logged in with token");
|
||||||
return _clearStorage().then(() => {
|
return clearStorage().then(() => {
|
||||||
_persistCredentialsToLocalStorage(creds);
|
persistCredentialsToLocalStorage(creds);
|
||||||
// remember that we just logged in
|
// remember that we just logged in
|
||||||
sessionStorage.setItem("mx_fresh_login", true);
|
sessionStorage.setItem("mx_fresh_login", String(true));
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
|
@ -197,8 +206,8 @@ export function attemptTokenLogin(queryParams, defaultDeviceDisplayName) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleInvalidStoreError(e) {
|
export function handleInvalidStoreError(e: InvalidStoreError): Promise<void> {
|
||||||
if (e.reason === Matrix.InvalidStoreError.TOGGLED_LAZY_LOADING) {
|
if (e.reason === InvalidStoreError.TOGGLED_LAZY_LOADING) {
|
||||||
return Promise.resolve().then(() => {
|
return Promise.resolve().then(() => {
|
||||||
const lazyLoadEnabled = e.value;
|
const lazyLoadEnabled = e.value;
|
||||||
if (lazyLoadEnabled) {
|
if (lazyLoadEnabled) {
|
||||||
|
@ -231,7 +240,11 @@ export function handleInvalidStoreError(e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) {
|
function registerAsGuest(
|
||||||
|
hsUrl: string,
|
||||||
|
isUrl: string,
|
||||||
|
defaultDeviceDisplayName: string,
|
||||||
|
): Promise<boolean> {
|
||||||
console.log(`Doing guest login on ${hsUrl}`);
|
console.log(`Doing guest login on ${hsUrl}`);
|
||||||
|
|
||||||
// create a temporary MatrixClient to do the login
|
// create a temporary MatrixClient to do the login
|
||||||
|
@ -245,7 +258,7 @@ function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) {
|
||||||
},
|
},
|
||||||
}).then((creds) => {
|
}).then((creds) => {
|
||||||
console.log(`Registered as guest: ${creds.user_id}`);
|
console.log(`Registered as guest: ${creds.user_id}`);
|
||||||
return _doSetLoggedIn({
|
return doSetLoggedIn({
|
||||||
userId: creds.user_id,
|
userId: creds.user_id,
|
||||||
deviceId: creds.device_id,
|
deviceId: creds.device_id,
|
||||||
accessToken: creds.access_token,
|
accessToken: creds.access_token,
|
||||||
|
@ -259,12 +272,21 @@ function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ILocalStorageSession {
|
||||||
|
hsUrl: string;
|
||||||
|
isUrl: string;
|
||||||
|
accessToken: string;
|
||||||
|
userId: string;
|
||||||
|
deviceId: string;
|
||||||
|
isGuest: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves information about the stored session in localstorage. The session
|
* Retrieves information about the stored session in localstorage. The session
|
||||||
* may not be valid, as it is not tested for consistency here.
|
* may not be valid, as it is not tested for consistency here.
|
||||||
* @returns {Object} Information about the session - see implementation for variables.
|
* @returns {Object} Information about the session - see implementation for variables.
|
||||||
*/
|
*/
|
||||||
export function getLocalStorageSessionVars() {
|
export function getLocalStorageSessionVars(): ILocalStorageSession {
|
||||||
const hsUrl = localStorage.getItem(HOMESERVER_URL_KEY);
|
const hsUrl = localStorage.getItem(HOMESERVER_URL_KEY);
|
||||||
const isUrl = localStorage.getItem(ID_SERVER_URL_KEY);
|
const isUrl = localStorage.getItem(ID_SERVER_URL_KEY);
|
||||||
const accessToken = localStorage.getItem("mx_access_token");
|
const accessToken = localStorage.getItem("mx_access_token");
|
||||||
|
@ -292,8 +314,8 @@ export function getLocalStorageSessionVars() {
|
||||||
// The plan is to gradually move the localStorage access done here into
|
// The plan is to gradually move the localStorage access done here into
|
||||||
// SessionStore to avoid bugs where the view becomes out-of-sync with
|
// SessionStore to avoid bugs where the view becomes out-of-sync with
|
||||||
// localStorage (e.g. isGuest etc.)
|
// localStorage (e.g. isGuest etc.)
|
||||||
async function _restoreFromLocalStorage(opts) {
|
async function restoreFromLocalStorage(opts?: { ignoreGuest?: boolean }): Promise<boolean> {
|
||||||
const ignoreGuest = opts.ignoreGuest;
|
const ignoreGuest = opts?.ignoreGuest;
|
||||||
|
|
||||||
if (!localStorage) {
|
if (!localStorage) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -314,11 +336,11 @@ async function _restoreFromLocalStorage(opts) {
|
||||||
console.log("No pickle key available");
|
console.log("No pickle key available");
|
||||||
}
|
}
|
||||||
|
|
||||||
const freshLogin = sessionStorage.getItem("mx_fresh_login");
|
const freshLogin = sessionStorage.getItem("mx_fresh_login") === "true";
|
||||||
sessionStorage.removeItem("mx_fresh_login");
|
sessionStorage.removeItem("mx_fresh_login");
|
||||||
|
|
||||||
console.log(`Restoring session for ${userId}`);
|
console.log(`Restoring session for ${userId}`);
|
||||||
await _doSetLoggedIn({
|
await doSetLoggedIn({
|
||||||
userId: userId,
|
userId: userId,
|
||||||
deviceId: deviceId,
|
deviceId: deviceId,
|
||||||
accessToken: accessToken,
|
accessToken: accessToken,
|
||||||
|
@ -335,7 +357,7 @@ async function _restoreFromLocalStorage(opts) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function _handleLoadSessionFailure(e) {
|
async function handleLoadSessionFailure(e: Error): Promise<boolean> {
|
||||||
console.error("Unable to load session", e);
|
console.error("Unable to load session", e);
|
||||||
|
|
||||||
const SessionRestoreErrorDialog =
|
const SessionRestoreErrorDialog =
|
||||||
|
@ -348,7 +370,7 @@ async function _handleLoadSessionFailure(e) {
|
||||||
const [success] = await modal.finished;
|
const [success] = await modal.finished;
|
||||||
if (success) {
|
if (success) {
|
||||||
// user clicked continue.
|
// user clicked continue.
|
||||||
await _clearStorage();
|
await clearStorage();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -369,7 +391,7 @@ async function _handleLoadSessionFailure(e) {
|
||||||
*
|
*
|
||||||
* @returns {Promise} promise which resolves to the new MatrixClient once it has been started
|
* @returns {Promise} promise which resolves to the new MatrixClient once it has been started
|
||||||
*/
|
*/
|
||||||
export async function setLoggedIn(credentials) {
|
export async function setLoggedIn(credentials: IMatrixClientCreds): Promise<MatrixClient> {
|
||||||
credentials.freshLogin = true;
|
credentials.freshLogin = true;
|
||||||
stopMatrixClient();
|
stopMatrixClient();
|
||||||
const pickleKey = credentials.userId && credentials.deviceId
|
const pickleKey = credentials.userId && credentials.deviceId
|
||||||
|
@ -382,7 +404,7 @@ export async function setLoggedIn(credentials) {
|
||||||
console.log("Pickle key not created");
|
console.log("Pickle key not created");
|
||||||
}
|
}
|
||||||
|
|
||||||
return _doSetLoggedIn(Object.assign({}, credentials, {pickleKey}), true);
|
return doSetLoggedIn(Object.assign({}, credentials, {pickleKey}), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -400,7 +422,7 @@ export async function setLoggedIn(credentials) {
|
||||||
*
|
*
|
||||||
* @returns {Promise} promise which resolves to the new MatrixClient once it has been started
|
* @returns {Promise} promise which resolves to the new MatrixClient once it has been started
|
||||||
*/
|
*/
|
||||||
export function hydrateSession(credentials) {
|
export function hydrateSession(credentials: IMatrixClientCreds): Promise<MatrixClient> {
|
||||||
const oldUserId = MatrixClientPeg.get().getUserId();
|
const oldUserId = MatrixClientPeg.get().getUserId();
|
||||||
const oldDeviceId = MatrixClientPeg.get().getDeviceId();
|
const oldDeviceId = MatrixClientPeg.get().getDeviceId();
|
||||||
|
|
||||||
|
@ -413,7 +435,7 @@ export function hydrateSession(credentials) {
|
||||||
console.warn("Clearing all data: Old session belongs to a different user/session");
|
console.warn("Clearing all data: Old session belongs to a different user/session");
|
||||||
}
|
}
|
||||||
|
|
||||||
return _doSetLoggedIn(credentials, overwrite);
|
return doSetLoggedIn(credentials, overwrite);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -425,7 +447,10 @@ export function hydrateSession(credentials) {
|
||||||
*
|
*
|
||||||
* @returns {Promise} promise which resolves to the new MatrixClient once it has been started
|
* @returns {Promise} promise which resolves to the new MatrixClient once it has been started
|
||||||
*/
|
*/
|
||||||
async function _doSetLoggedIn(credentials, clearStorage) {
|
async function doSetLoggedIn(
|
||||||
|
credentials: IMatrixClientCreds,
|
||||||
|
clearStorageEnabled: boolean,
|
||||||
|
): Promise<MatrixClient> {
|
||||||
credentials.guest = Boolean(credentials.guest);
|
credentials.guest = Boolean(credentials.guest);
|
||||||
|
|
||||||
const softLogout = isSoftLogout();
|
const softLogout = isSoftLogout();
|
||||||
|
@ -448,8 +473,8 @@ async function _doSetLoggedIn(credentials, clearStorage) {
|
||||||
// (dis.dispatch uses `setTimeout`, which does not guarantee ordering.)
|
// (dis.dispatch uses `setTimeout`, which does not guarantee ordering.)
|
||||||
dis.dispatch({action: 'on_logging_in'}, true);
|
dis.dispatch({action: 'on_logging_in'}, true);
|
||||||
|
|
||||||
if (clearStorage) {
|
if (clearStorageEnabled) {
|
||||||
await _clearStorage();
|
await clearStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
const results = await StorageManager.checkConsistency();
|
const results = await StorageManager.checkConsistency();
|
||||||
|
@ -457,9 +482,9 @@ async function _doSetLoggedIn(credentials, clearStorage) {
|
||||||
// crypto store, we'll be generally confused when handling encrypted data.
|
// crypto store, we'll be generally confused when handling encrypted data.
|
||||||
// Show a modal recommending a full reset of storage.
|
// Show a modal recommending a full reset of storage.
|
||||||
if (results.dataInLocalStorage && results.cryptoInited && !results.dataInCryptoStore) {
|
if (results.dataInLocalStorage && results.cryptoInited && !results.dataInCryptoStore) {
|
||||||
const signOut = await _showStorageEvictedDialog();
|
const signOut = await showStorageEvictedDialog();
|
||||||
if (signOut) {
|
if (signOut) {
|
||||||
await _clearStorage();
|
await clearStorage();
|
||||||
// This error feels a bit clunky, but we want to make sure we don't go any
|
// This error feels a bit clunky, but we want to make sure we don't go any
|
||||||
// further and instead head back to sign in.
|
// further and instead head back to sign in.
|
||||||
throw new AbortLoginAndRebuildStorage(
|
throw new AbortLoginAndRebuildStorage(
|
||||||
|
@ -487,20 +512,9 @@ async function _doSetLoggedIn(credentials, clearStorage) {
|
||||||
|
|
||||||
if (localStorage) {
|
if (localStorage) {
|
||||||
try {
|
try {
|
||||||
_persistCredentialsToLocalStorage(credentials);
|
persistCredentialsToLocalStorage(credentials);
|
||||||
|
|
||||||
// make sure we don't think that it's a fresh login any more
|
// make sure we don't think that it's a fresh login any more
|
||||||
sessionStorage.removeItem("mx_fresh_login");
|
sessionStorage.removeItem("mx_fresh_login");
|
||||||
|
|
||||||
// The user registered as a PWLU (PassWord-Less User), the generated password
|
|
||||||
// is cached here such that the user can change it at a later time.
|
|
||||||
if (credentials.password) {
|
|
||||||
// Update SessionStore
|
|
||||||
dis.dispatch({
|
|
||||||
action: 'cached_password',
|
|
||||||
cachedPassword: credentials.password,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("Error using local storage: can't persist session!", e);
|
console.warn("Error using local storage: can't persist session!", e);
|
||||||
}
|
}
|
||||||
|
@ -514,7 +528,7 @@ async function _doSetLoggedIn(credentials, clearStorage) {
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _showStorageEvictedDialog() {
|
function showStorageEvictedDialog(): Promise<boolean> {
|
||||||
const StorageEvictedDialog = sdk.getComponent('views.dialogs.StorageEvictedDialog');
|
const StorageEvictedDialog = sdk.getComponent('views.dialogs.StorageEvictedDialog');
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
Modal.createTrackedDialog('Storage evicted', '', StorageEvictedDialog, {
|
Modal.createTrackedDialog('Storage evicted', '', StorageEvictedDialog, {
|
||||||
|
@ -527,7 +541,7 @@ function _showStorageEvictedDialog() {
|
||||||
// `instanceof`. Babel 7 supports this natively in their class handling.
|
// `instanceof`. Babel 7 supports this natively in their class handling.
|
||||||
class AbortLoginAndRebuildStorage extends Error { }
|
class AbortLoginAndRebuildStorage extends Error { }
|
||||||
|
|
||||||
function _persistCredentialsToLocalStorage(credentials) {
|
function persistCredentialsToLocalStorage(credentials: IMatrixClientCreds): void {
|
||||||
localStorage.setItem(HOMESERVER_URL_KEY, credentials.homeserverUrl);
|
localStorage.setItem(HOMESERVER_URL_KEY, credentials.homeserverUrl);
|
||||||
if (credentials.identityServerUrl) {
|
if (credentials.identityServerUrl) {
|
||||||
localStorage.setItem(ID_SERVER_URL_KEY, credentials.identityServerUrl);
|
localStorage.setItem(ID_SERVER_URL_KEY, credentials.identityServerUrl);
|
||||||
|
@ -537,7 +551,7 @@ function _persistCredentialsToLocalStorage(credentials) {
|
||||||
localStorage.setItem("mx_is_guest", JSON.stringify(credentials.guest));
|
localStorage.setItem("mx_is_guest", JSON.stringify(credentials.guest));
|
||||||
|
|
||||||
if (credentials.pickleKey) {
|
if (credentials.pickleKey) {
|
||||||
localStorage.setItem("mx_has_pickle_key", true);
|
localStorage.setItem("mx_has_pickle_key", String(true));
|
||||||
} else {
|
} else {
|
||||||
if (localStorage.getItem("mx_has_pickle_key")) {
|
if (localStorage.getItem("mx_has_pickle_key")) {
|
||||||
console.error("Expected a pickle key, but none provided. Encryption may not work.");
|
console.error("Expected a pickle key, but none provided. Encryption may not work.");
|
||||||
|
@ -561,7 +575,7 @@ let _isLoggingOut = false;
|
||||||
/**
|
/**
|
||||||
* Logs the current session out and transitions to the logged-out state
|
* Logs the current session out and transitions to the logged-out state
|
||||||
*/
|
*/
|
||||||
export function logout() {
|
export function logout(): void {
|
||||||
if (!MatrixClientPeg.get()) return;
|
if (!MatrixClientPeg.get()) return;
|
||||||
|
|
||||||
if (MatrixClientPeg.get().isGuest()) {
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
|
@ -590,7 +604,7 @@ export function logout() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function softLogout() {
|
export function softLogout(): void {
|
||||||
if (!MatrixClientPeg.get()) return;
|
if (!MatrixClientPeg.get()) return;
|
||||||
|
|
||||||
// Track that we've detected and trapped a soft logout. This helps prevent other
|
// Track that we've detected and trapped a soft logout. This helps prevent other
|
||||||
|
@ -611,11 +625,11 @@ export function softLogout() {
|
||||||
// DO NOT CALL LOGOUT. A soft logout preserves data, logout does not.
|
// DO NOT CALL LOGOUT. A soft logout preserves data, logout does not.
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isSoftLogout() {
|
export function isSoftLogout(): boolean {
|
||||||
return localStorage.getItem("mx_soft_logout") === "true";
|
return localStorage.getItem("mx_soft_logout") === "true";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isLoggingOut() {
|
export function isLoggingOut(): boolean {
|
||||||
return _isLoggingOut;
|
return _isLoggingOut;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -625,7 +639,7 @@ export function isLoggingOut() {
|
||||||
* @param {boolean} startSyncing True (default) to actually start
|
* @param {boolean} startSyncing True (default) to actually start
|
||||||
* syncing the client.
|
* syncing the client.
|
||||||
*/
|
*/
|
||||||
async function startMatrixClient(startSyncing=true) {
|
async function startMatrixClient(startSyncing = true): Promise<void> {
|
||||||
console.log(`Lifecycle: Starting MatrixClient`);
|
console.log(`Lifecycle: Starting MatrixClient`);
|
||||||
|
|
||||||
// dispatch this before starting the matrix client: it's used
|
// dispatch this before starting the matrix client: it's used
|
||||||
|
@ -684,21 +698,21 @@ async function startMatrixClient(startSyncing=true) {
|
||||||
* Stops a running client and all related services, and clears persistent
|
* Stops a running client and all related services, and clears persistent
|
||||||
* storage. Used after a session has been logged out.
|
* storage. Used after a session has been logged out.
|
||||||
*/
|
*/
|
||||||
export async function onLoggedOut() {
|
export async function onLoggedOut(): Promise<void> {
|
||||||
_isLoggingOut = false;
|
_isLoggingOut = false;
|
||||||
// Ensure that we dispatch a view change **before** stopping the client so
|
// Ensure that we dispatch a view change **before** stopping the client so
|
||||||
// so that React components unmount first. This avoids React soft crashes
|
// so that React components unmount first. This avoids React soft crashes
|
||||||
// that can occur when components try to use a null client.
|
// that can occur when components try to use a null client.
|
||||||
dis.dispatch({action: 'on_logged_out'}, true);
|
dis.dispatch({action: 'on_logged_out'}, true);
|
||||||
stopMatrixClient();
|
stopMatrixClient();
|
||||||
await _clearStorage({deleteEverything: true});
|
await clearStorage({deleteEverything: true});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} opts Options for how to clear storage.
|
* @param {object} opts Options for how to clear storage.
|
||||||
* @returns {Promise} promise which resolves once the stores have been cleared
|
* @returns {Promise} promise which resolves once the stores have been cleared
|
||||||
*/
|
*/
|
||||||
async function _clearStorage(opts: {deleteEverything: boolean}) {
|
async function clearStorage(opts?: { deleteEverything?: boolean }): Promise<void> {
|
||||||
Analytics.disable();
|
Analytics.disable();
|
||||||
|
|
||||||
if (window.localStorage) {
|
if (window.localStorage) {
|
||||||
|
@ -736,7 +750,7 @@ async function _clearStorage(opts: {deleteEverything: boolean}) {
|
||||||
* @param {boolean} unsetClient True (default) to abandon the client
|
* @param {boolean} unsetClient True (default) to abandon the client
|
||||||
* on MatrixClientPeg after stopping.
|
* on MatrixClientPeg after stopping.
|
||||||
*/
|
*/
|
||||||
export function stopMatrixClient(unsetClient=true) {
|
export function stopMatrixClient(unsetClient = true): void {
|
||||||
Notifier.stop();
|
Notifier.stop();
|
||||||
UserActivity.sharedInstance().stop();
|
UserActivity.sharedInstance().stop();
|
||||||
TypingStore.sharedInstance().reset();
|
TypingStore.sharedInstance().reset();
|
|
@ -18,35 +18,72 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// @ts-ignore - XXX: tsc doesn't like this: our js-sdk imports are complex so this isn't surprising
|
||||||
import Matrix from "matrix-js-sdk";
|
import Matrix from "matrix-js-sdk";
|
||||||
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
|
import { IMatrixClientCreds } from "./MatrixClientPeg";
|
||||||
|
|
||||||
|
interface ILoginOptions {
|
||||||
|
defaultDeviceDisplayName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Move this to JS SDK
|
||||||
|
interface ILoginFlow {
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Move this to JS SDK
|
||||||
|
/* eslint-disable camelcase */
|
||||||
|
interface ILoginParams {
|
||||||
|
identifier?: string;
|
||||||
|
password?: string;
|
||||||
|
token?: string;
|
||||||
|
device_id?: string;
|
||||||
|
initial_device_display_name?: string;
|
||||||
|
}
|
||||||
|
/* eslint-enable camelcase */
|
||||||
|
|
||||||
export default class Login {
|
export default class Login {
|
||||||
constructor(hsUrl, isUrl, fallbackHsUrl, opts) {
|
private hsUrl: string;
|
||||||
this._hsUrl = hsUrl;
|
private isUrl: string;
|
||||||
this._isUrl = isUrl;
|
private fallbackHsUrl: string;
|
||||||
this._fallbackHsUrl = fallbackHsUrl;
|
private currentFlowIndex: number;
|
||||||
this._currentFlowIndex = 0;
|
// TODO: Flows need a type in JS SDK
|
||||||
this._flows = [];
|
private flows: Array<ILoginFlow>;
|
||||||
this._defaultDeviceDisplayName = opts.defaultDeviceDisplayName;
|
private defaultDeviceDisplayName: string;
|
||||||
this._tempClient = null; // memoize
|
private tempClient: MatrixClient;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
hsUrl: string,
|
||||||
|
isUrl: string,
|
||||||
|
fallbackHsUrl?: string,
|
||||||
|
opts?: ILoginOptions,
|
||||||
|
) {
|
||||||
|
this.hsUrl = hsUrl;
|
||||||
|
this.isUrl = isUrl;
|
||||||
|
this.fallbackHsUrl = fallbackHsUrl;
|
||||||
|
this.currentFlowIndex = 0;
|
||||||
|
this.flows = [];
|
||||||
|
this.defaultDeviceDisplayName = opts.defaultDeviceDisplayName;
|
||||||
|
this.tempClient = null; // memoize
|
||||||
}
|
}
|
||||||
|
|
||||||
getHomeserverUrl() {
|
public getHomeserverUrl(): string {
|
||||||
return this._hsUrl;
|
return this.hsUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
getIdentityServerUrl() {
|
public getIdentityServerUrl(): string {
|
||||||
return this._isUrl;
|
return this.isUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
setHomeserverUrl(hsUrl) {
|
public setHomeserverUrl(hsUrl: string): void {
|
||||||
this._tempClient = null; // clear memoization
|
this.tempClient = null; // clear memoization
|
||||||
this._hsUrl = hsUrl;
|
this.hsUrl = hsUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIdentityServerUrl(isUrl) {
|
public setIdentityServerUrl(isUrl: string): void {
|
||||||
this._tempClient = null; // clear memoization
|
this.tempClient = null; // clear memoization
|
||||||
this._isUrl = isUrl;
|
this.isUrl = isUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -54,40 +91,41 @@ export default class Login {
|
||||||
* requests.
|
* requests.
|
||||||
* @returns {MatrixClient}
|
* @returns {MatrixClient}
|
||||||
*/
|
*/
|
||||||
createTemporaryClient() {
|
public createTemporaryClient(): MatrixClient {
|
||||||
if (this._tempClient) return this._tempClient; // use memoization
|
if (this.tempClient) return this.tempClient; // use memoization
|
||||||
return this._tempClient = Matrix.createClient({
|
return this.tempClient = Matrix.createClient({
|
||||||
baseUrl: this._hsUrl,
|
baseUrl: this.hsUrl,
|
||||||
idBaseUrl: this._isUrl,
|
idBaseUrl: this.isUrl,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getFlows() {
|
public async getFlows(): Promise<Array<ILoginFlow>> {
|
||||||
const self = this;
|
|
||||||
const client = this.createTemporaryClient();
|
const client = this.createTemporaryClient();
|
||||||
return client.loginFlows().then(function(result) {
|
const { flows } = await client.loginFlows();
|
||||||
self._flows = result.flows;
|
this.flows = flows;
|
||||||
self._currentFlowIndex = 0;
|
this.currentFlowIndex = 0;
|
||||||
// technically the UI should display options for all flows for the
|
// technically the UI should display options for all flows for the
|
||||||
// user to then choose one, so return all the flows here.
|
// user to then choose one, so return all the flows here.
|
||||||
return self._flows;
|
return this.flows;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
chooseFlow(flowIndex) {
|
public chooseFlow(flowIndex): void {
|
||||||
this._currentFlowIndex = flowIndex;
|
this.currentFlowIndex = flowIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentFlowStep() {
|
public getCurrentFlowStep(): string {
|
||||||
// technically the flow can have multiple steps, but no one does this
|
// technically the flow can have multiple steps, but no one does this
|
||||||
// for login so we can ignore it.
|
// for login so we can ignore it.
|
||||||
const flowStep = this._flows[this._currentFlowIndex];
|
const flowStep = this.flows[this.currentFlowIndex];
|
||||||
return flowStep ? flowStep.type : null;
|
return flowStep ? flowStep.type : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
loginViaPassword(username, phoneCountry, phoneNumber, pass) {
|
public loginViaPassword(
|
||||||
const self = this;
|
username: string,
|
||||||
|
phoneCountry: string,
|
||||||
|
phoneNumber: string,
|
||||||
|
password: string,
|
||||||
|
): Promise<IMatrixClientCreds> {
|
||||||
const isEmail = username.indexOf("@") > 0;
|
const isEmail = username.indexOf("@") > 0;
|
||||||
|
|
||||||
let identifier;
|
let identifier;
|
||||||
|
@ -113,14 +151,14 @@ export default class Login {
|
||||||
}
|
}
|
||||||
|
|
||||||
const loginParams = {
|
const loginParams = {
|
||||||
password: pass,
|
password,
|
||||||
identifier: identifier,
|
identifier,
|
||||||
initial_device_display_name: this._defaultDeviceDisplayName,
|
initial_device_display_name: this.defaultDeviceDisplayName,
|
||||||
};
|
};
|
||||||
|
|
||||||
const tryFallbackHs = (originalError) => {
|
const tryFallbackHs = (originalError) => {
|
||||||
return sendLoginRequest(
|
return sendLoginRequest(
|
||||||
self._fallbackHsUrl, this._isUrl, 'm.login.password', loginParams,
|
this.fallbackHsUrl, this.isUrl, 'm.login.password', loginParams,
|
||||||
).catch((fallbackError) => {
|
).catch((fallbackError) => {
|
||||||
console.log("fallback HS login failed", fallbackError);
|
console.log("fallback HS login failed", fallbackError);
|
||||||
// throw the original error
|
// throw the original error
|
||||||
|
@ -130,11 +168,11 @@ export default class Login {
|
||||||
|
|
||||||
let originalLoginError = null;
|
let originalLoginError = null;
|
||||||
return sendLoginRequest(
|
return sendLoginRequest(
|
||||||
self._hsUrl, self._isUrl, 'm.login.password', loginParams,
|
this.hsUrl, this.isUrl, 'm.login.password', loginParams,
|
||||||
).catch((error) => {
|
).catch((error) => {
|
||||||
originalLoginError = error;
|
originalLoginError = error;
|
||||||
if (error.httpStatus === 403) {
|
if (error.httpStatus === 403) {
|
||||||
if (self._fallbackHsUrl) {
|
if (this.fallbackHsUrl) {
|
||||||
return tryFallbackHs(originalLoginError);
|
return tryFallbackHs(originalLoginError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,11 +192,16 @@ export default class Login {
|
||||||
* @param {string} hsUrl the base url of the Homeserver used to log in.
|
* @param {string} hsUrl the base url of the Homeserver used to log in.
|
||||||
* @param {string} isUrl the base url of the default identity server
|
* @param {string} isUrl the base url of the default identity server
|
||||||
* @param {string} loginType the type of login to do
|
* @param {string} loginType the type of login to do
|
||||||
* @param {object} loginParams the parameters for the login
|
* @param {ILoginParams} loginParams the parameters for the login
|
||||||
*
|
*
|
||||||
* @returns {MatrixClientCreds}
|
* @returns {MatrixClientCreds}
|
||||||
*/
|
*/
|
||||||
export async function sendLoginRequest(hsUrl, isUrl, loginType, loginParams) {
|
export async function sendLoginRequest(
|
||||||
|
hsUrl: string,
|
||||||
|
isUrl: string,
|
||||||
|
loginType: string,
|
||||||
|
loginParams: ILoginParams,
|
||||||
|
): Promise<IMatrixClientCreds> {
|
||||||
const client = Matrix.createClient({
|
const client = Matrix.createClient({
|
||||||
baseUrl: hsUrl,
|
baseUrl: hsUrl,
|
||||||
idBaseUrl: isUrl,
|
idBaseUrl: isUrl,
|
|
@ -38,9 +38,9 @@ export interface IMatrixClientCreds {
|
||||||
homeserverUrl: string;
|
homeserverUrl: string;
|
||||||
identityServerUrl: string;
|
identityServerUrl: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
deviceId: string;
|
deviceId?: string;
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
guest: boolean;
|
guest?: boolean;
|
||||||
pickleKey?: string;
|
pickleKey?: string;
|
||||||
freshLogin?: boolean;
|
freshLogin?: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,6 @@ import dis from './dispatcher/dispatcher';
|
||||||
import * as sdk from './index';
|
import * as sdk from './index';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
import { _t } from './languageHandler';
|
import { _t } from './languageHandler';
|
||||||
// import {MatrixClientPeg} from './MatrixClientPeg';
|
|
||||||
|
|
||||||
// Regex for what a "safe" or "Matrix-looking" localpart would be.
|
// Regex for what a "safe" or "Matrix-looking" localpart would be.
|
||||||
// TODO: Update as needed for https://github.com/matrix-org/matrix-doc/issues/1514
|
// TODO: Update as needed for https://github.com/matrix-org/matrix-doc/issues/1514
|
||||||
|
@ -44,29 +43,6 @@ export const SAFE_LOCALPART_REGEX = /^[a-z0-9=_\-./]+$/;
|
||||||
*/
|
*/
|
||||||
export async function startAnyRegistrationFlow(options) {
|
export async function startAnyRegistrationFlow(options) {
|
||||||
if (options === undefined) options = {};
|
if (options === undefined) options = {};
|
||||||
// look for an ILAG compatible flow. We define this as one
|
|
||||||
// which has only dummy or recaptcha flows. In practice it
|
|
||||||
// would support any stage InteractiveAuth supports, just not
|
|
||||||
// ones like email & msisdn which require the user to supply
|
|
||||||
// the relevant details in advance. We err on the side of
|
|
||||||
// caution though.
|
|
||||||
|
|
||||||
// XXX: ILAG is disabled for now,
|
|
||||||
// see https://github.com/vector-im/element-web/issues/8222
|
|
||||||
|
|
||||||
// const flows = await _getRegistrationFlows();
|
|
||||||
// const hasIlagFlow = flows.some((flow) => {
|
|
||||||
// return flow.stages.every((stage) => {
|
|
||||||
// return ['m.login.dummy', 'm.login.recaptcha', 'm.login.terms'].includes(stage);
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
|
|
||||||
// if (hasIlagFlow) {
|
|
||||||
// dis.dispatch({
|
|
||||||
// action: 'view_set_mxid',
|
|
||||||
// go_home_on_cancel: options.go_home_on_cancel,
|
|
||||||
// });
|
|
||||||
//} else {
|
|
||||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
const modal = Modal.createTrackedDialog('Registration required', '', QuestionDialog, {
|
const modal = Modal.createTrackedDialog('Registration required', '', QuestionDialog, {
|
||||||
hasCancelButton: true,
|
hasCancelButton: true,
|
||||||
|
@ -90,24 +66,4 @@ export async function startAnyRegistrationFlow(options) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// async function _getRegistrationFlows() {
|
|
||||||
// try {
|
|
||||||
// await MatrixClientPeg.get().register(
|
|
||||||
// null,
|
|
||||||
// null,
|
|
||||||
// undefined,
|
|
||||||
// {},
|
|
||||||
// {},
|
|
||||||
// );
|
|
||||||
// console.log("Register request succeeded when it should have returned 401!");
|
|
||||||
// } catch (e) {
|
|
||||||
// if (e.httpStatus === 401) {
|
|
||||||
// return e.data.flows;
|
|
||||||
// }
|
|
||||||
// throw e;
|
|
||||||
// }
|
|
||||||
// throw new Error("Register request succeeded when it should have returned 401!");
|
|
||||||
// }
|
|
||||||
|
|
|
@ -27,7 +27,6 @@ import CallMediaHandler from '../../CallMediaHandler';
|
||||||
import { fixupColorFonts } from '../../utils/FontManager';
|
import { fixupColorFonts } from '../../utils/FontManager';
|
||||||
import * as sdk from '../../index';
|
import * as sdk from '../../index';
|
||||||
import dis from '../../dispatcher/dispatcher';
|
import dis from '../../dispatcher/dispatcher';
|
||||||
import sessionStore from '../../stores/SessionStore';
|
|
||||||
import {MatrixClientPeg, IMatrixClientCreds} from '../../MatrixClientPeg';
|
import {MatrixClientPeg, IMatrixClientCreds} from '../../MatrixClientPeg';
|
||||||
import SettingsStore from "../../settings/SettingsStore";
|
import SettingsStore from "../../settings/SettingsStore";
|
||||||
|
|
||||||
|
@ -41,10 +40,6 @@ import HomePage from "./HomePage";
|
||||||
import ResizeNotifier from "../../utils/ResizeNotifier";
|
import ResizeNotifier from "../../utils/ResizeNotifier";
|
||||||
import PlatformPeg from "../../PlatformPeg";
|
import PlatformPeg from "../../PlatformPeg";
|
||||||
import { DefaultTagID } from "../../stores/room-list/models";
|
import { DefaultTagID } from "../../stores/room-list/models";
|
||||||
import {
|
|
||||||
showToast as showSetPasswordToast,
|
|
||||||
hideToast as hideSetPasswordToast,
|
|
||||||
} from "../../toasts/SetPasswordToast";
|
|
||||||
import {
|
import {
|
||||||
showToast as showServerLimitToast,
|
showToast as showServerLimitToast,
|
||||||
hideToast as hideServerLimitToast,
|
hideToast as hideServerLimitToast,
|
||||||
|
@ -149,8 +144,6 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
protected readonly _matrixClient: MatrixClient;
|
protected readonly _matrixClient: MatrixClient;
|
||||||
protected readonly _roomView: React.RefObject<any>;
|
protected readonly _roomView: React.RefObject<any>;
|
||||||
protected readonly _resizeContainer: React.RefObject<ResizeHandle>;
|
protected readonly _resizeContainer: React.RefObject<ResizeHandle>;
|
||||||
protected readonly _sessionStore: sessionStore;
|
|
||||||
protected readonly _sessionStoreToken: { remove: () => void };
|
|
||||||
protected readonly _compactLayoutWatcherRef: string;
|
protected readonly _compactLayoutWatcherRef: string;
|
||||||
protected resizer: Resizer;
|
protected resizer: Resizer;
|
||||||
|
|
||||||
|
@ -171,12 +164,6 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
document.addEventListener('keydown', this._onNativeKeyDown, false);
|
document.addEventListener('keydown', this._onNativeKeyDown, false);
|
||||||
|
|
||||||
this._sessionStore = sessionStore;
|
|
||||||
this._sessionStoreToken = this._sessionStore.addListener(
|
|
||||||
this._setStateFromSessionStore,
|
|
||||||
);
|
|
||||||
this._setStateFromSessionStore();
|
|
||||||
|
|
||||||
this._updateServerNoticeEvents();
|
this._updateServerNoticeEvents();
|
||||||
|
|
||||||
this._matrixClient.on("accountData", this.onAccountData);
|
this._matrixClient.on("accountData", this.onAccountData);
|
||||||
|
@ -205,9 +192,6 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
this._matrixClient.removeListener("sync", this.onSync);
|
this._matrixClient.removeListener("sync", this.onSync);
|
||||||
this._matrixClient.removeListener("RoomState.events", this.onRoomStateEvents);
|
this._matrixClient.removeListener("RoomState.events", this.onRoomStateEvents);
|
||||||
SettingsStore.unwatchSetting(this._compactLayoutWatcherRef);
|
SettingsStore.unwatchSetting(this._compactLayoutWatcherRef);
|
||||||
if (this._sessionStoreToken) {
|
|
||||||
this._sessionStoreToken.remove();
|
|
||||||
}
|
|
||||||
this.resizer.detach();
|
this.resizer.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,14 +212,6 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
return this._roomView.current.canResetTimeline();
|
return this._roomView.current.canResetTimeline();
|
||||||
};
|
};
|
||||||
|
|
||||||
_setStateFromSessionStore = () => {
|
|
||||||
if (this._sessionStore.getCachedPassword()) {
|
|
||||||
showSetPasswordToast();
|
|
||||||
} else {
|
|
||||||
hideSetPasswordToast();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
_createResizer() {
|
_createResizer() {
|
||||||
const classNames = {
|
const classNames = {
|
||||||
handle: "mx_ResizeHandle",
|
handle: "mx_ResizeHandle",
|
||||||
|
|
|
@ -30,7 +30,7 @@ import 'what-input';
|
||||||
|
|
||||||
import Analytics from "../../Analytics";
|
import Analytics from "../../Analytics";
|
||||||
import { DecryptionFailureTracker } from "../../DecryptionFailureTracker";
|
import { DecryptionFailureTracker } from "../../DecryptionFailureTracker";
|
||||||
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
import { MatrixClientPeg, IMatrixClientCreds } from "../../MatrixClientPeg";
|
||||||
import PlatformPeg from "../../PlatformPeg";
|
import PlatformPeg from "../../PlatformPeg";
|
||||||
import SdkConfig from "../../SdkConfig";
|
import SdkConfig from "../../SdkConfig";
|
||||||
import * as RoomListSorter from "../../RoomListSorter";
|
import * as RoomListSorter from "../../RoomListSorter";
|
||||||
|
@ -290,7 +290,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
// When the session loads it'll be detected as soft logged out and a dispatch
|
// When the session loads it'll be detected as soft logged out and a dispatch
|
||||||
// will be sent out to say that, triggering this MatrixChat to show the soft
|
// will be sent out to say that, triggering this MatrixChat to show the soft
|
||||||
// logout page.
|
// logout page.
|
||||||
Lifecycle.loadSession({});
|
Lifecycle.loadSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.accountPassword = null;
|
this.accountPassword = null;
|
||||||
|
@ -670,9 +670,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
case 'view_home_page':
|
case 'view_home_page':
|
||||||
this.viewHome();
|
this.viewHome();
|
||||||
break;
|
break;
|
||||||
case 'view_set_mxid':
|
|
||||||
this.setMxId(payload);
|
|
||||||
break;
|
|
||||||
case 'view_start_chat_or_reuse':
|
case 'view_start_chat_or_reuse':
|
||||||
this.chatCreateOrReuse(payload.user_id);
|
this.chatCreateOrReuse(payload.user_id);
|
||||||
break;
|
break;
|
||||||
|
@ -985,36 +982,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private setMxId(payload) {
|
|
||||||
const SetMxIdDialog = sdk.getComponent('views.dialogs.SetMxIdDialog');
|
|
||||||
const close = Modal.createTrackedDialog('Set MXID', '', SetMxIdDialog, {
|
|
||||||
homeserverUrl: MatrixClientPeg.get().getHomeserverUrl(),
|
|
||||||
onFinished: (submitted, credentials) => {
|
|
||||||
if (!submitted) {
|
|
||||||
dis.dispatch({
|
|
||||||
action: 'cancel_after_sync_prepared',
|
|
||||||
});
|
|
||||||
if (payload.go_home_on_cancel) {
|
|
||||||
dis.dispatch({
|
|
||||||
action: 'view_home_page',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
MatrixClientPeg.setJustRegisteredUserId(credentials.user_id);
|
|
||||||
this.onRegistered(credentials);
|
|
||||||
},
|
|
||||||
onDifferentServerClicked: (ev) => {
|
|
||||||
dis.dispatch({action: 'start_registration'});
|
|
||||||
close();
|
|
||||||
},
|
|
||||||
onLoginClick: (ev) => {
|
|
||||||
dis.dispatch({action: 'start_login'});
|
|
||||||
close();
|
|
||||||
},
|
|
||||||
}).close;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async createRoom(defaultPublic = false) {
|
private async createRoom(defaultPublic = false) {
|
||||||
const communityId = CommunityPrototypeStore.instance.getSelectedCommunityId();
|
const communityId = CommunityPrototypeStore.instance.getSelectedCommunityId();
|
||||||
if (communityId) {
|
if (communityId) {
|
||||||
|
@ -1814,12 +1781,12 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
this.showScreen("forgot_password");
|
this.showScreen("forgot_password");
|
||||||
};
|
};
|
||||||
|
|
||||||
onRegisterFlowComplete = (credentials: object, password: string) => {
|
onRegisterFlowComplete = (credentials: IMatrixClientCreds, password: string) => {
|
||||||
return this.onUserCompletedLoginFlow(credentials, password);
|
return this.onUserCompletedLoginFlow(credentials, password);
|
||||||
};
|
};
|
||||||
|
|
||||||
// returns a promise which resolves to the new MatrixClient
|
// returns a promise which resolves to the new MatrixClient
|
||||||
onRegistered(credentials: object) {
|
onRegistered(credentials: IMatrixClientCreds) {
|
||||||
return Lifecycle.setLoggedIn(credentials);
|
return Lifecycle.setLoggedIn(credentials);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1905,7 +1872,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
* Note: SSO users (and any others using token login) currently do not pass through
|
* Note: SSO users (and any others using token login) currently do not pass through
|
||||||
* this, as they instead jump straight into the app after `attemptTokenLogin`.
|
* this, as they instead jump straight into the app after `attemptTokenLogin`.
|
||||||
*/
|
*/
|
||||||
onUserCompletedLoginFlow = async (credentials: object, password: string) => {
|
onUserCompletedLoginFlow = async (credentials: IMatrixClientCreds, password: string) => {
|
||||||
this.accountPassword = password;
|
this.accountPassword = password;
|
||||||
// self-destruct the password after 5mins
|
// self-destruct the password after 5mins
|
||||||
if (this.accountPasswordTimer !== null) clearTimeout(this.accountPasswordTimer);
|
if (this.accountPasswordTimer !== null) clearTimeout(this.accountPasswordTimer);
|
||||||
|
|
|
@ -1090,42 +1090,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
room_id: this.getRoomId(),
|
room_id: this.getRoomId(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Don't peek whilst registering otherwise getPendingEventList complains
|
|
||||||
// Do this by indicating our intention to join
|
|
||||||
|
|
||||||
// XXX: ILAG is disabled for now,
|
|
||||||
// see https://github.com/vector-im/element-web/issues/8222
|
|
||||||
dis.dispatch({action: 'require_registration'});
|
dis.dispatch({action: 'require_registration'});
|
||||||
// dis.dispatch({
|
|
||||||
// action: 'will_join',
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const SetMxIdDialog = sdk.getComponent('views.dialogs.SetMxIdDialog');
|
|
||||||
// const close = Modal.createTrackedDialog('Set MXID', '', SetMxIdDialog, {
|
|
||||||
// homeserverUrl: cli.getHomeserverUrl(),
|
|
||||||
// onFinished: (submitted, credentials) => {
|
|
||||||
// if (submitted) {
|
|
||||||
// this.props.onRegistered(credentials);
|
|
||||||
// } else {
|
|
||||||
// dis.dispatch({
|
|
||||||
// action: 'cancel_after_sync_prepared',
|
|
||||||
// });
|
|
||||||
// dis.dispatch({
|
|
||||||
// action: 'cancel_join',
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// onDifferentServerClicked: (ev) => {
|
|
||||||
// dis.dispatch({action: 'start_registration'});
|
|
||||||
// close();
|
|
||||||
// },
|
|
||||||
// onLoginClick: (ev) => {
|
|
||||||
// dis.dispatch({action: 'start_login'});
|
|
||||||
// close();
|
|
||||||
// },
|
|
||||||
// }).close;
|
|
||||||
// return;
|
|
||||||
} else {
|
} else {
|
||||||
Promise.resolve().then(() => {
|
Promise.resolve().then(() => {
|
||||||
const signUrl = this.props.threepidInvite?.signUrl;
|
const signUrl = this.props.threepidInvite?.signUrl;
|
||||||
|
|
|
@ -1,304 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2016 OpenMarket Ltd
|
|
||||||
Copyright 2017 Vector Creations 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, {createRef} from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import * as sdk from '../../../index';
|
|
||||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
|
||||||
import classnames from 'classnames';
|
|
||||||
import { Key } from '../../../Keyboard';
|
|
||||||
import { _t } from '../../../languageHandler';
|
|
||||||
import { SAFE_LOCALPART_REGEX } from '../../../Registration';
|
|
||||||
|
|
||||||
// The amount of time to wait for further changes to the input username before
|
|
||||||
// sending a request to the server
|
|
||||||
const USERNAME_CHECK_DEBOUNCE_MS = 250;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Prompt the user to set a display name.
|
|
||||||
*
|
|
||||||
* On success, `onFinished(true, newDisplayName)` is called.
|
|
||||||
*/
|
|
||||||
export default class SetMxIdDialog extends React.Component {
|
|
||||||
static propTypes = {
|
|
||||||
onFinished: PropTypes.func.isRequired,
|
|
||||||
// Called when the user requests to register with a different homeserver
|
|
||||||
onDifferentServerClicked: PropTypes.func.isRequired,
|
|
||||||
// Called if the user wants to switch to login instead
|
|
||||||
onLoginClick: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this._input_value = createRef();
|
|
||||||
this._uiAuth = createRef();
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
// The entered username
|
|
||||||
username: '',
|
|
||||||
// Indicate ongoing work on the username
|
|
||||||
usernameBusy: false,
|
|
||||||
// Indicate error with username
|
|
||||||
usernameError: '',
|
|
||||||
// Assume the homeserver supports username checking until "M_UNRECOGNIZED"
|
|
||||||
usernameCheckSupport: true,
|
|
||||||
|
|
||||||
// Whether the auth UI is currently being used
|
|
||||||
doingUIAuth: false,
|
|
||||||
// Indicate error with auth
|
|
||||||
authError: '',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this._input_value.current.select();
|
|
||||||
|
|
||||||
this._matrixClient = MatrixClientPeg.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
onValueChange = ev => {
|
|
||||||
this.setState({
|
|
||||||
username: ev.target.value,
|
|
||||||
usernameBusy: true,
|
|
||||||
usernameError: '',
|
|
||||||
}, () => {
|
|
||||||
if (!this.state.username || !this.state.usernameCheckSupport) {
|
|
||||||
this.setState({
|
|
||||||
usernameBusy: false,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debounce the username check to limit number of requests sent
|
|
||||||
if (this._usernameCheckTimeout) {
|
|
||||||
clearTimeout(this._usernameCheckTimeout);
|
|
||||||
}
|
|
||||||
this._usernameCheckTimeout = setTimeout(() => {
|
|
||||||
this._doUsernameCheck().finally(() => {
|
|
||||||
this.setState({
|
|
||||||
usernameBusy: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}, USERNAME_CHECK_DEBOUNCE_MS);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
onKeyUp = ev => {
|
|
||||||
if (ev.key === Key.ENTER) {
|
|
||||||
this.onSubmit();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onSubmit = ev => {
|
|
||||||
if (this._uiAuth.current) {
|
|
||||||
this._uiAuth.current.tryContinue();
|
|
||||||
}
|
|
||||||
this.setState({
|
|
||||||
doingUIAuth: true,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
_doUsernameCheck() {
|
|
||||||
// We do a quick check ahead of the username availability API to ensure the
|
|
||||||
// user ID roughly looks okay from a Matrix perspective.
|
|
||||||
if (!SAFE_LOCALPART_REGEX.test(this.state.username)) {
|
|
||||||
this.setState({
|
|
||||||
usernameError: _t("A username can only contain lower case letters, numbers and '=_-./'"),
|
|
||||||
});
|
|
||||||
return Promise.reject();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if username is available
|
|
||||||
return this._matrixClient.isUsernameAvailable(this.state.username).then(
|
|
||||||
(isAvailable) => {
|
|
||||||
if (isAvailable) {
|
|
||||||
this.setState({usernameError: ''});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
// Indicate whether the homeserver supports username checking
|
|
||||||
const newState = {
|
|
||||||
usernameCheckSupport: err.errcode !== "M_UNRECOGNIZED",
|
|
||||||
};
|
|
||||||
console.error('Error whilst checking username availability: ', err);
|
|
||||||
switch (err.errcode) {
|
|
||||||
case "M_USER_IN_USE":
|
|
||||||
newState.usernameError = _t('Username not available');
|
|
||||||
break;
|
|
||||||
case "M_INVALID_USERNAME":
|
|
||||||
newState.usernameError = _t(
|
|
||||||
'Username invalid: %(errMessage)s',
|
|
||||||
{ errMessage: err.message},
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "M_UNRECOGNIZED":
|
|
||||||
// This homeserver doesn't support username checking, assume it's
|
|
||||||
// fine and rely on the error appearing in registration step.
|
|
||||||
newState.usernameError = '';
|
|
||||||
break;
|
|
||||||
case undefined:
|
|
||||||
newState.usernameError = _t('Something went wrong!');
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
newState.usernameError = _t(
|
|
||||||
'An error occurred: %(error_string)s',
|
|
||||||
{ error_string: err.message },
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
this.setState(newState);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_generatePassword() {
|
|
||||||
return Math.random().toString(36).slice(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
_makeRegisterRequest = auth => {
|
|
||||||
// Not upgrading - changing mxids
|
|
||||||
const guestAccessToken = null;
|
|
||||||
if (!this._generatedPassword) {
|
|
||||||
this._generatedPassword = this._generatePassword();
|
|
||||||
}
|
|
||||||
return this._matrixClient.register(
|
|
||||||
this.state.username,
|
|
||||||
this._generatedPassword,
|
|
||||||
undefined, // session id: included in the auth dict already
|
|
||||||
auth,
|
|
||||||
{},
|
|
||||||
guestAccessToken,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
_onUIAuthFinished = (success, response) => {
|
|
||||||
this.setState({
|
|
||||||
doingUIAuth: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!success) {
|
|
||||||
this.setState({ authError: response.message });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.props.onFinished(true, {
|
|
||||||
userId: response.user_id,
|
|
||||||
deviceId: response.device_id,
|
|
||||||
homeserverUrl: this._matrixClient.getHomeserverUrl(),
|
|
||||||
identityServerUrl: this._matrixClient.getIdentityServerUrl(),
|
|
||||||
accessToken: response.access_token,
|
|
||||||
password: this._generatedPassword,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
|
||||||
const InteractiveAuth = sdk.getComponent('structures.InteractiveAuth');
|
|
||||||
|
|
||||||
let auth;
|
|
||||||
if (this.state.doingUIAuth) {
|
|
||||||
auth = <InteractiveAuth
|
|
||||||
matrixClient={this._matrixClient}
|
|
||||||
makeRequest={this._makeRegisterRequest}
|
|
||||||
onAuthFinished={this._onUIAuthFinished}
|
|
||||||
inputs={{}}
|
|
||||||
poll={true}
|
|
||||||
ref={this._uiAuth}
|
|
||||||
continueIsManaged={true}
|
|
||||||
/>;
|
|
||||||
}
|
|
||||||
const inputClasses = classnames({
|
|
||||||
"mx_SetMxIdDialog_input": true,
|
|
||||||
"error": Boolean(this.state.usernameError),
|
|
||||||
});
|
|
||||||
|
|
||||||
let usernameIndicator = null;
|
|
||||||
if (this.state.usernameBusy) {
|
|
||||||
usernameIndicator = <div>{_t("Checking...")}</div>;
|
|
||||||
} else {
|
|
||||||
const usernameAvailable = this.state.username &&
|
|
||||||
this.state.usernameCheckSupport && !this.state.usernameError;
|
|
||||||
const usernameIndicatorClasses = classnames({
|
|
||||||
"error": Boolean(this.state.usernameError),
|
|
||||||
"success": usernameAvailable,
|
|
||||||
});
|
|
||||||
usernameIndicator = <div className={usernameIndicatorClasses} role="alert">
|
|
||||||
{ usernameAvailable ? _t('Username available') : this.state.usernameError }
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
let authErrorIndicator = null;
|
|
||||||
if (this.state.authError) {
|
|
||||||
authErrorIndicator = <div className="error" role="alert">
|
|
||||||
{ this.state.authError }
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
const canContinue = this.state.username &&
|
|
||||||
!this.state.usernameError &&
|
|
||||||
!this.state.usernameBusy;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BaseDialog className="mx_SetMxIdDialog"
|
|
||||||
onFinished={this.props.onFinished}
|
|
||||||
title={_t('To get started, please pick a username!')}
|
|
||||||
contentId='mx_Dialog_content'
|
|
||||||
>
|
|
||||||
<div className="mx_Dialog_content" id='mx_Dialog_content'>
|
|
||||||
<div className="mx_SetMxIdDialog_input_group">
|
|
||||||
<input type="text" ref={this._input_value} value={this.state.username}
|
|
||||||
autoFocus={true}
|
|
||||||
onChange={this.onValueChange}
|
|
||||||
onKeyUp={this.onKeyUp}
|
|
||||||
size="30"
|
|
||||||
className={inputClasses}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{ usernameIndicator }
|
|
||||||
<p>
|
|
||||||
{ _t(
|
|
||||||
'This will be your account name on the <span></span> ' +
|
|
||||||
'homeserver, or you can pick a <a>different server</a>.',
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
'span': <span>{ this.props.homeserverUrl }</span>,
|
|
||||||
'a': (sub) => <a href="#" onClick={this.props.onDifferentServerClicked}>{ sub }</a>,
|
|
||||||
},
|
|
||||||
) }
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{ _t(
|
|
||||||
'If you already have a Matrix account you can <a>log in</a> instead.',
|
|
||||||
{},
|
|
||||||
{ 'a': (sub) => <a href="#" onClick={this.props.onLoginClick}>{ sub }</a> },
|
|
||||||
) }
|
|
||||||
</p>
|
|
||||||
{ auth }
|
|
||||||
{ authErrorIndicator }
|
|
||||||
</div>
|
|
||||||
<div className="mx_Dialog_buttons">
|
|
||||||
<input className="mx_Dialog_primary"
|
|
||||||
type="submit"
|
|
||||||
value={_t("Continue")}
|
|
||||||
onClick={this.onSubmit}
|
|
||||||
disabled={!canContinue}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</BaseDialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,130 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 Vector Creations Ltd
|
|
||||||
Copyright 2018 New Vector Ltd
|
|
||||||
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
|
||||||
|
|
||||||
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 * as sdk from '../../../index';
|
|
||||||
import { _t } from '../../../languageHandler';
|
|
||||||
import Modal from '../../../Modal';
|
|
||||||
|
|
||||||
const WarmFuzzy = function(props) {
|
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
|
||||||
let title = _t('You have successfully set a password!');
|
|
||||||
if (props.didSetEmail) {
|
|
||||||
title = _t('You have successfully set a password and an email address!');
|
|
||||||
}
|
|
||||||
const advice = _t('You can now return to your account after signing out, and sign in on other devices.');
|
|
||||||
let extraAdvice = null;
|
|
||||||
if (!props.didSetEmail) {
|
|
||||||
extraAdvice = _t('Remember, you can always set an email address in user settings if you change your mind.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return <BaseDialog className="mx_SetPasswordDialog"
|
|
||||||
onFinished={props.onFinished}
|
|
||||||
title={ title }
|
|
||||||
>
|
|
||||||
<div className="mx_Dialog_content">
|
|
||||||
<p>
|
|
||||||
{ advice }
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{ extraAdvice }
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="mx_Dialog_buttons">
|
|
||||||
<button
|
|
||||||
className="mx_Dialog_primary"
|
|
||||||
autoFocus={true}
|
|
||||||
onClick={props.onFinished}>
|
|
||||||
{ _t('Continue') }
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</BaseDialog>;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prompt the user to set a password
|
|
||||||
*
|
|
||||||
* On success, `onFinished()` when finished
|
|
||||||
*/
|
|
||||||
export default class SetPasswordDialog extends React.Component {
|
|
||||||
static propTypes = {
|
|
||||||
onFinished: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
|
||||||
error: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
_onPasswordChanged = res => {
|
|
||||||
Modal.createDialog(WarmFuzzy, {
|
|
||||||
didSetEmail: res.didSetEmail,
|
|
||||||
onFinished: () => {
|
|
||||||
this.props.onFinished();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
_onPasswordChangeError = err => {
|
|
||||||
let errMsg = err.error || "";
|
|
||||||
if (err.httpStatus === 403) {
|
|
||||||
errMsg = _t('Failed to change password. Is your password correct?');
|
|
||||||
} else if (err.httpStatus) {
|
|
||||||
errMsg += ' ' + _t(
|
|
||||||
'(HTTP status %(httpStatus)s)',
|
|
||||||
{ httpStatus: err.httpStatus },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
this.setState({
|
|
||||||
error: errMsg,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
|
||||||
const ChangePassword = sdk.getComponent('views.settings.ChangePassword');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BaseDialog className="mx_SetPasswordDialog"
|
|
||||||
onFinished={this.props.onFinished}
|
|
||||||
title={ _t('Please set a password!') }
|
|
||||||
>
|
|
||||||
<div className="mx_Dialog_content">
|
|
||||||
<p>
|
|
||||||
{ _t('This will allow you to return to your account after signing out, and sign in on other sessions.') }
|
|
||||||
</p>
|
|
||||||
<ChangePassword
|
|
||||||
className="mx_SetPasswordDialog_change_password"
|
|
||||||
rowClassName=""
|
|
||||||
buttonClassNames="mx_Dialog_primary mx_SetPasswordDialog_change_password_button"
|
|
||||||
buttonKind="primary"
|
|
||||||
confirm={false}
|
|
||||||
autoFocusNewPasswordInput={true}
|
|
||||||
shouldAskForEmail={true}
|
|
||||||
onError={this._onPasswordChangeError}
|
|
||||||
onFinished={this._onPasswordChanged}
|
|
||||||
buttonLabel={_t("Set Password")}
|
|
||||||
/>
|
|
||||||
<div className="error">
|
|
||||||
{ this.state.error }
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</BaseDialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -62,7 +62,8 @@ export default class AccessibleTooltipButton extends React.PureComponent<IToolti
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {title, tooltip, children, tooltipClassName, ...props} = this.props;
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const {title, tooltip, children, tooltipClassName, forceHide, ...props} = this.props;
|
||||||
|
|
||||||
const tip = this.state.hover ? <Tooltip
|
const tip = this.state.hover ? <Tooltip
|
||||||
className="mx_AccessibleTooltipButton_container"
|
className="mx_AccessibleTooltipButton_container"
|
||||||
|
|
|
@ -401,7 +401,8 @@ export default class TextualBody extends React.Component {
|
||||||
const mxEvent = this.props.mxEvent;
|
const mxEvent = this.props.mxEvent;
|
||||||
const content = mxEvent.getContent();
|
const content = mxEvent.getContent();
|
||||||
|
|
||||||
const stripReply = ReplyThread.getParentEventId(mxEvent);
|
// only strip reply if this is the original replying event, edits thereafter do not have the fallback
|
||||||
|
const stripReply = !mxEvent.replacingEvent() && ReplyThread.getParentEventId(mxEvent);
|
||||||
let body = HtmlUtils.bodyToHtml(content, this.props.highlights, {
|
let body = HtmlUtils.bodyToHtml(content, this.props.highlights, {
|
||||||
disableBigEmoji: content.msgtype === "m.emote" || !SettingsStore.getValue('TextualBody.enableBigEmoji'),
|
disableBigEmoji: content.msgtype === "m.emote" || !SettingsStore.getValue('TextualBody.enableBigEmoji'),
|
||||||
// Part of Replies fallback support
|
// Part of Replies fallback support
|
||||||
|
|
|
@ -437,6 +437,7 @@ export default class MessageComposer extends React.Component {
|
||||||
const canEndConf = WidgetUtils.canUserModifyWidgets(this.props.room.roomId);
|
const canEndConf = WidgetUtils.canUserModifyWidgets(this.props.room.roomId);
|
||||||
controls.push(
|
controls.push(
|
||||||
<HangupButton
|
<HangupButton
|
||||||
|
key="controls_hangup"
|
||||||
roomId={this.props.room.roomId}
|
roomId={this.props.room.roomId}
|
||||||
isConference={true}
|
isConference={true}
|
||||||
canEndConference={canEndConf}
|
canEndConference={canEndConf}
|
||||||
|
|
|
@ -19,14 +19,12 @@ import Field from "../elements/Field";
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||||
import dis from "../../../dispatcher/dispatcher";
|
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
|
import Spinner from '../elements/Spinner';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import * as sdk from "../../../index";
|
import * as sdk from "../../../index";
|
||||||
import Modal from "../../../Modal";
|
import Modal from "../../../Modal";
|
||||||
|
|
||||||
import sessionStore from '../../../stores/SessionStore';
|
|
||||||
|
|
||||||
export default class ChangePassword extends React.Component {
|
export default class ChangePassword extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onFinished: PropTypes.func,
|
onFinished: PropTypes.func,
|
||||||
|
@ -66,33 +64,11 @@ export default class ChangePassword extends React.Component {
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
phase: ChangePassword.Phases.Edit,
|
phase: ChangePassword.Phases.Edit,
|
||||||
cachedPassword: null,
|
|
||||||
oldPassword: "",
|
oldPassword: "",
|
||||||
newPassword: "",
|
newPassword: "",
|
||||||
newPasswordConfirm: "",
|
newPasswordConfirm: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this._sessionStore = sessionStore;
|
|
||||||
this._sessionStoreToken = this._sessionStore.addListener(
|
|
||||||
this._setStateFromSessionStore,
|
|
||||||
);
|
|
||||||
|
|
||||||
this._setStateFromSessionStore();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
if (this._sessionStoreToken) {
|
|
||||||
this._sessionStoreToken.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_setStateFromSessionStore = () => {
|
|
||||||
this.setState({
|
|
||||||
cachedPassword: this._sessionStore.getCachedPassword(),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
changePassword(oldPassword, newPassword) {
|
changePassword(oldPassword, newPassword) {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
|
|
||||||
|
@ -119,8 +95,11 @@ export default class ChangePassword extends React.Component {
|
||||||
</div>,
|
</div>,
|
||||||
button: _t("Continue"),
|
button: _t("Continue"),
|
||||||
extraButtons: [
|
extraButtons: [
|
||||||
<button className="mx_Dialog_primary"
|
<button
|
||||||
onClick={this._onExportE2eKeysClicked}>
|
key="exportRoomKeys"
|
||||||
|
className="mx_Dialog_primary"
|
||||||
|
onClick={this._onExportE2eKeysClicked}
|
||||||
|
>
|
||||||
{ _t('Export E2E room keys') }
|
{ _t('Export E2E room keys') }
|
||||||
</button>,
|
</button>,
|
||||||
],
|
],
|
||||||
|
@ -150,9 +129,6 @@ export default class ChangePassword extends React.Component {
|
||||||
});
|
});
|
||||||
|
|
||||||
cli.setPassword(authDict, newPassword).then(() => {
|
cli.setPassword(authDict, newPassword).then(() => {
|
||||||
// Notify SessionStore that the user's password was changed
|
|
||||||
dis.dispatch({action: 'password_changed'});
|
|
||||||
|
|
||||||
if (this.props.shouldAskForEmail) {
|
if (this.props.shouldAskForEmail) {
|
||||||
return this._optionallySetEmail().then((confirmed) => {
|
return this._optionallySetEmail().then((confirmed) => {
|
||||||
this.props.onFinished({
|
this.props.onFinished({
|
||||||
|
@ -212,7 +188,7 @@ export default class ChangePassword extends React.Component {
|
||||||
|
|
||||||
onClickChange = (ev) => {
|
onClickChange = (ev) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
const oldPassword = this.state.cachedPassword || this.state.oldPassword;
|
const oldPassword = this.state.oldPassword;
|
||||||
const newPassword = this.state.newPassword;
|
const newPassword = this.state.newPassword;
|
||||||
const confirmPassword = this.state.newPasswordConfirm;
|
const confirmPassword = this.state.newPasswordConfirm;
|
||||||
const err = this.props.onCheckPassword(
|
const err = this.props.onCheckPassword(
|
||||||
|
@ -231,9 +207,10 @@ export default class ChangePassword extends React.Component {
|
||||||
const rowClassName = this.props.rowClassName;
|
const rowClassName = this.props.rowClassName;
|
||||||
const buttonClassName = this.props.buttonClassName;
|
const buttonClassName = this.props.buttonClassName;
|
||||||
|
|
||||||
let currentPassword = null;
|
switch (this.state.phase) {
|
||||||
if (!this.state.cachedPassword) {
|
case ChangePassword.Phases.Edit:
|
||||||
currentPassword = (
|
return (
|
||||||
|
<form className={this.props.className} onSubmit={this.onClickChange}>
|
||||||
<div className={rowClassName}>
|
<div className={rowClassName}>
|
||||||
<Field
|
<Field
|
||||||
type="password"
|
type="password"
|
||||||
|
@ -242,20 +219,10 @@ export default class ChangePassword extends React.Component {
|
||||||
onChange={this.onChangeOldPassword}
|
onChange={this.onChangeOldPassword}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (this.state.phase) {
|
|
||||||
case ChangePassword.Phases.Edit:
|
|
||||||
const passwordLabel = this.state.cachedPassword ?
|
|
||||||
_t('Password') : _t('New Password');
|
|
||||||
return (
|
|
||||||
<form className={this.props.className} onSubmit={this.onClickChange}>
|
|
||||||
{ currentPassword }
|
|
||||||
<div className={rowClassName}>
|
<div className={rowClassName}>
|
||||||
<Field
|
<Field
|
||||||
type="password"
|
type="password"
|
||||||
label={passwordLabel}
|
label={_t('New Password')}
|
||||||
value={this.state.newPassword}
|
value={this.state.newPassword}
|
||||||
autoFocus={this.props.autoFocusNewPasswordInput}
|
autoFocus={this.props.autoFocusNewPasswordInput}
|
||||||
onChange={this.onChangeNewPassword}
|
onChange={this.onChangeNewPassword}
|
||||||
|
@ -277,10 +244,9 @@ export default class ChangePassword extends React.Component {
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
case ChangePassword.Phases.Uploading:
|
case ChangePassword.Phases.Uploading:
|
||||||
var Loader = sdk.getComponent("elements.Spinner");
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_Dialog_content">
|
<div className="mx_Dialog_content">
|
||||||
<Loader />
|
<Spinner />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
"Continue": "Fortfahren",
|
"Continue": "Fortfahren",
|
||||||
"Create Room": "Raum erstellen",
|
"Create Room": "Raum erstellen",
|
||||||
"Cryptography": "Verschlüsselung",
|
"Cryptography": "Verschlüsselung",
|
||||||
"Deactivate Account": "Benutzerkonto schließen",
|
"Deactivate Account": "Benutzerkonto deaktivieren",
|
||||||
"Failed to send email": "Fehler beim Senden der E-Mail",
|
"Failed to send email": "Fehler beim Senden der E-Mail",
|
||||||
"Account": "Benutzerkonto",
|
"Account": "Benutzerkonto",
|
||||||
"Click here to fix": "Zum reparieren hier klicken",
|
"Click here to fix": "Zum reparieren hier klicken",
|
||||||
|
@ -1322,7 +1322,7 @@
|
||||||
"Add Email Address": "E-Mail-Adresse hinzufügen",
|
"Add Email Address": "E-Mail-Adresse hinzufügen",
|
||||||
"Add Phone Number": "Telefonnummer hinzufügen",
|
"Add Phone Number": "Telefonnummer hinzufügen",
|
||||||
"Changes the avatar of the current room": "Ändert den Avatar für diesen Raum",
|
"Changes the avatar of the current room": "Ändert den Avatar für diesen Raum",
|
||||||
"Deactivate account": "Benutzerkonto schließen",
|
"Deactivate account": "Benutzerkonto deaktivieren",
|
||||||
"Show previews/thumbnails for images": "Zeige Vorschauen/Thumbnails für Bilder",
|
"Show previews/thumbnails for images": "Zeige Vorschauen/Thumbnails für Bilder",
|
||||||
"View": "Vorschau",
|
"View": "Vorschau",
|
||||||
"Find a room…": "Suche einen Raum…",
|
"Find a room…": "Suche einen Raum…",
|
||||||
|
@ -2504,5 +2504,20 @@
|
||||||
"Start a conversation with someone using their name or username (like <userId/>).": "Starte ein Gespräch unter Verwendung des Namen oder Benutzernamens des Gegenübers (z. B. <userId/>).",
|
"Start a conversation with someone using their name or username (like <userId/>).": "Starte ein Gespräch unter Verwendung des Namen oder Benutzernamens des Gegenübers (z. B. <userId/>).",
|
||||||
"This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click <a>here</a>": "Das wird sie nicht zu %(communityName)s einladen. Um jemand zu %(communityName)s einzuladen, klicke <a>hier</a>",
|
"This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click <a>here</a>": "Das wird sie nicht zu %(communityName)s einladen. Um jemand zu %(communityName)s einzuladen, klicke <a>hier</a>",
|
||||||
"Invite someone using their name, username (like <userId/>) or <a>share this room</a>.": "Lade jemand mittels seinem/ihrem Namen oder Benutzernamen (z.B. <userId/>) ein, oder <a>teile diesem Raum</a>.",
|
"Invite someone using their name, username (like <userId/>) or <a>share this room</a>.": "Lade jemand mittels seinem/ihrem Namen oder Benutzernamen (z.B. <userId/>) ein, oder <a>teile diesem Raum</a>.",
|
||||||
"Unable to set up keys": "Schlüssel können nicht eingerichtet werden"
|
"Unable to set up keys": "Schlüssel können nicht eingerichtet werden",
|
||||||
|
"Use the <a>Desktop app</a> to see all encrypted files": "Nutze die <a>Desktop-App</a> um alle verschlüsselten Dateien zu sehen",
|
||||||
|
"Use the <a>Desktop app</a> to search encrypted messages": "Nutze die <a>Desktop-App</a> um verschlüsselte Nachrichten zu suchen",
|
||||||
|
"This version of %(brand)s does not support viewing some encrypted files": "Diese Version von %(brand)s unterstützt nicht alle verschlüsselten Dateien anzuzeigen",
|
||||||
|
"This version of %(brand)s does not support searching encrypted messages": "Diese Version von %(brand)s unterstützt nicht verschlüsselte Nachrichten zu durchsuchen",
|
||||||
|
"Cannot create rooms in this community": "Räume können in dieser Community nicht erstellt werden",
|
||||||
|
"You do not have permission to create rooms in this community.": "Du bist nicht berechtigt Räume in dieser Community zu erstellen.",
|
||||||
|
"End conference": "Konferenzgespräch beenden",
|
||||||
|
"This will end the conference for everyone. Continue?": "Dies wird das Konferenzgespräch für alle beenden. Fortfahren?",
|
||||||
|
"Join the conference at the top of this room": "Konferenzgespräch oben in diesem Raum beitreten",
|
||||||
|
"Join the conference from the room information card on the right": "Konferenzgespräch in den Rauminformationen rechts beitreten",
|
||||||
|
"Video conference ended by %(senderName)s": "Videokonferenz von %(senderName)s beendet",
|
||||||
|
"Video conference updated by %(senderName)s": "Videokonferenz wurde von %(senderName)s aktualisiert",
|
||||||
|
"Video conference started by %(senderName)s": "Videokonferenz wurde von %(senderName)s gestartet",
|
||||||
|
"Ignored attempt to disable encryption": "Versuch, die Verschlüsselung zu deaktivieren, wurde ignoriert",
|
||||||
|
"Failed to save your profile": "Profil speichern fehlgeschlagen"
|
||||||
}
|
}
|
||||||
|
|
|
@ -401,9 +401,6 @@
|
||||||
"Contact your <a>server admin</a>.": "Contact your <a>server admin</a>.",
|
"Contact your <a>server admin</a>.": "Contact your <a>server admin</a>.",
|
||||||
"Warning": "Warning",
|
"Warning": "Warning",
|
||||||
"Ok": "Ok",
|
"Ok": "Ok",
|
||||||
"Set password": "Set password",
|
|
||||||
"To return to your account in future you need to set a password": "To return to your account in future you need to set a password",
|
|
||||||
"Set Password": "Set Password",
|
|
||||||
"Set up Secure Backup": "Set up Secure Backup",
|
"Set up Secure Backup": "Set up Secure Backup",
|
||||||
"Encryption upgrade available": "Encryption upgrade available",
|
"Encryption upgrade available": "Encryption upgrade available",
|
||||||
"Verify this session": "Verify this session",
|
"Verify this session": "Verify this session",
|
||||||
|
@ -636,7 +633,6 @@
|
||||||
"Export E2E room keys": "Export E2E room keys",
|
"Export E2E room keys": "Export E2E room keys",
|
||||||
"Do you want to set an email address?": "Do you want to set an email address?",
|
"Do you want to set an email address?": "Do you want to set an email address?",
|
||||||
"Current password": "Current password",
|
"Current password": "Current password",
|
||||||
"Password": "Password",
|
|
||||||
"New Password": "New Password",
|
"New Password": "New Password",
|
||||||
"Confirm password": "Confirm password",
|
"Confirm password": "Confirm password",
|
||||||
"Change Password": "Change Password",
|
"Change Password": "Change Password",
|
||||||
|
@ -1817,22 +1813,6 @@
|
||||||
"Verification Pending": "Verification Pending",
|
"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.",
|
"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.",
|
||||||
"This will allow you to reset your password and receive notifications.": "This will allow you to reset your password and receive notifications.",
|
"This will allow you to reset your password and receive notifications.": "This will allow you to reset your password and receive notifications.",
|
||||||
"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",
|
|
||||||
"An error occurred: %(error_string)s": "An error occurred: %(error_string)s",
|
|
||||||
"Checking...": "Checking...",
|
|
||||||
"Username available": "Username available",
|
|
||||||
"To get started, please pick a username!": "To get started, please pick a username!",
|
|
||||||
"This will be your account name on the <span></span> homeserver, or you can pick a <a>different server</a>.": "This will be your account name on the <span></span> homeserver, or you can pick a <a>different server</a>.",
|
|
||||||
"If you already have a Matrix account you can <a>log in</a> instead.": "If you already have a Matrix account you can <a>log in</a> instead.",
|
|
||||||
"You have successfully set a password!": "You have successfully set a password!",
|
|
||||||
"You have successfully set a password and an email address!": "You have successfully set a password and an email address!",
|
|
||||||
"You can now return to your account after signing out, and sign in on other devices.": "You can now return to your account after signing out, and sign in on other devices.",
|
|
||||||
"Remember, you can always set an email address in user settings if you change your mind.": "Remember, you can always set an email address in user settings if you change your mind.",
|
|
||||||
"(HTTP status %(httpStatus)s)": "(HTTP status %(httpStatus)s)",
|
|
||||||
"Please set a password!": "Please set a password!",
|
|
||||||
"This will allow you to return to your account after signing out, and sign in on other sessions.": "This will allow you to return to your account after signing out, and sign in on other sessions.",
|
|
||||||
"Share Room": "Share Room",
|
"Share Room": "Share Room",
|
||||||
"Link to most recent message": "Link to most recent message",
|
"Link to most recent message": "Link to most recent message",
|
||||||
"Share User": "Share User",
|
"Share User": "Share User",
|
||||||
|
@ -1946,6 +1926,7 @@
|
||||||
"Custom Server Options": "Custom Server Options",
|
"Custom Server Options": "Custom Server Options",
|
||||||
"You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use %(brand)s with an existing Matrix account on a different homeserver.": "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use %(brand)s with an existing Matrix account on a different homeserver.",
|
"You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use %(brand)s with an existing Matrix account on a different homeserver.": "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use %(brand)s with an existing Matrix account on a different homeserver.",
|
||||||
"Confirm your identity by entering your account password below.": "Confirm your identity by entering your account password below.",
|
"Confirm your identity by entering your account password below.": "Confirm your identity by entering your account password below.",
|
||||||
|
"Password": "Password",
|
||||||
"Missing captcha public key in homeserver configuration. Please report this to your homeserver administrator.": "Missing captcha public key in homeserver configuration. Please report this to your homeserver administrator.",
|
"Missing captcha public key in homeserver configuration. Please report this to your homeserver administrator.": "Missing captcha public key in homeserver configuration. Please report this to your homeserver administrator.",
|
||||||
"Please review and accept all of the homeserver's policies": "Please review and accept all of the homeserver's policies",
|
"Please review and accept all of the homeserver's policies": "Please review and accept all of the homeserver's policies",
|
||||||
"Please review and accept the policies of this homeserver:": "Please review and accept the policies of this homeserver:",
|
"Please review and accept the policies of this homeserver:": "Please review and accept the policies of this homeserver:",
|
||||||
|
|
|
@ -2512,5 +2512,17 @@
|
||||||
"This version of %(brand)s does not support viewing some encrypted files": "See %(brand)s versioon ei toeta mõnede krüptitud failide vaatatamist",
|
"This version of %(brand)s does not support viewing some encrypted files": "See %(brand)s versioon ei toeta mõnede krüptitud failide vaatatamist",
|
||||||
"This version of %(brand)s does not support searching encrypted messages": "See %(brand)s versioon ei toeta otsingut krüptitud sõnumite seast",
|
"This version of %(brand)s does not support searching encrypted messages": "See %(brand)s versioon ei toeta otsingut krüptitud sõnumite seast",
|
||||||
"Cannot create rooms in this community": "Siia kogukonda ei saa jututubasid luua",
|
"Cannot create rooms in this community": "Siia kogukonda ei saa jututubasid luua",
|
||||||
"You do not have permission to create rooms in this community.": "Sul pole õigusi luua siin kogukonnas uusi jututubasid."
|
"You do not have permission to create rooms in this community.": "Sul pole õigusi luua siin kogukonnas uusi jututubasid.",
|
||||||
|
"Join the conference at the top of this room": "Liitu konverentsiga selle jututoa ülaosas",
|
||||||
|
"Join the conference from the room information card on the right": "Liitu konverentsiga selle jututoa infolehelt paremal",
|
||||||
|
"Video conference ended by %(senderName)s": "%(senderName)s lõpetas video rühmakõne",
|
||||||
|
"Video conference updated by %(senderName)s": "%(senderName)s uuendas video rühmakõne",
|
||||||
|
"Video conference started by %(senderName)s": "%(senderName)s alustas video rühmakõne",
|
||||||
|
"End conference": "Lõpeta videokonverents",
|
||||||
|
"This will end the conference for everyone. Continue?": "Sellega lõpetame kõikide osalejate jaoks videokonverentsi. Nõus?",
|
||||||
|
"Ignored attempt to disable encryption": "Eirasin katset lõpetada krüptimise kasutamine",
|
||||||
|
"Offline encrypted messaging using dehydrated devices": "Võrguühenduseta kasutamiseks mõeldud krüptitud sõnumid dehydrated teenuse abil",
|
||||||
|
"Remove messages sent by others": "Kustuta teiste saadetud sõnumid",
|
||||||
|
"Failed to save your profile": "Sinu profiili salvestamine ei õnnestunud",
|
||||||
|
"The operation could not be completed": "Toimingut ei õnnestunud lõpetada"
|
||||||
}
|
}
|
||||||
|
|
|
@ -2512,5 +2512,16 @@
|
||||||
"This version of %(brand)s does not support viewing some encrypted files": "Esta versión de %(brand)s non soporta o visionado dalgúns ficheiros cifrados",
|
"This version of %(brand)s does not support viewing some encrypted files": "Esta versión de %(brand)s non soporta o visionado dalgúns ficheiros cifrados",
|
||||||
"This version of %(brand)s does not support searching encrypted messages": "Esta versión de %(brand)s non soporta a busca de mensaxes cifradas",
|
"This version of %(brand)s does not support searching encrypted messages": "Esta versión de %(brand)s non soporta a busca de mensaxes cifradas",
|
||||||
"Cannot create rooms in this community": "Non se poden crear salas nesta comunidade",
|
"Cannot create rooms in this community": "Non se poden crear salas nesta comunidade",
|
||||||
"You do not have permission to create rooms in this community.": "Non tes permiso para crear salas nesta comunidade."
|
"You do not have permission to create rooms in this community.": "Non tes permiso para crear salas nesta comunidade.",
|
||||||
|
"Join the conference at the top of this room": "Únete á conferencia na ligazón arriba nesta sala",
|
||||||
|
"Join the conference from the room information card on the right": "Únete á conferencia desde a tarxeta con información da sala á dereita",
|
||||||
|
"Video conference ended by %(senderName)s": "Video conferencia rematada por %(senderName)s",
|
||||||
|
"Video conference updated by %(senderName)s": "Video conferencia actualizada por %(senderName)s",
|
||||||
|
"Video conference started by %(senderName)s": "Video conferencia iniciada por %(senderName)s",
|
||||||
|
"End conference": "Rematar conferencia",
|
||||||
|
"This will end the conference for everyone. Continue?": "Así finalizarás a conferencia para todas. ¿Adiante?",
|
||||||
|
"Ignored attempt to disable encryption": "Intento ignorado de desactivar o cifrado",
|
||||||
|
"Failed to save your profile": "Non se gardaron os cambios",
|
||||||
|
"The operation could not be completed": "Non se puido realizar a acción",
|
||||||
|
"Remove messages sent by others": "Eliminar mensaxes enviadas por outras"
|
||||||
}
|
}
|
||||||
|
|
|
@ -2502,5 +2502,27 @@
|
||||||
"Minimize widget": "Widget minimalizálása",
|
"Minimize widget": "Widget minimalizálása",
|
||||||
"Maximize widget": "Widget maximalizálása",
|
"Maximize widget": "Widget maximalizálása",
|
||||||
"Your server requires encryption to be enabled in private rooms.": "A szervered megköveteli, hogy a titkosítás be legyen kapcsolva a privát szobákban.",
|
"Your server requires encryption to be enabled in private rooms.": "A szervered megköveteli, hogy a titkosítás be legyen kapcsolva a privát szobákban.",
|
||||||
"Unable to set up keys": "Nem sikerült a kulcsok beállítása"
|
"Unable to set up keys": "Nem sikerült a kulcsok beállítása",
|
||||||
|
"Safeguard against losing access to encrypted messages & data": "Biztosítás a titkosított üzenetek és adatokhoz való hozzáférés elvesztése ellen",
|
||||||
|
"not found in storage": "a tárban nem található",
|
||||||
|
"Widgets": "Kisalkalmazások",
|
||||||
|
"Edit widgets, bridges & bots": "Kisalkalmazások, hidak és botok szerkesztése",
|
||||||
|
"Use the <a>Desktop app</a> to see all encrypted files": "Minden titkosított fájl eléréséhez használd az <a>Asztali alkalmazást</a>",
|
||||||
|
"Use the <a>Desktop app</a> to search encrypted messages": "A titkosított üzenetek kereséséhez használd az <a>Asztali alkalmazást</a>",
|
||||||
|
"This version of %(brand)s does not support viewing some encrypted files": "%(brand)s ezen verziója nem minden titkosított fájl megjelenítését támogatja",
|
||||||
|
"This version of %(brand)s does not support searching encrypted messages": "%(brand)s ezen verziója nem támogatja a keresést a titkosított üzenetekben",
|
||||||
|
"Cannot create rooms in this community": "A közösségben nem lehet szobát készíteni",
|
||||||
|
"You do not have permission to create rooms in this community.": "A közösségben szoba létrehozásához nincs jogosultságod.",
|
||||||
|
"End conference": "Konferenciahívás befejezése",
|
||||||
|
"This will end the conference for everyone. Continue?": "Mindenki számára befejeződik a konferencia. Folytatod?",
|
||||||
|
"Offline encrypted messaging using dehydrated devices": "Kapcsolat nélküli titkosított üzenetküldés tartósított eszközökkel",
|
||||||
|
"Ignored attempt to disable encryption": "A titkosítás kikapcsolására tett kísérlet figyelmen kívül lett hagyva",
|
||||||
|
"Join the conference at the top of this room": "Csatlakozz a konferenciához a szoba tetején",
|
||||||
|
"Join the conference from the room information card on the right": "Csatlakozz a konferenciához a jobb oldali szoba információs panel segítségével",
|
||||||
|
"Video conference ended by %(senderName)s": "A videókonferenciát befejezte: %(senderName)s",
|
||||||
|
"Video conference updated by %(senderName)s": "A videókonferenciát frissítette: %(senderName)s",
|
||||||
|
"Video conference started by %(senderName)s": "A videókonferenciát elindította: %(senderName)s",
|
||||||
|
"Failed to save your profile": "A profilodat nem sikerült elmenteni",
|
||||||
|
"The operation could not be completed": "A műveletet nem lehetett befejezni",
|
||||||
|
"Remove messages sent by others": "Mások által küldött üzenetek törlése"
|
||||||
}
|
}
|
||||||
|
|
|
@ -2515,5 +2515,13 @@
|
||||||
"This version of %(brand)s does not support viewing some encrypted files": "Questa versione di %(brand)s non supporta la visualizzazione di alcuni file cifrati",
|
"This version of %(brand)s does not support viewing some encrypted files": "Questa versione di %(brand)s non supporta la visualizzazione di alcuni file cifrati",
|
||||||
"This version of %(brand)s does not support searching encrypted messages": "Questa versione di %(brand)s non supporta la ricerca di messaggi cifrati",
|
"This version of %(brand)s does not support searching encrypted messages": "Questa versione di %(brand)s non supporta la ricerca di messaggi cifrati",
|
||||||
"Cannot create rooms in this community": "Impossibile creare stanze in questa comunità",
|
"Cannot create rooms in this community": "Impossibile creare stanze in questa comunità",
|
||||||
"You do not have permission to create rooms in this community.": "Non hai i permessi per creare stanze in questa comunità."
|
"You do not have permission to create rooms in this community.": "Non hai i permessi per creare stanze in questa comunità.",
|
||||||
|
"Join the conference at the top of this room": "Entra nella conferenza in cima alla stanza",
|
||||||
|
"Join the conference from the room information card on the right": "Entra nella conferenza dalla scheda di informazione della stanza a destra",
|
||||||
|
"Video conference ended by %(senderName)s": "Conferenza video terminata da %(senderName)s",
|
||||||
|
"Video conference updated by %(senderName)s": "Conferenza video aggiornata da %(senderName)s",
|
||||||
|
"Video conference started by %(senderName)s": "Conferenza video iniziata da %(senderName)s",
|
||||||
|
"End conference": "Termina conferenza",
|
||||||
|
"This will end the conference for everyone. Continue?": "Verrà terminata la conferenza per tutti. Continuare?",
|
||||||
|
"Ignored attempt to disable encryption": "Tentativo di disattivare la crittografia ignorato"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1325,7 +1325,7 @@
|
||||||
"Invalid homeserver discovery response": "Неверный ответ при попытке обнаружения домашнего сервера",
|
"Invalid homeserver discovery response": "Неверный ответ при попытке обнаружения домашнего сервера",
|
||||||
"Failed to get autodiscovery configuration from server": "Не удалось получить конфигурацию автообнаружения с сервера",
|
"Failed to get autodiscovery configuration from server": "Не удалось получить конфигурацию автообнаружения с сервера",
|
||||||
"Invalid base_url for m.homeserver": "Неверный base_url для m.homeserver",
|
"Invalid base_url for m.homeserver": "Неверный base_url для m.homeserver",
|
||||||
"Homeserver URL does not appear to be a valid Matrix homeserver": "URL-адрес сервера не является действительным URL-адресом сервера Матрица",
|
"Homeserver URL does not appear to be a valid Matrix homeserver": "URL-адрес домашнего сервера не является допустимым домашним сервером Matrix",
|
||||||
"Invalid identity server discovery response": "Неверный ответ на запрос идентификации сервера",
|
"Invalid identity server discovery response": "Неверный ответ на запрос идентификации сервера",
|
||||||
"Invalid base_url for m.identity_server": "Неверный base_url для m.identity_server",
|
"Invalid base_url for m.identity_server": "Неверный base_url для m.identity_server",
|
||||||
"Identity server URL does not appear to be a valid identity server": "URL-адрес сервера идентификации не является действительным сервером идентификации",
|
"Identity server URL does not appear to be a valid identity server": "URL-адрес сервера идентификации не является действительным сервером идентификации",
|
||||||
|
@ -2411,7 +2411,7 @@
|
||||||
"Explore public rooms": "Просмотреть публичные комнаты",
|
"Explore public rooms": "Просмотреть публичные комнаты",
|
||||||
"Uploading logs": "Загрузка журналов",
|
"Uploading logs": "Загрузка журналов",
|
||||||
"Downloading logs": "Скачивание журналов",
|
"Downloading logs": "Скачивание журналов",
|
||||||
"Can't see what you’re looking for?": "Не видите то, что ищете?",
|
"Can't see what you’re looking for?": "Не нашли, что искали?",
|
||||||
"Explore all public rooms": "Просмотреть все публичные комнаты",
|
"Explore all public rooms": "Просмотреть все публичные комнаты",
|
||||||
"%(count)s results|other": "%(count)s результатов",
|
"%(count)s results|other": "%(count)s результатов",
|
||||||
"Preparing to download logs": "Подготовка к загрузке журналов",
|
"Preparing to download logs": "Подготовка к загрузке журналов",
|
||||||
|
@ -2508,5 +2508,14 @@
|
||||||
"This version of %(brand)s does not support viewing some encrypted files": "Эта версия %(brand)s не поддерживает просмотр некоторых зашифрованных файлов",
|
"This version of %(brand)s does not support viewing some encrypted files": "Эта версия %(brand)s не поддерживает просмотр некоторых зашифрованных файлов",
|
||||||
"This version of %(brand)s does not support searching encrypted messages": "Эта версия %(brand)s не поддерживает поиск зашифрованных сообщений",
|
"This version of %(brand)s does not support searching encrypted messages": "Эта версия %(brand)s не поддерживает поиск зашифрованных сообщений",
|
||||||
"Cannot create rooms in this community": "Невозможно создать комнаты в этом сообществе",
|
"Cannot create rooms in this community": "Невозможно создать комнаты в этом сообществе",
|
||||||
"You do not have permission to create rooms in this community.": "У вас нет разрешения на создание комнат в этом сообществе."
|
"You do not have permission to create rooms in this community.": "У вас нет разрешения на создание комнат в этом сообществе.",
|
||||||
|
"Join the conference at the top of this room": "Присоединяйтесь к конференции в верхней части этой комнаты",
|
||||||
|
"Join the conference from the room information card on the right": "Присоединяйтесь к конференции, используя информационную карточку комнаты справа",
|
||||||
|
"Video conference ended by %(senderName)s": "%(senderName)s завершил(а) видеоконференцию",
|
||||||
|
"Video conference updated by %(senderName)s": "%(senderName)s обновил(а) видеоконференцию",
|
||||||
|
"Video conference started by %(senderName)s": "%(senderName)s начал(а) видеоконференцию",
|
||||||
|
"End conference": "Завершить конференцию",
|
||||||
|
"This will end the conference for everyone. Continue?": "Это завершит конференцию для всех. Продолжить?",
|
||||||
|
"Failed to save your profile": "Не удалось сохранить ваш профиль",
|
||||||
|
"The operation could not be completed": "Операция не может быть выполнена"
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,5 +11,14 @@
|
||||||
"Custom Server Options": "Možnosti strežnika po meri",
|
"Custom Server Options": "Možnosti strežnika po meri",
|
||||||
"Your language of choice": "Vaš jezik po izbiri",
|
"Your language of choice": "Vaš jezik po izbiri",
|
||||||
"Use Single Sign On to continue": "Uporabi Single Sign On za prijavo",
|
"Use Single Sign On to continue": "Uporabi Single Sign On za prijavo",
|
||||||
"Confirm adding this email address by using Single Sign On to prove your identity.": ""
|
"Confirm adding this email address by using Single Sign On to prove your identity.": "Potrdite dodajanje tega e-poštnega naslova z enkratno prijavo, da dokažete svojo identiteto.",
|
||||||
|
"Single Sign On": "Enkratna prijava",
|
||||||
|
"Confirm adding email": "Potrdi dodajanje e-poštnega naslova",
|
||||||
|
"Click the button below to confirm adding this email address.": "Kliknite gumb spodaj za potrditev dodajanja tega elektronskega naslova.",
|
||||||
|
"Confirm": "Potrdi",
|
||||||
|
"Add Email Address": "Dodaj e-poštni naslov",
|
||||||
|
"Confirm adding this phone number by using Single Sign On to prove your identity.": "Potrdite dodajanje te telefonske številke z enkratno prijavo, da dokažete svojo identiteto.",
|
||||||
|
"Confirm adding phone number": "Potrdi dodajanje telefonske številke",
|
||||||
|
"Click the button below to confirm adding this phone number.": "Pritisnite gumb spodaj da potrdite dodajanje te telefonske številke.",
|
||||||
|
"Add Phone Number": "Dodaj telefonsko številko"
|
||||||
}
|
}
|
||||||
|
|
|
@ -2500,5 +2500,23 @@
|
||||||
"Start a conversation with someone using their name or username (like <userId/>).": "Nisni një bisedë me dikë duke përdorur emrin e tij ose emrin e tij të përdoruesit (bie fjala, <userId/>).",
|
"Start a conversation with someone using their name or username (like <userId/>).": "Nisni një bisedë me dikë duke përdorur emrin e tij ose emrin e tij të përdoruesit (bie fjala, <userId/>).",
|
||||||
"This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click <a>here</a>": "Kjo s’do ta ftojë te %(communityName)s. Që të ftoni dikë te %(communityName)s, klikoni <a>këtu</a>",
|
"This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click <a>here</a>": "Kjo s’do ta ftojë te %(communityName)s. Që të ftoni dikë te %(communityName)s, klikoni <a>këtu</a>",
|
||||||
"Invite someone using their name, username (like <userId/>) or <a>share this room</a>.": "Ftoni dikë duke përdorur emrin e tij, emrin e tij të përdoruesit (bie fjala, <userId/>) ose <a>ndani me të këtë dhomë</a>.",
|
"Invite someone using their name, username (like <userId/>) or <a>share this room</a>.": "Ftoni dikë duke përdorur emrin e tij, emrin e tij të përdoruesit (bie fjala, <userId/>) ose <a>ndani me të këtë dhomë</a>.",
|
||||||
"Unable to set up keys": "S’arrihet të ujdisen kyçe"
|
"Unable to set up keys": "S’arrihet të ujdisen kyçe",
|
||||||
|
"End conference": "Përfundoje konferencën",
|
||||||
|
"This will end the conference for everyone. Continue?": "Kjo do të mbyllë konferencën për këdo. Të vazhdohet?",
|
||||||
|
"Offline encrypted messaging using dehydrated devices": "Shkëmbim jashtë linje mesazhesh të fshehtëzuar duke përdorur pajisje të dehidratuara",
|
||||||
|
"Cross-signing is ready for use.": "“Cross-signing” është gati për përdorim.",
|
||||||
|
"Cross-signing is not set up.": "“Cross-signing” s’është ujdisur.",
|
||||||
|
"Remove messages sent by others": "Hiqi mesazhet e dërguar nga të tjerët",
|
||||||
|
"Ignored attempt to disable encryption": "U shpërfill përpjekje për të çaktivizuar fshehtëzimin",
|
||||||
|
"Join the conference at the top of this room": "Merrni pjesë në konferencë, në krye të kësaj dhome",
|
||||||
|
"Join the conference from the room information card on the right": "Merrni pjesë në konferencë që prej kartës së informacionit mbi dhomën, në të djathtë",
|
||||||
|
"Video conference ended by %(senderName)s": "Konferenca video u përfundua nga %(senderName)s",
|
||||||
|
"Video conference updated by %(senderName)s": "Konferenca video u përditësua nga %(senderName)s",
|
||||||
|
"Video conference started by %(senderName)s": "Konferenca video u fillua nga %(senderName)s",
|
||||||
|
"Use the <a>Desktop app</a> to see all encrypted files": "Që të shihni krejt kartelat e fshehtëzuara, përdorni <a>aplikacionin për Desktop</a>",
|
||||||
|
"Use the <a>Desktop app</a> to search encrypted messages": "Që të kërkoni te mesazhe të fshehtëzuar, përdorni <a>aplikacionin për Desktop</a>",
|
||||||
|
"This version of %(brand)s does not support viewing some encrypted files": "Ky version i %(brand)s nuk mbulon parjen për disa kartela të fshehtëzuara",
|
||||||
|
"This version of %(brand)s does not support searching encrypted messages": "Ky version i %(brand)s nuk mbulon kërkimin në mesazhe të fshehtëzuar",
|
||||||
|
"Cannot create rooms in this community": "S’mund të krijohen dhoma në këtë bashkësi",
|
||||||
|
"You do not have permission to create rooms in this community.": "S’keni leje të krijoni dhoma në këtë bashkësi."
|
||||||
}
|
}
|
||||||
|
|
|
@ -2439,5 +2439,23 @@
|
||||||
"Start a conversation with someone using their name or username (like <userId/>).": "Starta en konversation med någon med deras namn eller användarnamn (som <userId/>).",
|
"Start a conversation with someone using their name or username (like <userId/>).": "Starta en konversation med någon med deras namn eller användarnamn (som <userId/>).",
|
||||||
"This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click <a>here</a>": "Detta kommer inte att bjuda in dem till %(communityName)s. För att bjuda in någon till %(communityName)s, klicka <a>här</a>",
|
"This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click <a>here</a>": "Detta kommer inte att bjuda in dem till %(communityName)s. För att bjuda in någon till %(communityName)s, klicka <a>här</a>",
|
||||||
"Invite someone using their name, username (like <userId/>) or <a>share this room</a>.": "Bjud in någon med deras namn eller användarnamn (som <userId/>) eller <a>dela det här rummet</a>.",
|
"Invite someone using their name, username (like <userId/>) or <a>share this room</a>.": "Bjud in någon med deras namn eller användarnamn (som <userId/>) eller <a>dela det här rummet</a>.",
|
||||||
"Unable to set up keys": "Kunde inte ställa in nycklar"
|
"Unable to set up keys": "Kunde inte ställa in nycklar",
|
||||||
|
"End conference": "Avsluta gruppsamtal",
|
||||||
|
"This will end the conference for everyone. Continue?": "Detta kommer att avsluta gruppsamtalet för alla. Fortsätt?",
|
||||||
|
"Offline encrypted messaging using dehydrated devices": "Krypterad meddelandehantering offline med hjälp av frystorkade enheter",
|
||||||
|
"Failed to save your profile": "Misslyckades att spara din profil",
|
||||||
|
"The operation could not be completed": "Operationen kunde inte slutföras",
|
||||||
|
"Remove messages sent by others": "Ta bort meddelanden skickade av andra",
|
||||||
|
"Ignored attempt to disable encryption": "Ignorerade försök att inaktivera kryptering",
|
||||||
|
"Join the conference at the top of this room": "Gå med i gruppsamtalet på toppen av det här rummet",
|
||||||
|
"Join the conference from the room information card on the right": "Gå med i gruppsamtalet ifrån informationskortet till höger",
|
||||||
|
"Video conference ended by %(senderName)s": "Videogruppsamtal avslutat av %(senderName)s",
|
||||||
|
"Video conference updated by %(senderName)s": "Videogruppsamtal uppdaterat av %(senderName)s",
|
||||||
|
"Video conference started by %(senderName)s": "Videogruppsamtal startat av %(senderName)s",
|
||||||
|
"Use the <a>Desktop app</a> to see all encrypted files": "Använd <a>skrivbordsappen</a> för att se alla krypterade filer",
|
||||||
|
"Use the <a>Desktop app</a> to search encrypted messages": "Använd <a>skrivbordsappen</a> söka bland krypterade meddelanden",
|
||||||
|
"This version of %(brand)s does not support viewing some encrypted files": "Den här versionen av %(brand)s stöder inte visning av vissa krypterade filer",
|
||||||
|
"This version of %(brand)s does not support searching encrypted messages": "Den här versionen av %(brand)s stöder inte sökning bland krypterade meddelanden",
|
||||||
|
"Cannot create rooms in this community": "Kan inte skapa rum i den här gemenskapen",
|
||||||
|
"You do not have permission to create rooms in this community.": "Du har inte behörighet att skapa rum i den här gemenskapen."
|
||||||
}
|
}
|
||||||
|
|
|
@ -642,7 +642,7 @@
|
||||||
"Sunday": "星期日",
|
"Sunday": "星期日",
|
||||||
"Notification targets": "通知目标",
|
"Notification targets": "通知目标",
|
||||||
"Today": "今天",
|
"Today": "今天",
|
||||||
"You are not receiving desktop notifications": "您将不会收到桌面通知",
|
"You are not receiving desktop notifications": "您现在不会收到桌面通知",
|
||||||
"Friday": "星期五",
|
"Friday": "星期五",
|
||||||
"Update": "更新",
|
"Update": "更新",
|
||||||
"What's New": "更新内容",
|
"What's New": "更新内容",
|
||||||
|
@ -1278,7 +1278,7 @@
|
||||||
"If you cancel now, you won't complete verifying your other session.": "如果现在取消,您将无法完成验证您的其他会话。",
|
"If you cancel now, you won't complete verifying your other session.": "如果现在取消,您将无法完成验证您的其他会话。",
|
||||||
"If you cancel now, you won't complete your operation.": "如果现在取消,您将无法完成您的操作。",
|
"If you cancel now, you won't complete your operation.": "如果现在取消,您将无法完成您的操作。",
|
||||||
"Cancel entering passphrase?": "取消输入密码?",
|
"Cancel entering passphrase?": "取消输入密码?",
|
||||||
"Setting up keys": "设置按键",
|
"Setting up keys": "设置密钥",
|
||||||
"Verify this session": "验证此会话",
|
"Verify this session": "验证此会话",
|
||||||
"Encryption upgrade available": "提供加密升级",
|
"Encryption upgrade available": "提供加密升级",
|
||||||
"Set up encryption": "设置加密",
|
"Set up encryption": "设置加密",
|
||||||
|
@ -2384,5 +2384,19 @@
|
||||||
"Cross-signing is ready for use.": "交叉签名已可用。",
|
"Cross-signing is ready for use.": "交叉签名已可用。",
|
||||||
"Cross-signing is not set up.": "未设置交叉签名。",
|
"Cross-signing is not set up.": "未设置交叉签名。",
|
||||||
"Backup version:": "备份版本:",
|
"Backup version:": "备份版本:",
|
||||||
"Algorithm:": "算法:"
|
"Algorithm:": "算法:",
|
||||||
|
"Set up Secure Backup": "设置安全备份",
|
||||||
|
"Safeguard against losing access to encrypted messages & data": "保护加密信息 & 数据的访问权",
|
||||||
|
"not found in storage": "未在存储中找到",
|
||||||
|
"Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.": "请备份加密密钥及帐户数据,以防无法访问您的会话。您的密钥将使用唯一的恢复密钥进行保护。",
|
||||||
|
"Backup key stored:": "备份密钥已保存:",
|
||||||
|
"Backup key cached:": "备份密钥已缓存:",
|
||||||
|
"Secret storage:": "秘密存储:",
|
||||||
|
"ready": "就绪",
|
||||||
|
"not ready": "尚未就绪",
|
||||||
|
"Secure Backup": "安全备份",
|
||||||
|
"Privacy": "隐私",
|
||||||
|
"Explore community rooms": "探索社区聊天室",
|
||||||
|
"%(count)s results|one": "%(count)s 个结果",
|
||||||
|
"Room Info": "聊天室信息"
|
||||||
}
|
}
|
||||||
|
|
|
@ -2515,5 +2515,17 @@
|
||||||
"This version of %(brand)s does not support viewing some encrypted files": "此版本的 %(brand)s 不支援檢視某些加密檔案",
|
"This version of %(brand)s does not support viewing some encrypted files": "此版本的 %(brand)s 不支援檢視某些加密檔案",
|
||||||
"This version of %(brand)s does not support searching encrypted messages": "此版本的 %(brand)s 不支援搜尋加密訊息",
|
"This version of %(brand)s does not support searching encrypted messages": "此版本的 %(brand)s 不支援搜尋加密訊息",
|
||||||
"Cannot create rooms in this community": "無法在此社群中建立聊天室",
|
"Cannot create rooms in this community": "無法在此社群中建立聊天室",
|
||||||
"You do not have permission to create rooms in this community.": "您沒有在此社群中建立聊天室的權限。"
|
"You do not have permission to create rooms in this community.": "您沒有在此社群中建立聊天室的權限。",
|
||||||
|
"Join the conference at the top of this room": "加入此聊天室頂部的會議",
|
||||||
|
"Join the conference from the room information card on the right": "從右側的聊天室資訊卡片加入會議",
|
||||||
|
"Video conference ended by %(senderName)s": "視訊會議由 %(senderName)s 結束",
|
||||||
|
"Video conference updated by %(senderName)s": "視訊會議由 %(senderName)s 更新",
|
||||||
|
"Video conference started by %(senderName)s": "視訊會議由 %(senderName)s 開始",
|
||||||
|
"End conference": "結束會議",
|
||||||
|
"This will end the conference for everyone. Continue?": "這將會對所有人結束會議。要繼續嗎?",
|
||||||
|
"Ignored attempt to disable encryption": "已忽略嘗試停用加密",
|
||||||
|
"Offline encrypted messaging using dehydrated devices": "使用乾淨裝置的離線加密訊息",
|
||||||
|
"Failed to save your profile": "儲存您的設定檔失敗",
|
||||||
|
"The operation could not be completed": "無法完成操作",
|
||||||
|
"Remove messages sent by others": "移除其他人傳送的訊息"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,90 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 Vector Creations 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 dis from '../dispatcher/dispatcher';
|
|
||||||
import {Store} from 'flux/utils';
|
|
||||||
|
|
||||||
const INITIAL_STATE = {
|
|
||||||
cachedPassword: localStorage.getItem('mx_pass'),
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A class for storing application state to do with the session. This is a simple flux
|
|
||||||
* store that listens for actions and updates its state accordingly, informing any
|
|
||||||
* listeners (views) of state changes.
|
|
||||||
*
|
|
||||||
* Usage:
|
|
||||||
* ```
|
|
||||||
* sessionStore.addListener(() => {
|
|
||||||
* this.setState({ cachedPassword: sessionStore.getCachedPassword() })
|
|
||||||
* })
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
class SessionStore extends Store {
|
|
||||||
constructor() {
|
|
||||||
super(dis);
|
|
||||||
|
|
||||||
// Initialise state
|
|
||||||
this._state = INITIAL_STATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
_update() {
|
|
||||||
// Persist state to localStorage
|
|
||||||
if (this._state.cachedPassword) {
|
|
||||||
localStorage.setItem('mx_pass', this._state.cachedPassword);
|
|
||||||
} else {
|
|
||||||
localStorage.removeItem('mx_pass', this._state.cachedPassword);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.__emitChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
_setState(newState) {
|
|
||||||
this._state = Object.assign(this._state, newState);
|
|
||||||
this._update();
|
|
||||||
}
|
|
||||||
|
|
||||||
__onDispatch(payload) {
|
|
||||||
switch (payload.action) {
|
|
||||||
case 'cached_password':
|
|
||||||
this._setState({
|
|
||||||
cachedPassword: payload.cachedPassword,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'password_changed':
|
|
||||||
this._setState({
|
|
||||||
cachedPassword: null,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'on_client_not_viable':
|
|
||||||
case 'on_logged_out':
|
|
||||||
this._setState({
|
|
||||||
cachedPassword: null,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getCachedPassword() {
|
|
||||||
return this._state.cachedPassword;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let singletonSessionStore = null;
|
|
||||||
if (!singletonSessionStore) {
|
|
||||||
singletonSessionStore = new SessionStore();
|
|
||||||
}
|
|
||||||
export default singletonSessionStore;
|
|
|
@ -66,7 +66,7 @@ class ElementWidget extends Widget {
|
||||||
if (WidgetType.JITSI.matches(this.type)) {
|
if (WidgetType.JITSI.matches(this.type)) {
|
||||||
return WidgetUtils.getLocalJitsiWrapperUrl({
|
return WidgetUtils.getLocalJitsiWrapperUrl({
|
||||||
forLocalRender: true,
|
forLocalRender: true,
|
||||||
auth: this.rawData?.auth,
|
auth: super.rawData?.auth, // this.rawData can call templateUrl, do this to prevent looping
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return super.templateUrl;
|
return super.templateUrl;
|
||||||
|
|
|
@ -1,47 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { _t } from "../languageHandler";
|
|
||||||
import Modal from "../Modal";
|
|
||||||
import SetPasswordDialog from "../components/views/dialogs/SetPasswordDialog";
|
|
||||||
import GenericToast from "../components/views/toasts/GenericToast";
|
|
||||||
import ToastStore from "../stores/ToastStore";
|
|
||||||
|
|
||||||
const onAccept = () => {
|
|
||||||
Modal.createTrackedDialog('Set Password Dialog', 'Password Nag Bar', SetPasswordDialog);
|
|
||||||
};
|
|
||||||
|
|
||||||
const TOAST_KEY = "setpassword";
|
|
||||||
|
|
||||||
export const showToast = () => {
|
|
||||||
ToastStore.sharedInstance().addOrReplaceToast({
|
|
||||||
key: TOAST_KEY,
|
|
||||||
title: _t("Set password"),
|
|
||||||
props: {
|
|
||||||
description: _t("To return to your account in future you need to set a password"),
|
|
||||||
acceptLabel: _t("Set Password"),
|
|
||||||
onAccept,
|
|
||||||
rejectLabel: _t("Later"),
|
|
||||||
onReject: hideToast, // it'll return on reload
|
|
||||||
},
|
|
||||||
component: GenericToast,
|
|
||||||
priority: 60,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const hideToast = () => {
|
|
||||||
ToastStore.sharedInstance().dismissToast(TOAST_KEY);
|
|
||||||
};
|
|
|
@ -5928,7 +5928,7 @@ mathml-tag-names@^2.0.1:
|
||||||
|
|
||||||
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop":
|
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop":
|
||||||
version "8.4.1"
|
version "8.4.1"
|
||||||
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/a9a6b2de48250440dc2a1c3eee630f4957fb9f83"
|
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/a727da9193e0ccb2fa8d7c3e8e321916f6717190"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.11.2"
|
"@babel/runtime" "^7.11.2"
|
||||||
another-json "^0.2.0"
|
another-json "^0.2.0"
|
||||||
|
|
Loading…
Reference in a new issue