diff --git a/.eslintignore.errorfiles b/.eslintignore.errorfiles index 2e2a404338..aa2a6b7f0b 100644 --- a/.eslintignore.errorfiles +++ b/.eslintignore.errorfiles @@ -1,50 +1,31 @@ # 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/Markdown.js -src/notifications/ContentRules.js -src/notifications/PushRuleVectorState.js -src/PlatformPeg.js -src/rageshake/rageshake.js -src/ratelimitedfunc.js src/Rooms.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/DirectoryUtils.js -src/utils/DMRoomMap.js -src/utils/FormattingUtils.js src/utils/MultiInviter.js src/utils/Receipt.js -src/Velociraptor.js test/components/structures/MessagePanel-test.js test/components/views/dialogs/InteractiveAuthDialog-test.js test/mock-clock.js diff --git a/res/css/_common.scss b/res/css/_common.scss index aafd6e5297..3346394edd 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -262,7 +262,7 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { font-weight: 300; font-size: $font-15px; position: relative; - padding: 25px 30px 30px 30px; + padding: 24px; max-height: 80%; box-shadow: 2px 15px 30px 0 $dialog-shadow-color; border-radius: 8px; diff --git a/res/css/_components.scss b/res/css/_components.scss index 261b35690e..06cdbdcb4b 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -81,8 +81,6 @@ @import "./views/dialogs/_RoomUpgradeWarningDialog.scss"; @import "./views/dialogs/_ServerOfflineDialog.scss"; @import "./views/dialogs/_SetEmailDialog.scss"; -@import "./views/dialogs/_SetMxIdDialog.scss"; -@import "./views/dialogs/_SetPasswordDialog.scss"; @import "./views/dialogs/_SettingsDialog.scss"; @import "./views/dialogs/_ShareDialog.scss"; @import "./views/dialogs/_SlashCommandHelpDialog.scss"; diff --git a/res/css/structures/_TabbedView.scss b/res/css/structures/_TabbedView.scss index 4a4bb125a3..39a8ebed32 100644 --- a/res/css/structures/_TabbedView.scss +++ b/res/css/structures/_TabbedView.scss @@ -17,7 +17,7 @@ limitations under the License. .mx_TabbedView { margin: 0; - padding: 0 0 0 58px; + padding: 0 0 0 16px; display: flex; flex-direction: column; position: absolute; @@ -25,6 +25,7 @@ limitations under the License. bottom: 0; left: 0; right: 0; + margin-top: 8px; } .mx_TabbedView_tabLabels { @@ -35,13 +36,13 @@ limitations under the License. } .mx_TabbedView_tabLabel { + display: flex; + align-items: center; vertical-align: text-top; cursor: pointer; - display: block; - border-radius: 3px; - font-size: $font-14px; - min-height: 24px; // use min-height instead of height to allow the label to overflow a bit - margin-bottom: 6px; + padding: 8px 0; + border-radius: 8px; + font-size: $font-13px; position: relative; } @@ -51,9 +52,8 @@ limitations under the License. } .mx_TabbedView_maskedIcon { - margin-left: 6px; - margin-right: 9px; - margin-top: 1px; + margin-left: 8px; + margin-right: 16px; width: 16px; height: 16px; display: inline-block; @@ -65,10 +65,9 @@ limitations under the License. mask-repeat: no-repeat; mask-size: 16px; width: 16px; - height: 22px; + height: 16px; mask-position: center; content: ''; - vertical-align: middle; } .mx_TabbedView_tabLabel_active .mx_TabbedView_maskedIcon::before { diff --git a/res/css/views/dialogs/_RoomSettingsDialog.scss b/res/css/views/dialogs/_RoomSettingsDialog.scss index d4199a1e66..9bcde6e1e0 100644 --- a/res/css/views/dialogs/_RoomSettingsDialog.scss +++ b/res/css/views/dialogs/_RoomSettingsDialog.scss @@ -48,7 +48,6 @@ limitations under the License. white-space: nowrap; overflow: hidden; margin: 0 auto; - padding-left: 40px; padding-right: 80px; } diff --git a/res/css/views/dialogs/_SetMxIdDialog.scss b/res/css/views/dialogs/_SetMxIdDialog.scss deleted file mode 100644 index 1df34f3408..0000000000 --- a/res/css/views/dialogs/_SetMxIdDialog.scss +++ /dev/null @@ -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; -} diff --git a/res/css/views/dialogs/_SetPasswordDialog.scss b/res/css/views/dialogs/_SetPasswordDialog.scss deleted file mode 100644 index 1f99353298..0000000000 --- a/res/css/views/dialogs/_SetPasswordDialog.scss +++ /dev/null @@ -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; -} diff --git a/res/css/views/dialogs/_SettingsDialog.scss b/res/css/views/dialogs/_SettingsDialog.scss index ec813a1a07..6c4ed35c5a 100644 --- a/res/css/views/dialogs/_SettingsDialog.scss +++ b/res/css/views/dialogs/_SettingsDialog.scss @@ -36,7 +36,6 @@ limitations under the License. } .mx_Dialog_title { - text-align: center; margin-bottom: 24px; } } diff --git a/res/css/views/right_panel/_BaseCard.scss b/res/css/views/right_panel/_BaseCard.scss index b254b651e8..cfc8b335dd 100644 --- a/res/css/views/right_panel/_BaseCard.scss +++ b/res/css/views/right_panel/_BaseCard.scss @@ -40,6 +40,7 @@ limitations under the License. width: 20px; margin: 12px; top: 0; + border-radius: 10px; &::before { content: ""; @@ -55,7 +56,6 @@ limitations under the License. } .mx_BaseCard_back { - border-radius: 4px; left: 0; &::before { @@ -66,7 +66,6 @@ limitations under the License. } .mx_BaseCard_close { - border-radius: 10px; right: 0; &::before { diff --git a/res/css/views/rooms/_MemberList.scss b/res/css/views/rooms/_MemberList.scss index 2366667c95..f00907aeef 100644 --- a/res/css/views/rooms/_MemberList.scss +++ b/res/css/views/rooms/_MemberList.scss @@ -96,11 +96,21 @@ limitations under the License. } .mx_MemberList_invite span { - background-image: url('$(res)/img/element-icons/room/invite.svg'); - background-repeat: no-repeat; - background-position: center left; - background-size: 20px; - padding: 8px 0 8px 25px; + padding: 8px 0; + display: inline-flex; + + &::before { + 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 { diff --git a/res/img/element-icons/room/invite.svg b/res/img/element-icons/room/invite.svg index f713e57d73..655f9f118a 100644 --- a/res/img/element-icons/room/invite.svg +++ b/res/img/element-icons/room/invite.svg @@ -1,3 +1,3 @@ - - + + diff --git a/src/Lifecycle.js b/src/Lifecycle.ts similarity index 85% rename from src/Lifecycle.js rename to src/Lifecycle.ts index dc04e47535..f2cd1bce9e 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.ts @@ -17,9 +17,12 @@ See the License for the specific language governing permissions and 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 { 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 createMatrixClient from './utils/createMatrixClient'; import Analytics from './Analytics'; @@ -47,44 +50,46 @@ import ThreepidInviteStore from "./stores/ThreepidInviteStore"; const HOMESERVER_URL_KEY = "mx_hs_url"; const ID_SERVER_URL_KEY = "mx_is_url"; +interface ILoadSessionOpts { + enableGuest?: boolean; + guestHsUrl?: string; + guestIsUrl?: string; + ignoreGuest?: boolean; + defaultDeviceDisplayName?: string; + fragmentQueryParams?: Record; +} + /** * Called at startup, to attempt to build a logged-in Matrix session. It tries * a number of things: * - * * 1. if we have a guest access token in the fragment query params, it uses * that. - * * 2. if an access token is stored in local storage (from a previous session), * it uses that. - * * 3. it attempts to auto-register as a guest user. * * 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. * - * @param {object} opts - * - * @param {object} opts.fragmentQueryParams: string->string map of the + * @param {object} [opts] + * @param {object} [opts.fragmentQueryParams]: string->string map of the * query-parameters extracted from the #-fragment of the starting URI. - * - * @param {boolean} opts.enableGuest: set to true to enable guest access tokens - * and auto-guest registrations. - * - * @params {string} opts.guestHsUrl: homeserver URL. Only used if enableGuest is - * true; defines the HS to register against. - * - * @params {string} opts.guestIsUrl: homeserver URL. Only used if enableGuest is - * true; defines the IS to use. - * - * @params {bool} opts.ignoreGuest: If the stored session is a guest account, ignore - * it and don't load it. - * + * @param {boolean} [opts.enableGuest]: set to true to enable guest access + * tokens and auto-guest registrations. + * @param {string} [opts.guestHsUrl]: homeserver URL. Only used if enableGuest + * is true; defines the HS to register against. + * @param {string} [opts.guestIsUrl]: homeserver URL. Only used if enableGuest + * is true; defines the IS to use. + * @param {bool} [opts.ignoreGuest]: If the stored session is a guest account, + * ignore it and don't load it. + * @param {string} [opts.defaultDeviceDisplayName]: Default display name to use + * when registering as a guest. * @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 * failed. */ -export async function loadSession(opts) { +export async function loadSession(opts: ILoadSessionOpts = {}): Promise { try { let enableGuest = opts.enableGuest || false; const guestHsUrl = opts.guestHsUrl; @@ -97,12 +102,13 @@ export async function loadSession(opts) { enableGuest = false; } - if (enableGuest && + if ( + enableGuest && fragmentQueryParams.guest_user_id && fragmentQueryParams.guest_access_token - ) { + ) { console.log("Using guest access credentials"); - return _doSetLoggedIn({ + return doSetLoggedIn({ userId: fragmentQueryParams.guest_user_id, accessToken: fragmentQueryParams.guest_access_token, homeserverUrl: guestHsUrl, @@ -110,7 +116,7 @@ export async function loadSession(opts) { guest: true, }, true).then(() => true); } - const success = await _restoreFromLocalStorage({ + const success = await restoreFromLocalStorage({ ignoreGuest: Boolean(opts.ignoreGuest), }); if (success) { @@ -118,7 +124,7 @@ export async function loadSession(opts) { } if (enableGuest) { - return _registerAsGuest(guestHsUrl, guestIsUrl, defaultDeviceDisplayName); + return registerAsGuest(guestHsUrl, guestIsUrl, defaultDeviceDisplayName); } // 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. 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. * @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(); 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 * for a real user. If there is no stored session, return null. */ -export function getStoredSessionIsGuest() { +export function getStoredSessionIsGuest(): boolean { const sessVars = getLocalStorageSessionVars(); 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 * login, else false */ -export function attemptTokenLogin(queryParams, defaultDeviceDisplayName) { +export function attemptTokenLogin( + queryParams: Record, + defaultDeviceDisplayName?: string, +): Promise { if (!queryParams.loginToken) { return Promise.resolve(false); } @@ -184,10 +193,10 @@ export function attemptTokenLogin(queryParams, defaultDeviceDisplayName) { }, ).then(function(creds) { console.log("Logged in with token"); - return _clearStorage().then(() => { - _persistCredentialsToLocalStorage(creds); + return clearStorage().then(() => { + persistCredentialsToLocalStorage(creds); // remember that we just logged in - sessionStorage.setItem("mx_fresh_login", true); + sessionStorage.setItem("mx_fresh_login", String(true)); return true; }); }).catch((err) => { @@ -197,8 +206,8 @@ export function attemptTokenLogin(queryParams, defaultDeviceDisplayName) { }); } -export function handleInvalidStoreError(e) { - if (e.reason === Matrix.InvalidStoreError.TOGGLED_LAZY_LOADING) { +export function handleInvalidStoreError(e: InvalidStoreError): Promise { + if (e.reason === InvalidStoreError.TOGGLED_LAZY_LOADING) { return Promise.resolve().then(() => { const lazyLoadEnabled = e.value; if (lazyLoadEnabled) { @@ -231,7 +240,11 @@ export function handleInvalidStoreError(e) { } } -function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) { +function registerAsGuest( + hsUrl: string, + isUrl: string, + defaultDeviceDisplayName: string, +): Promise { console.log(`Doing guest login on ${hsUrl}`); // create a temporary MatrixClient to do the login @@ -245,7 +258,7 @@ function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) { }, }).then((creds) => { console.log(`Registered as guest: ${creds.user_id}`); - return _doSetLoggedIn({ + return doSetLoggedIn({ userId: creds.user_id, deviceId: creds.device_id, 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 * may not be valid, as it is not tested for consistency here. * @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 isUrl = localStorage.getItem(ID_SERVER_URL_KEY); 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 // SessionStore to avoid bugs where the view becomes out-of-sync with // localStorage (e.g. isGuest etc.) -async function _restoreFromLocalStorage(opts) { - const ignoreGuest = opts.ignoreGuest; +async function restoreFromLocalStorage(opts?: { ignoreGuest?: boolean }): Promise { + const ignoreGuest = opts?.ignoreGuest; if (!localStorage) { return false; @@ -314,11 +336,11 @@ async function _restoreFromLocalStorage(opts) { 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"); console.log(`Restoring session for ${userId}`); - await _doSetLoggedIn({ + await doSetLoggedIn({ userId: userId, deviceId: deviceId, accessToken: accessToken, @@ -335,7 +357,7 @@ async function _restoreFromLocalStorage(opts) { } } -async function _handleLoadSessionFailure(e) { +async function handleLoadSessionFailure(e: Error): Promise { console.error("Unable to load session", e); const SessionRestoreErrorDialog = @@ -348,7 +370,7 @@ async function _handleLoadSessionFailure(e) { const [success] = await modal.finished; if (success) { // user clicked continue. - await _clearStorage(); + await clearStorage(); return false; } @@ -369,12 +391,12 @@ async function _handleLoadSessionFailure(e) { * * @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 { credentials.freshLogin = true; stopMatrixClient(); const pickleKey = credentials.userId && credentials.deviceId - ? await PlatformPeg.get().createPickleKey(credentials.userId, credentials.deviceId) - : null; + ? await PlatformPeg.get().createPickleKey(credentials.userId, credentials.deviceId) + : null; if (pickleKey) { console.log("Created pickle key"); @@ -382,7 +404,7 @@ export async function setLoggedIn(credentials) { 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 */ -export function hydrateSession(credentials) { +export function hydrateSession(credentials: IMatrixClientCreds): Promise { const oldUserId = MatrixClientPeg.get().getUserId(); 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"); } - 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 */ -async function _doSetLoggedIn(credentials, clearStorage) { +async function doSetLoggedIn( + credentials: IMatrixClientCreds, + clearStorageEnabled: boolean, +): Promise { credentials.guest = Boolean(credentials.guest); const softLogout = isSoftLogout(); @@ -448,8 +473,8 @@ async function _doSetLoggedIn(credentials, clearStorage) { // (dis.dispatch uses `setTimeout`, which does not guarantee ordering.) dis.dispatch({action: 'on_logging_in'}, true); - if (clearStorage) { - await _clearStorage(); + if (clearStorageEnabled) { + await clearStorage(); } 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. // Show a modal recommending a full reset of storage. if (results.dataInLocalStorage && results.cryptoInited && !results.dataInCryptoStore) { - const signOut = await _showStorageEvictedDialog(); + const signOut = await showStorageEvictedDialog(); if (signOut) { - await _clearStorage(); + await clearStorage(); // 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. throw new AbortLoginAndRebuildStorage( @@ -487,20 +512,9 @@ async function _doSetLoggedIn(credentials, clearStorage) { if (localStorage) { try { - _persistCredentialsToLocalStorage(credentials); - + persistCredentialsToLocalStorage(credentials); // make sure we don't think that it's a fresh login any more 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) { console.warn("Error using local storage: can't persist session!", e); } @@ -514,7 +528,7 @@ async function _doSetLoggedIn(credentials, clearStorage) { return client; } -function _showStorageEvictedDialog() { +function showStorageEvictedDialog(): Promise { const StorageEvictedDialog = sdk.getComponent('views.dialogs.StorageEvictedDialog'); return new Promise(resolve => { Modal.createTrackedDialog('Storage evicted', '', StorageEvictedDialog, { @@ -527,7 +541,7 @@ function _showStorageEvictedDialog() { // `instanceof`. Babel 7 supports this natively in their class handling. class AbortLoginAndRebuildStorage extends Error { } -function _persistCredentialsToLocalStorage(credentials) { +function persistCredentialsToLocalStorage(credentials: IMatrixClientCreds): void { localStorage.setItem(HOMESERVER_URL_KEY, credentials.homeserverUrl); if (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)); if (credentials.pickleKey) { - localStorage.setItem("mx_has_pickle_key", true); + localStorage.setItem("mx_has_pickle_key", String(true)); } else { if (localStorage.getItem("mx_has_pickle_key")) { 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 */ -export function logout() { +export function logout(): void { if (!MatrixClientPeg.get()) return; if (MatrixClientPeg.get().isGuest()) { @@ -590,7 +604,7 @@ export function logout() { ); } -export function softLogout() { +export function softLogout(): void { if (!MatrixClientPeg.get()) return; // 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. } -export function isSoftLogout() { +export function isSoftLogout(): boolean { return localStorage.getItem("mx_soft_logout") === "true"; } -export function isLoggingOut() { +export function isLoggingOut(): boolean { return _isLoggingOut; } @@ -625,7 +639,7 @@ export function isLoggingOut() { * @param {boolean} startSyncing True (default) to actually start * syncing the client. */ -async function startMatrixClient(startSyncing=true) { +async function startMatrixClient(startSyncing = true): Promise { console.log(`Lifecycle: Starting MatrixClient`); // 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 * storage. Used after a session has been logged out. */ -export async function onLoggedOut() { +export async function onLoggedOut(): Promise { _isLoggingOut = false; // Ensure that we dispatch a view change **before** stopping the client so // so that React components unmount first. This avoids React soft crashes // that can occur when components try to use a null client. dis.dispatch({action: 'on_logged_out'}, true); stopMatrixClient(); - await _clearStorage({deleteEverything: true}); + await clearStorage({deleteEverything: true}); } /** * @param {object} opts Options for how to clear storage. * @returns {Promise} promise which resolves once the stores have been cleared */ -async function _clearStorage(opts: {deleteEverything: boolean}) { +async function clearStorage(opts?: { deleteEverything?: boolean }): Promise { Analytics.disable(); if (window.localStorage) { @@ -736,7 +750,7 @@ async function _clearStorage(opts: {deleteEverything: boolean}) { * @param {boolean} unsetClient True (default) to abandon the client * on MatrixClientPeg after stopping. */ -export function stopMatrixClient(unsetClient=true) { +export function stopMatrixClient(unsetClient = true): void { Notifier.stop(); UserActivity.sharedInstance().stop(); TypingStore.sharedInstance().reset(); diff --git a/src/Login.js b/src/Login.ts similarity index 56% rename from src/Login.js rename to src/Login.ts index 04805b4af9..38d78feab6 100644 --- a/src/Login.js +++ b/src/Login.ts @@ -18,35 +18,72 @@ See the License for the specific language governing permissions and 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 { 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 { - constructor(hsUrl, isUrl, fallbackHsUrl, opts) { - this._hsUrl = hsUrl; - this._isUrl = isUrl; - this._fallbackHsUrl = fallbackHsUrl; - this._currentFlowIndex = 0; - this._flows = []; - this._defaultDeviceDisplayName = opts.defaultDeviceDisplayName; - this._tempClient = null; // memoize + private hsUrl: string; + private isUrl: string; + private fallbackHsUrl: string; + private currentFlowIndex: number; + // TODO: Flows need a type in JS SDK + private flows: Array; + private defaultDeviceDisplayName: string; + 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() { - return this._hsUrl; + public getHomeserverUrl(): string { + return this.hsUrl; } - getIdentityServerUrl() { - return this._isUrl; + public getIdentityServerUrl(): string { + return this.isUrl; } - setHomeserverUrl(hsUrl) { - this._tempClient = null; // clear memoization - this._hsUrl = hsUrl; + public setHomeserverUrl(hsUrl: string): void { + this.tempClient = null; // clear memoization + this.hsUrl = hsUrl; } - setIdentityServerUrl(isUrl) { - this._tempClient = null; // clear memoization - this._isUrl = isUrl; + public setIdentityServerUrl(isUrl: string): void { + this.tempClient = null; // clear memoization + this.isUrl = isUrl; } /** @@ -54,40 +91,41 @@ export default class Login { * requests. * @returns {MatrixClient} */ - createTemporaryClient() { - if (this._tempClient) return this._tempClient; // use memoization - return this._tempClient = Matrix.createClient({ - baseUrl: this._hsUrl, - idBaseUrl: this._isUrl, + public createTemporaryClient(): MatrixClient { + if (this.tempClient) return this.tempClient; // use memoization + return this.tempClient = Matrix.createClient({ + baseUrl: this.hsUrl, + idBaseUrl: this.isUrl, }); } - getFlows() { - const self = this; + public async getFlows(): Promise> { const client = this.createTemporaryClient(); - return client.loginFlows().then(function(result) { - self._flows = result.flows; - self._currentFlowIndex = 0; - // technically the UI should display options for all flows for the - // user to then choose one, so return all the flows here. - return self._flows; - }); + const { flows } = await client.loginFlows(); + this.flows = flows; + this.currentFlowIndex = 0; + // technically the UI should display options for all flows for the + // user to then choose one, so return all the flows here. + return this.flows; } - chooseFlow(flowIndex) { - this._currentFlowIndex = flowIndex; + public chooseFlow(flowIndex): void { + this.currentFlowIndex = flowIndex; } - getCurrentFlowStep() { + public getCurrentFlowStep(): string { // technically the flow can have multiple steps, but no one does this // for login so we can ignore it. - const flowStep = this._flows[this._currentFlowIndex]; + const flowStep = this.flows[this.currentFlowIndex]; return flowStep ? flowStep.type : null; } - loginViaPassword(username, phoneCountry, phoneNumber, pass) { - const self = this; - + public loginViaPassword( + username: string, + phoneCountry: string, + phoneNumber: string, + password: string, + ): Promise { const isEmail = username.indexOf("@") > 0; let identifier; @@ -113,14 +151,14 @@ export default class Login { } const loginParams = { - password: pass, - identifier: identifier, - initial_device_display_name: this._defaultDeviceDisplayName, + password, + identifier, + initial_device_display_name: this.defaultDeviceDisplayName, }; const tryFallbackHs = (originalError) => { return sendLoginRequest( - self._fallbackHsUrl, this._isUrl, 'm.login.password', loginParams, + this.fallbackHsUrl, this.isUrl, 'm.login.password', loginParams, ).catch((fallbackError) => { console.log("fallback HS login failed", fallbackError); // throw the original error @@ -130,11 +168,11 @@ export default class Login { let originalLoginError = null; return sendLoginRequest( - self._hsUrl, self._isUrl, 'm.login.password', loginParams, + this.hsUrl, this.isUrl, 'm.login.password', loginParams, ).catch((error) => { originalLoginError = error; if (error.httpStatus === 403) { - if (self._fallbackHsUrl) { + if (this.fallbackHsUrl) { 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} isUrl the base url of the default identity server * @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} */ -export async function sendLoginRequest(hsUrl, isUrl, loginType, loginParams) { +export async function sendLoginRequest( + hsUrl: string, + isUrl: string, + loginType: string, + loginParams: ILoginParams, +): Promise { const client = Matrix.createClient({ baseUrl: hsUrl, idBaseUrl: isUrl, diff --git a/src/MatrixClientPeg.ts b/src/MatrixClientPeg.ts index 69e586c58d..4651a0afe3 100644 --- a/src/MatrixClientPeg.ts +++ b/src/MatrixClientPeg.ts @@ -38,9 +38,9 @@ export interface IMatrixClientCreds { homeserverUrl: string; identityServerUrl: string; userId: string; - deviceId: string; + deviceId?: string; accessToken: string; - guest: boolean; + guest?: boolean; pickleKey?: string; freshLogin?: boolean; } diff --git a/src/Registration.js b/src/Registration.js index 9c0264c067..0df2ec3eb3 100644 --- a/src/Registration.js +++ b/src/Registration.js @@ -24,7 +24,6 @@ import dis from './dispatcher/dispatcher'; import * as sdk from './index'; import Modal from './Modal'; import { _t } from './languageHandler'; -// import {MatrixClientPeg} from './MatrixClientPeg'; // 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 @@ -44,70 +43,27 @@ export const SAFE_LOCALPART_REGEX = /^[a-z0-9=_\-./]+$/; */ export async function startAnyRegistrationFlow(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 modal = Modal.createTrackedDialog('Registration required', '', QuestionDialog, { - hasCancelButton: true, - quitOnly: true, - title: _t("Sign In or Create Account"), - description: _t("Use your account or create a new one to continue."), - button: _t("Create Account"), - extraButtons: [ - , - ], - onFinished: (proceed) => { - if (proceed) { - dis.dispatch({action: 'start_registration', screenAfterLogin: options.screen_after}); - } else if (options.go_home_on_cancel) { - dis.dispatch({action: 'view_home_page'}); - } else if (options.go_welcome_on_cancel) { - dis.dispatch({action: 'view_welcome_page'}); - } - }, - }); - //} + const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); + const modal = Modal.createTrackedDialog('Registration required', '', QuestionDialog, { + hasCancelButton: true, + quitOnly: true, + title: _t("Sign In or Create Account"), + description: _t("Use your account or create a new one to continue."), + button: _t("Create Account"), + extraButtons: [ + , + ], + onFinished: (proceed) => { + if (proceed) { + dis.dispatch({action: 'start_registration', screenAfterLogin: options.screen_after}); + } else if (options.go_home_on_cancel) { + dis.dispatch({action: 'view_home_page'}); + } else if (options.go_welcome_on_cancel) { + dis.dispatch({action: 'view_welcome_page'}); + } + }, + }); } - -// 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!"); -// } diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 4dc2080895..79f2916200 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -27,7 +27,6 @@ import CallMediaHandler from '../../CallMediaHandler'; import { fixupColorFonts } from '../../utils/FontManager'; import * as sdk from '../../index'; import dis from '../../dispatcher/dispatcher'; -import sessionStore from '../../stores/SessionStore'; import {MatrixClientPeg, IMatrixClientCreds} from '../../MatrixClientPeg'; import SettingsStore from "../../settings/SettingsStore"; @@ -41,10 +40,6 @@ import HomePage from "./HomePage"; import ResizeNotifier from "../../utils/ResizeNotifier"; import PlatformPeg from "../../PlatformPeg"; import { DefaultTagID } from "../../stores/room-list/models"; -import { - showToast as showSetPasswordToast, - hideToast as hideSetPasswordToast, -} from "../../toasts/SetPasswordToast"; import { showToast as showServerLimitToast, hideToast as hideServerLimitToast, @@ -149,8 +144,6 @@ class LoggedInView extends React.Component { protected readonly _matrixClient: MatrixClient; protected readonly _roomView: React.RefObject; protected readonly _resizeContainer: React.RefObject; - protected readonly _sessionStore: sessionStore; - protected readonly _sessionStoreToken: { remove: () => void }; protected readonly _compactLayoutWatcherRef: string; protected resizer: Resizer; @@ -171,12 +164,6 @@ class LoggedInView extends React.Component { document.addEventListener('keydown', this._onNativeKeyDown, false); - this._sessionStore = sessionStore; - this._sessionStoreToken = this._sessionStore.addListener( - this._setStateFromSessionStore, - ); - this._setStateFromSessionStore(); - this._updateServerNoticeEvents(); this._matrixClient.on("accountData", this.onAccountData); @@ -205,9 +192,6 @@ class LoggedInView extends React.Component { this._matrixClient.removeListener("sync", this.onSync); this._matrixClient.removeListener("RoomState.events", this.onRoomStateEvents); SettingsStore.unwatchSetting(this._compactLayoutWatcherRef); - if (this._sessionStoreToken) { - this._sessionStoreToken.remove(); - } this.resizer.detach(); } @@ -228,14 +212,6 @@ class LoggedInView extends React.Component { return this._roomView.current.canResetTimeline(); }; - _setStateFromSessionStore = () => { - if (this._sessionStore.getCachedPassword()) { - showSetPasswordToast(); - } else { - hideSetPasswordToast(); - } - }; - _createResizer() { const classNames = { handle: "mx_ResizeHandle", diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 19418df414..4f5489d796 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -30,7 +30,7 @@ import 'what-input'; import Analytics from "../../Analytics"; import { DecryptionFailureTracker } from "../../DecryptionFailureTracker"; -import { MatrixClientPeg } from "../../MatrixClientPeg"; +import { MatrixClientPeg, IMatrixClientCreds } from "../../MatrixClientPeg"; import PlatformPeg from "../../PlatformPeg"; import SdkConfig from "../../SdkConfig"; import * as RoomListSorter from "../../RoomListSorter"; @@ -290,7 +290,7 @@ export default class MatrixChat extends React.PureComponent { // 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 // logout page. - Lifecycle.loadSession({}); + Lifecycle.loadSession(); } this.accountPassword = null; @@ -670,9 +670,6 @@ export default class MatrixChat extends React.PureComponent { case 'view_home_page': this.viewHome(); break; - case 'view_set_mxid': - this.setMxId(payload); - break; case 'view_start_chat_or_reuse': this.chatCreateOrReuse(payload.user_id); break; @@ -985,36 +982,6 @@ export default class MatrixChat extends React.PureComponent { }); } - 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) { const communityId = CommunityPrototypeStore.instance.getSelectedCommunityId(); if (communityId) { @@ -1814,12 +1781,12 @@ export default class MatrixChat extends React.PureComponent { this.showScreen("forgot_password"); }; - onRegisterFlowComplete = (credentials: object, password: string) => { + onRegisterFlowComplete = (credentials: IMatrixClientCreds, password: string) => { return this.onUserCompletedLoginFlow(credentials, password); }; // returns a promise which resolves to the new MatrixClient - onRegistered(credentials: object) { + onRegistered(credentials: IMatrixClientCreds) { return Lifecycle.setLoggedIn(credentials); } @@ -1905,7 +1872,7 @@ export default class MatrixChat extends React.PureComponent { * 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`. */ - onUserCompletedLoginFlow = async (credentials: object, password: string) => { + onUserCompletedLoginFlow = async (credentials: IMatrixClientCreds, password: string) => { this.accountPassword = password; // self-destruct the password after 5mins if (this.accountPasswordTimer !== null) clearTimeout(this.accountPasswordTimer); diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 3aedaa5219..fcb2d274c1 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -1090,42 +1090,7 @@ export default class RoomView extends React.Component { 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: '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 { Promise.resolve().then(() => { const signUrl = this.props.threepidInvite?.signUrl; diff --git a/src/components/views/dialogs/SetMxIdDialog.js b/src/components/views/dialogs/SetMxIdDialog.js deleted file mode 100644 index 090def5e54..0000000000 --- a/src/components/views/dialogs/SetMxIdDialog.js +++ /dev/null @@ -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 = ; - } - const inputClasses = classnames({ - "mx_SetMxIdDialog_input": true, - "error": Boolean(this.state.usernameError), - }); - - let usernameIndicator = null; - if (this.state.usernameBusy) { - usernameIndicator =
{_t("Checking...")}
; - } else { - const usernameAvailable = this.state.username && - this.state.usernameCheckSupport && !this.state.usernameError; - const usernameIndicatorClasses = classnames({ - "error": Boolean(this.state.usernameError), - "success": usernameAvailable, - }); - usernameIndicator =
- { usernameAvailable ? _t('Username available') : this.state.usernameError } -
; - } - - let authErrorIndicator = null; - if (this.state.authError) { - authErrorIndicator =
- { this.state.authError } -
; - } - const canContinue = this.state.username && - !this.state.usernameError && - !this.state.usernameBusy; - - return ( - -
-
- -
- { usernameIndicator } -

- { _t( - 'This will be your account name on the ' + - 'homeserver, or you can pick a different server.', - {}, - { - 'span': { this.props.homeserverUrl }, - 'a': (sub) => { sub }, - }, - ) } -

-

- { _t( - 'If you already have a Matrix account you can log in instead.', - {}, - { 'a': (sub) => { sub } }, - ) } -

- { auth } - { authErrorIndicator } -
-
- -
-
- ); - } -} diff --git a/src/components/views/dialogs/SetPasswordDialog.js b/src/components/views/dialogs/SetPasswordDialog.js deleted file mode 100644 index f2d5a96b4c..0000000000 --- a/src/components/views/dialogs/SetPasswordDialog.js +++ /dev/null @@ -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 -
-

- { advice } -

-

- { extraAdvice } -

-
-
- -
-
; -}; - -/** - * 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 ( - -
-

- { _t('This will allow you to return to your account after signing out, and sign in on other sessions.') } -

- -
- { this.state.error } -
-
-
- ); - } -} diff --git a/src/components/views/elements/AccessibleTooltipButton.tsx b/src/components/views/elements/AccessibleTooltipButton.tsx index 0388c565ad..29e79dc396 100644 --- a/src/components/views/elements/AccessibleTooltipButton.tsx +++ b/src/components/views/elements/AccessibleTooltipButton.tsx @@ -62,7 +62,8 @@ export default class AccessibleTooltipButton extends React.PureComponent { - this.setState({ - cachedPassword: this._sessionStore.getCachedPassword(), - }); - }; - changePassword(oldPassword, newPassword) { const cli = MatrixClientPeg.get(); @@ -119,8 +95,11 @@ export default class ChangePassword extends React.Component { , button: _t("Continue"), extraButtons: [ - , ], @@ -150,9 +129,6 @@ export default class ChangePassword extends React.Component { }); cli.setPassword(authDict, newPassword).then(() => { - // Notify SessionStore that the user's password was changed - dis.dispatch({action: 'password_changed'}); - if (this.props.shouldAskForEmail) { return this._optionallySetEmail().then((confirmed) => { this.props.onFinished({ @@ -212,7 +188,7 @@ export default class ChangePassword extends React.Component { onClickChange = (ev) => { ev.preventDefault(); - const oldPassword = this.state.cachedPassword || this.state.oldPassword; + const oldPassword = this.state.oldPassword; const newPassword = this.state.newPassword; const confirmPassword = this.state.newPasswordConfirm; const err = this.props.onCheckPassword( @@ -231,31 +207,22 @@ export default class ChangePassword extends React.Component { const rowClassName = this.props.rowClassName; const buttonClassName = this.props.buttonClassName; - let currentPassword = null; - if (!this.state.cachedPassword) { - currentPassword = ( -
- -
- ); - } - switch (this.state.phase) { case ChangePassword.Phases.Edit: - const passwordLabel = this.state.cachedPassword ? - _t('Password') : _t('New Password'); return (
- { currentPassword }
+
+
+ ); case ChangePassword.Phases.Uploading: - var Loader = sdk.getComponent("elements.Spinner"); return (
- +
); } diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 9ed6119873..734d3af75a 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -32,7 +32,7 @@ "Continue": "Fortfahren", "Create Room": "Raum erstellen", "Cryptography": "Verschlüsselung", - "Deactivate Account": "Benutzerkonto schließen", + "Deactivate Account": "Benutzerkonto deaktivieren", "Failed to send email": "Fehler beim Senden der E-Mail", "Account": "Benutzerkonto", "Click here to fix": "Zum reparieren hier klicken", @@ -1322,7 +1322,7 @@ "Add Email Address": "E-Mail-Adresse hinzufügen", "Add Phone Number": "Telefonnummer hinzufügen", "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", "View": "Vorschau", "Find a room…": "Suche einen Raum…", @@ -2504,5 +2504,20 @@ "Start a conversation with someone using their name or username (like ).": "Starte ein Gespräch unter Verwendung des Namen oder Benutzernamens des Gegenübers (z. B. ).", "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here": "Das wird sie nicht zu %(communityName)s einladen. Um jemand zu %(communityName)s einzuladen, klicke hier", "Invite someone using their name, username (like ) or share this room.": "Lade jemand mittels seinem/ihrem Namen oder Benutzernamen (z.B. ) ein, oder teile diesem Raum.", - "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 Desktop app to see all encrypted files": "Nutze die Desktop-App um alle verschlüsselten Dateien zu sehen", + "Use the Desktop app to search encrypted messages": "Nutze die Desktop-App 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" } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index ea287894a5..0b68c9dd19 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -401,9 +401,6 @@ "Contact your server admin.": "Contact your server admin.", "Warning": "Warning", "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", "Encryption upgrade available": "Encryption upgrade available", "Verify this session": "Verify this session", @@ -636,7 +633,6 @@ "Export E2E room keys": "Export E2E room keys", "Do you want to set an email address?": "Do you want to set an email address?", "Current password": "Current password", - "Password": "Password", "New Password": "New Password", "Confirm password": "Confirm password", "Change Password": "Change Password", @@ -1817,22 +1813,6 @@ "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.", "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 homeserver, or you can pick a different server.": "This will be your account name on the homeserver, or you can pick a different server.", - "If you already have a Matrix account you can log in instead.": "If you already have a Matrix account you can log in 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", "Link to most recent message": "Link to most recent message", "Share User": "Share User", @@ -1946,6 +1926,7 @@ "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.", "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.", "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:", diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index 50517165d3..7d820b4fce 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -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 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", - "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" } diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index ab00d564d0..0dc3b770dd 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -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 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", - "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" } diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index 8095401bf9..0e76c1a97d 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -2502,5 +2502,27 @@ "Minimize widget": "Widget minimalizá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.", - "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 Desktop app to see all encrypted files": "Minden titkosított fájl eléréséhez használd az Asztali alkalmazást", + "Use the Desktop app to search encrypted messages": "A titkosított üzenetek kereséséhez használd az Asztali alkalmazást", + "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" } diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index 3deba88144..83adb8c173 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -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 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à", - "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" } diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index 946e8a5c89..5dd9c66688 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -1325,7 +1325,7 @@ "Invalid homeserver discovery response": "Неверный ответ при попытке обнаружения домашнего сервера", "Failed to get autodiscovery configuration from server": "Не удалось получить конфигурацию автообнаружения с сервера", "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 base_url for m.identity_server": "Неверный base_url для m.identity_server", "Identity server URL does not appear to be a valid identity server": "URL-адрес сервера идентификации не является действительным сервером идентификации", @@ -2411,7 +2411,7 @@ "Explore public rooms": "Просмотреть публичные комнаты", "Uploading logs": "Загрузка журналов", "Downloading logs": "Скачивание журналов", - "Can't see what you’re looking for?": "Не видите то, что ищете?", + "Can't see what you’re looking for?": "Не нашли, что искали?", "Explore all public rooms": "Просмотреть все публичные комнаты", "%(count)s results|other": "%(count)s результатов", "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 searching encrypted messages": "Эта версия %(brand)s не поддерживает поиск зашифрованных сообщений", "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": "Операция не может быть выполнена" } diff --git a/src/i18n/strings/sl.json b/src/i18n/strings/sl.json index 985e4a39d1..beff9b5bcb 100644 --- a/src/i18n/strings/sl.json +++ b/src/i18n/strings/sl.json @@ -11,5 +11,14 @@ "Custom Server Options": "Možnosti strežnika po meri", "Your language of choice": "Vaš jezik po izbiri", "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" } diff --git a/src/i18n/strings/sq.json b/src/i18n/strings/sq.json index 9d44131ed0..6b61316356 100644 --- a/src/i18n/strings/sq.json +++ b/src/i18n/strings/sq.json @@ -2500,5 +2500,23 @@ "Start a conversation with someone using their name or username (like ).": "Nisni një bisedë me dikë duke përdorur emrin e tij ose emrin e tij të përdoruesit (bie fjala, ).", "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here": "Kjo s’do ta ftojë te %(communityName)s. Që të ftoni dikë te %(communityName)s, klikoni këtu", "Invite someone using their name, username (like ) or share this room.": "Ftoni dikë duke përdorur emrin e tij, emrin e tij të përdoruesit (bie fjala, ) ose ndani me të këtë dhomë.", - "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 Desktop app to see all encrypted files": "Që të shihni krejt kartelat e fshehtëzuara, përdorni aplikacionin për Desktop", + "Use the Desktop app to search encrypted messages": "Që të kërkoni te mesazhe të fshehtëzuar, përdorni aplikacionin për Desktop", + "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." } diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index a5421b2bc0..6283e8b945 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -2439,5 +2439,23 @@ "Start a conversation with someone using their name or username (like ).": "Starta en konversation med någon med deras namn eller användarnamn (som ).", "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here": "Detta kommer inte att bjuda in dem till %(communityName)s. För att bjuda in någon till %(communityName)s, klicka här", "Invite someone using their name, username (like ) or share this room.": "Bjud in någon med deras namn eller användarnamn (som ) eller dela det här rummet.", - "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 Desktop app to see all encrypted files": "Använd skrivbordsappen för att se alla krypterade filer", + "Use the Desktop app to search encrypted messages": "Använd skrivbordsappen 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." } diff --git a/src/i18n/strings/zh_Hans.json b/src/i18n/strings/zh_Hans.json index eaf3b0d329..eeb119dfd4 100644 --- a/src/i18n/strings/zh_Hans.json +++ b/src/i18n/strings/zh_Hans.json @@ -642,7 +642,7 @@ "Sunday": "星期日", "Notification targets": "通知目标", "Today": "今天", - "You are not receiving desktop notifications": "您将不会收到桌面通知", + "You are not receiving desktop notifications": "您现在不会收到桌面通知", "Friday": "星期五", "Update": "更新", "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 your operation.": "如果现在取消,您将无法完成您的操作。", "Cancel entering passphrase?": "取消输入密码?", - "Setting up keys": "设置按键", + "Setting up keys": "设置密钥", "Verify this session": "验证此会话", "Encryption upgrade available": "提供加密升级", "Set up encryption": "设置加密", @@ -2384,5 +2384,19 @@ "Cross-signing is ready for use.": "交叉签名已可用。", "Cross-signing is not set up.": "未设置交叉签名。", "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": "聊天室信息" } diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index 5fdf2b7d85..da93d6c270 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -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 searching encrypted messages": "此版本的 %(brand)s 不支援搜尋加密訊息", "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": "移除其他人傳送的訊息" } diff --git a/src/stores/SessionStore.js b/src/stores/SessionStore.js deleted file mode 100644 index 096811940c..0000000000 --- a/src/stores/SessionStore.js +++ /dev/null @@ -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; diff --git a/src/stores/widgets/StopGapWidget.ts b/src/stores/widgets/StopGapWidget.ts index 1c24f70d0d..9e4d124d5b 100644 --- a/src/stores/widgets/StopGapWidget.ts +++ b/src/stores/widgets/StopGapWidget.ts @@ -66,7 +66,7 @@ class ElementWidget extends Widget { if (WidgetType.JITSI.matches(this.type)) { return WidgetUtils.getLocalJitsiWrapperUrl({ forLocalRender: true, - auth: this.rawData?.auth, + auth: super.rawData?.auth, // this.rawData can call templateUrl, do this to prevent looping }); } return super.templateUrl; diff --git a/src/toasts/SetPasswordToast.ts b/src/toasts/SetPasswordToast.ts deleted file mode 100644 index 88cc317978..0000000000 --- a/src/toasts/SetPasswordToast.ts +++ /dev/null @@ -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); -}; diff --git a/yarn.lock b/yarn.lock index 51ff681783..325f5212b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5928,7 +5928,7 @@ mathml-tag-names@^2.0.1: "matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": 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: "@babel/runtime" "^7.11.2" another-json "^0.2.0"