diff --git a/package.json b/package.json index 1783102b35..c0e489515e 100644 --- a/package.json +++ b/package.json @@ -119,9 +119,12 @@ "@peculiar/webcrypto": "^1.0.22", "@types/classnames": "^2.2.10", "@types/flux": "^3.1.9", + "@types/lodash": "^4.14.152", "@types/modernizr": "^3.5.3", + "@types/node": "^12.12.41", "@types/qrcode": "^1.3.4", - "@types/react": "16.9", + "@types/react": "^16.9", + "@types/react-dom": "^16.9.8", "@types/zxcvbn": "^4.4.0", "babel-eslint": "^10.0.3", "babel-jest": "^24.9.0", diff --git a/res/css/_components.scss b/res/css/_components.scss index 3a6a3257a3..44c63b9df7 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -63,7 +63,6 @@ @import "./views/dialogs/_DeactivateAccountDialog.scss"; @import "./views/dialogs/_DeviceVerifyDialog.scss"; @import "./views/dialogs/_DevtoolsDialog.scss"; -@import "./views/dialogs/_EncryptedEventDialog.scss"; @import "./views/dialogs/_GroupAddressPicker.scss"; @import "./views/dialogs/_IncomingSasDialog.scss"; @import "./views/dialogs/_InviteDialog.scss"; diff --git a/res/css/views/dialogs/_ShareDialog.scss b/res/css/views/dialogs/_ShareDialog.scss index e08469ec6d..e3d2ae8306 100644 --- a/res/css/views/dialogs/_ShareDialog.scss +++ b/res/css/views/dialogs/_ShareDialog.scss @@ -55,6 +55,7 @@ limitations under the License. margin-left: 5px; width: 20px; height: 20px; + background-repeat: none; } .mx_ShareDialog_split { diff --git a/res/css/views/globals/_MatrixToolbar.scss b/res/css/views/globals/_MatrixToolbar.scss index 5fdf572f99..07b92a7235 100644 --- a/res/css/views/globals/_MatrixToolbar.scss +++ b/res/css/views/globals/_MatrixToolbar.scss @@ -67,7 +67,3 @@ limitations under the License. .mx_MatrixToolbar_action { margin-right: 16px; } - -.mx_MatrixToolbar_changelog { - white-space: pre; -} diff --git a/res/css/views/rooms/_IRCLayout.scss b/res/css/views/rooms/_IRCLayout.scss index 5f88473c5f..a8eb35eeed 100644 --- a/res/css/views/rooms/_IRCLayout.scss +++ b/res/css/views/rooms/_IRCLayout.scss @@ -41,7 +41,7 @@ $irc-line-height: $font-18px; } > .mx_EventTile_msgOption { - order: 4; + order: 5; flex-shrink: 0; } @@ -63,6 +63,8 @@ $irc-line-height: $font-18px; flex-direction: column; order: 3; flex-grow: 1; + flex-shrink: 1; + min-width: 0; } > .mx_EventTile_avatar { @@ -90,12 +92,14 @@ $irc-line-height: $font-18px; text-align: right; } - .mx_EventTile_e2eIcon { + > .mx_EventTile_e2eIcon { position: relative; right: unset; left: unset; - top: -2px; padding: 0; + order: 3; + flex-shrink: 0; + flex-grow: 0; } .mx_EventTile_line { @@ -113,7 +117,7 @@ $irc-line-height: $font-18px; } .mx_EventTile_reply { - order: 3; + order: 4; } .mx_EditMessageComposer_buttons { diff --git a/res/css/views/rooms/_RoomTile.scss b/res/css/views/rooms/_RoomTile.scss index 759dce5afa..7f93da0bbf 100644 --- a/res/css/views/rooms/_RoomTile.scss +++ b/res/css/views/rooms/_RoomTile.scss @@ -20,7 +20,7 @@ limitations under the License. flex-direction: row; align-items: center; cursor: pointer; - height: 32px; + height: 34px; margin: 0; padding: 0 8px 0 10px; position: relative; diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index db0591b576..b244993955 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -17,6 +17,8 @@ limitations under the License. import * as ModernizrStatic from "modernizr"; import ContentMessages from "../ContentMessages"; import { IMatrixClientPeg } from "../MatrixClientPeg"; +import ToastStore from "../stores/ToastStore"; +import DeviceListener from "../DeviceListener"; declare global { interface Window { @@ -27,6 +29,8 @@ declare global { }; mx_ContentMessages: ContentMessages; + mx_ToastStore: ToastStore; + mx_DeviceListener: DeviceListener; } // workaround for https://github.com/microsoft/TypeScript/issues/30933 diff --git a/src/Avatar.js b/src/Avatar.js index 8393ce02b2..2cb90eaea6 100644 --- a/src/Avatar.js +++ b/src/Avatar.js @@ -19,6 +19,7 @@ import {MatrixClientPeg} from './MatrixClientPeg'; import DMRoomMap from './utils/DMRoomMap'; import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo"; +// Not to be used for BaseAvatar urls as that has similar default avatar fallback already export function avatarUrlForMember(member, width, height, resizeMethod) { let url; if (member && member.getAvatarUrl) { diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts index d4a6c34daf..ed04c67803 100644 --- a/src/BasePlatform.ts +++ b/src/BasePlatform.ts @@ -180,4 +180,35 @@ export default abstract class BasePlatform { onKeyDown(ev: KeyboardEvent): boolean { return false; // no shortcuts implemented } + + /** + * Get a previously stored pickle key. The pickle key is used for + * encrypting libolm objects. + * @param {string} userId the user ID for the user that the pickle key is for. + * @param {string} userId the device ID that the pickle key is for. + * @returns {string|null} the previously stored pickle key, or null if no + * pickle key has been stored. + */ + async getPickleKey(userId: string, deviceId: string): Promise { + return null; + } + + /** + * Create and store a pickle key for encrypting libolm objects. + * @param {string} userId the user ID for the user that the pickle key is for. + * @param {string} userId the device ID that the pickle key is for. + * @returns {string|null} the pickle key, or null if the platform does not + * support storing pickle keys. + */ + async createPickleKey(userId: string, deviceId: string): Promise { + return null; + } + + /** + * Delete a previously stored pickle key from storage. + * @param {string} userId the user ID for the user that the pickle key is for. + * @param {string} userId the device ID that the pickle key is for. + */ + async destroyPickleKey(userId: string, deviceId: string): Promise { + } } diff --git a/src/DeviceListener.js b/src/DeviceListener.ts similarity index 53% rename from src/DeviceListener.js rename to src/DeviceListener.ts index 27caba971e..ca51b5ac1c 100644 --- a/src/DeviceListener.js +++ b/src/DeviceListener.ts @@ -14,43 +14,43 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { MatrixClientPeg } from './MatrixClientPeg'; +import {MatrixClientPeg} from './MatrixClientPeg'; import SettingsStore from './settings/SettingsStore'; -import * as sdk from './index'; -import { _t } from './languageHandler'; -import ToastStore from './stores/ToastStore'; +import { + hideToast as hideBulkUnverifiedSessionsToast, + showToast as showBulkUnverifiedSessionsToast +} from "./toasts/BulkUnverifiedSessionsToast"; +import { + hideToast as hideSetupEncryptionToast, + Kind as SetupKind, + Kind, + showToast as showSetupEncryptionToast +} from "./toasts/SetupEncryptionToast"; +import { + hideToast as hideUnverifiedSessionsToast, + showToast as showUnverifiedSessionsToast +} from "./toasts/UnverifiedSessionToast"; const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000; -const THIS_DEVICE_TOAST_KEY = 'setupencryption'; -const OTHER_DEVICES_TOAST_KEY = 'reviewsessions'; - -function toastKey(deviceId) { - return "unverified_session_" + deviceId; -} export default class DeviceListener { + // device IDs for which the user has dismissed the verify toast ('Later') + private dismissed = new Set(); + // has the user dismissed any of the various nag toasts to setup encryption on this device? + private dismissedThisDeviceToast = false; + // cache of the key backup info + private keyBackupInfo: object = null; + private keyBackupFetchedAt: number = null; + // We keep a list of our own device IDs so we can batch ones that were already + // there the last time the app launched into a single toast, but display new + // ones in their own toasts. + private ourDeviceIdsAtStart: Set = null; + // The set of device IDs we're currently displaying toasts for + private displayingToastsForDeviceIds = new Set(); + static sharedInstance() { - if (!global.mx_DeviceListener) global.mx_DeviceListener = new DeviceListener(); - return global.mx_DeviceListener; - } - - constructor() { - // device IDs for which the user has dismissed the verify toast ('Later') - this._dismissed = new Set(); - // has the user dismissed any of the various nag toasts to setup encryption on this device? - this._dismissedThisDeviceToast = false; - - // cache of the key backup info - this._keyBackupInfo = null; - this._keyBackupFetchedAt = null; - - // We keep a list of our own device IDs so we can batch ones that were already - // there the last time the app launched into a single toast, but display new - // ones in their own toasts. - this._ourDeviceIdsAtStart = null; - - // The set of device IDs we're currently displaying toasts for - this._displayingToastsForDeviceIds = new Set(); + if (!window.mx_DeviceListener) window.mx_DeviceListener = new DeviceListener(); + return window.mx_DeviceListener; } start() { @@ -74,12 +74,12 @@ export default class DeviceListener { MatrixClientPeg.get().removeListener('accountData', this._onAccountData); MatrixClientPeg.get().removeListener('sync', this._onSync); } - this._dismissed.clear(); - this._dismissedThisDeviceToast = false; - this._keyBackupInfo = null; - this._keyBackupFetchedAt = null; - this._ourDeviceIdsAtStart = null; - this._displayingToastsForDeviceIds = new Set(); + this.dismissed.clear(); + this.dismissedThisDeviceToast = false; + this.keyBackupInfo = null; + this.keyBackupFetchedAt = null; + this.ourDeviceIdsAtStart = null; + this.displayingToastsForDeviceIds = new Set(); } /** @@ -87,29 +87,29 @@ export default class DeviceListener { * * @param {String[]} deviceIds List of device IDs to dismiss notifications for */ - async dismissUnverifiedSessions(deviceIds) { + async dismissUnverifiedSessions(deviceIds: Iterable) { for (const d of deviceIds) { - this._dismissed.add(d); + this.dismissed.add(d); } this._recheck(); } dismissEncryptionSetup() { - this._dismissedThisDeviceToast = true; + this.dismissedThisDeviceToast = true; this._recheck(); } _ensureDeviceIdsAtStartPopulated() { - if (this._ourDeviceIdsAtStart === null) { + if (this.ourDeviceIdsAtStart === null) { const cli = MatrixClientPeg.get(); - this._ourDeviceIdsAtStart = new Set( + this.ourDeviceIdsAtStart = new Set( cli.getStoredDevicesForUser(cli.getUserId()).map(d => d.deviceId), ); } } - _onWillUpdateDevices = async (users, initialFetch) => { + _onWillUpdateDevices = async (users: string[], initialFetch?: boolean) => { // If we didn't know about *any* devices before (ie. it's fresh login), // then they are all pre-existing devices, so ignore this and set the // devicesAtStart list to the devices that we see after the fetch. @@ -122,17 +122,17 @@ export default class DeviceListener { // before we download any new ones. } - _onDevicesUpdated = (users) => { + _onDevicesUpdated = (users: string[]) => { if (!users.includes(MatrixClientPeg.get().getUserId())) return; this._recheck(); } - _onDeviceVerificationChanged = (userId) => { + _onDeviceVerificationChanged = (userId: string) => { if (userId !== MatrixClientPeg.get().getUserId()) return; this._recheck(); } - _onUserTrustStatusChanged = (userId, trustLevel) => { + _onUserTrustStatusChanged = (userId: string) => { if (userId !== MatrixClientPeg.get().getUserId()) return; this._recheck(); } @@ -163,11 +163,11 @@ export default class DeviceListener { // & cache the result async _getKeyBackupInfo() { const now = (new Date()).getTime(); - if (!this._keyBackupInfo || this._keyBackupFetchedAt < now - KEY_BACKUP_POLL_INTERVAL) { - this._keyBackupInfo = await MatrixClientPeg.get().getKeyBackupVersion(); - this._keyBackupFetchedAt = now; + if (!this.keyBackupInfo || this.keyBackupFetchedAt < now - KEY_BACKUP_POLL_INTERVAL) { + this.keyBackupInfo = await MatrixClientPeg.get().getKeyBackupVersion(); + this.keyBackupFetchedAt = now; } - return this._keyBackupInfo; + return this.keyBackupInfo; } async _recheck() { @@ -186,48 +186,25 @@ export default class DeviceListener { const crossSigningReady = await cli.isCrossSigningReady(); - if (this._dismissedThisDeviceToast) { - ToastStore.sharedInstance().dismissToast(THIS_DEVICE_TOAST_KEY); + if (this.dismissedThisDeviceToast || crossSigningReady) { + hideSetupEncryptionToast(); } else { - if (!crossSigningReady) { - // make sure our keys are finished downlaoding - await cli.downloadKeys([cli.getUserId()]); - // cross signing isn't enabled - nag to enable it - // There are 3 different toasts for: - if (cli.getStoredCrossSigningForUser(cli.getUserId())) { - // Cross-signing on account but this device doesn't trust the master key (verify this session) - ToastStore.sharedInstance().addOrReplaceToast({ - key: THIS_DEVICE_TOAST_KEY, - title: _t("Verify this session"), - icon: "verification_warning", - props: {kind: 'verify_this_session'}, - component: sdk.getComponent("toasts.SetupEncryptionToast"), - }); - } else { - const backupInfo = await this._getKeyBackupInfo(); - if (backupInfo) { - // No cross-signing on account but key backup available (upgrade encryption) - ToastStore.sharedInstance().addOrReplaceToast({ - key: THIS_DEVICE_TOAST_KEY, - title: _t("Encryption upgrade available"), - icon: "verification_warning", - props: {kind: 'upgrade_encryption'}, - component: sdk.getComponent("toasts.SetupEncryptionToast"), - }); - } else { - // No cross-signing or key backup on account (set up encryption) - ToastStore.sharedInstance().addOrReplaceToast({ - key: THIS_DEVICE_TOAST_KEY, - title: _t("Set up encryption"), - icon: "verification_warning", - props: {kind: 'set_up_encryption'}, - component: sdk.getComponent("toasts.SetupEncryptionToast"), - }); - } - } + // make sure our keys are finished downloading + await cli.downloadKeys([cli.getUserId()]); + // cross signing isn't enabled - nag to enable it + // There are 3 different toasts for: + if (cli.getStoredCrossSigningForUser(cli.getUserId())) { + // Cross-signing on account but this device doesn't trust the master key (verify this session) + showSetupEncryptionToast(SetupKind.VERIFY_THIS_SESSION); } else { - // cross-signing is ready, and we don't need to upgrade encryption - ToastStore.sharedInstance().dismissToast(THIS_DEVICE_TOAST_KEY); + const backupInfo = await this._getKeyBackupInfo(); + if (backupInfo) { + // No cross-signing on account but key backup available (upgrade encryption) + showSetupEncryptionToast(Kind.UPGRADE_ENCRYPTION); + } else { + // No cross-signing or key backup on account (set up encryption) + showSetupEncryptionToast(Kind.SET_UP_ENCRYPTION); + } } } @@ -239,20 +216,20 @@ export default class DeviceListener { // (technically could just be a boolean: we don't actually // need to remember the device IDs, but for the sake of // symmetry...). - const oldUnverifiedDeviceIds = new Set(); + const oldUnverifiedDeviceIds = new Set(); // Unverified devices that have appeared since then - const newUnverifiedDeviceIds = new Set(); + const newUnverifiedDeviceIds = new Set(); // as long as cross-signing isn't ready, // you can't see or dismiss any device toasts if (crossSigningReady) { const devices = cli.getStoredDevicesForUser(cli.getUserId()); for (const device of devices) { - if (device.deviceId == cli.deviceId) continue; + if (device.deviceId === cli.deviceId) continue; const deviceTrust = await cli.checkDeviceTrust(cli.getUserId(), device.deviceId); - if (!deviceTrust.isCrossSigningVerified() && !this._dismissed.has(device.deviceId)) { - if (this._ourDeviceIdsAtStart.has(device.deviceId)) { + if (!deviceTrust.isCrossSigningVerified() && !this.dismissed.has(device.deviceId)) { + if (this.ourDeviceIdsAtStart.has(device.deviceId)) { oldUnverifiedDeviceIds.add(device.deviceId); } else { newUnverifiedDeviceIds.add(device.deviceId); @@ -263,38 +240,23 @@ export default class DeviceListener { // Display or hide the batch toast for old unverified sessions if (oldUnverifiedDeviceIds.size > 0) { - ToastStore.sharedInstance().addOrReplaceToast({ - key: OTHER_DEVICES_TOAST_KEY, - title: _t("Review where you’re logged in"), - icon: "verification_warning", - priority: ToastStore.PRIORITY_LOW, - props: { - deviceIds: oldUnverifiedDeviceIds, - }, - component: sdk.getComponent("toasts.BulkUnverifiedSessionsToast"), - }); + showBulkUnverifiedSessionsToast(oldUnverifiedDeviceIds); } else { - ToastStore.sharedInstance().dismissToast(OTHER_DEVICES_TOAST_KEY); + hideBulkUnverifiedSessionsToast(); } // Show toasts for new unverified devices if they aren't already there for (const deviceId of newUnverifiedDeviceIds) { - ToastStore.sharedInstance().addOrReplaceToast({ - key: toastKey(deviceId), - title: _t("New login. Was this you?"), - icon: "verification_warning", - props: { deviceId }, - component: sdk.getComponent("toasts.UnverifiedSessionToast"), - }); + showUnverifiedSessionsToast(deviceId); } // ...and hide any we don't need any more - for (const deviceId of this._displayingToastsForDeviceIds) { + for (const deviceId of this.displayingToastsForDeviceIds) { if (!newUnverifiedDeviceIds.has(deviceId)) { - ToastStore.sharedInstance().dismissToast(toastKey(deviceId)); + hideUnverifiedSessionsToast(deviceId); } } - this._displayingToastsForDeviceIds = newUnverifiedDeviceIds; + this.displayingToastsForDeviceIds = newUnverifiedDeviceIds; } } diff --git a/src/KeyRequestHandler.js b/src/KeyRequestHandler.js deleted file mode 100644 index ceaff0c54d..0000000000 --- a/src/KeyRequestHandler.js +++ /dev/null @@ -1,158 +0,0 @@ -/* -Copyright 2017 Vector Creations Ltd -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import * as sdk from './index'; -import Modal from './Modal'; -import SettingsStore from './settings/SettingsStore'; - -// TODO: We can remove this once cross-signing is the only way. -// https://github.com/vector-im/riot-web/issues/11908 -export default class KeyRequestHandler { - constructor(matrixClient) { - this._matrixClient = matrixClient; - - // the user/device for which we currently have a dialog open - this._currentUser = null; - this._currentDevice = null; - - // userId -> deviceId -> [keyRequest] - this._pendingKeyRequests = Object.create(null); - } - - handleKeyRequest(keyRequest) { - // Ignore own device key requests if cross-signing lab enabled - if (SettingsStore.getValue("feature_cross_signing")) { - return; - } - - const userId = keyRequest.userId; - const deviceId = keyRequest.deviceId; - const requestId = keyRequest.requestId; - - if (!this._pendingKeyRequests[userId]) { - this._pendingKeyRequests[userId] = Object.create(null); - } - if (!this._pendingKeyRequests[userId][deviceId]) { - this._pendingKeyRequests[userId][deviceId] = []; - } - - // check if we already have this request - const requests = this._pendingKeyRequests[userId][deviceId]; - if (requests.find((r) => r.requestId === requestId)) { - console.log("Already have this key request, ignoring"); - return; - } - - requests.push(keyRequest); - - if (this._currentUser) { - // ignore for now - console.log("Key request, but we already have a dialog open"); - return; - } - - this._processNextRequest(); - } - - handleKeyRequestCancellation(cancellation) { - // Ignore own device key requests if cross-signing lab enabled - if (SettingsStore.getValue("feature_cross_signing")) { - return; - } - - // see if we can find the request in the queue - const userId = cancellation.userId; - const deviceId = cancellation.deviceId; - const requestId = cancellation.requestId; - - if (userId === this._currentUser && deviceId === this._currentDevice) { - console.log( - "room key request cancellation for the user we currently have a" - + " dialog open for", - ); - // TODO: update the dialog. For now, we just ignore the - // cancellation. - return; - } - - if (!this._pendingKeyRequests[userId]) { - return; - } - const requests = this._pendingKeyRequests[userId][deviceId]; - if (!requests) { - return; - } - const idx = requests.findIndex((r) => r.requestId === requestId); - if (idx < 0) { - return; - } - console.log("Forgetting room key request"); - requests.splice(idx, 1); - if (requests.length === 0) { - delete this._pendingKeyRequests[userId][deviceId]; - if (Object.keys(this._pendingKeyRequests[userId]).length === 0) { - delete this._pendingKeyRequests[userId]; - } - } - } - - _processNextRequest() { - const userId = Object.keys(this._pendingKeyRequests)[0]; - if (!userId) { - return; - } - const deviceId = Object.keys(this._pendingKeyRequests[userId])[0]; - if (!deviceId) { - return; - } - console.log(`Starting KeyShareDialog for ${userId}:${deviceId}`); - - const finished = (r) => { - this._currentUser = null; - this._currentDevice = null; - - if (!this._pendingKeyRequests[userId] || !this._pendingKeyRequests[userId][deviceId]) { - // request was removed in the time the dialog was displayed - this._processNextRequest(); - return; - } - - if (r) { - for (const req of this._pendingKeyRequests[userId][deviceId]) { - req.share(); - } - } - delete this._pendingKeyRequests[userId][deviceId]; - if (Object.keys(this._pendingKeyRequests[userId]).length === 0) { - delete this._pendingKeyRequests[userId]; - } - - this._processNextRequest(); - }; - - const KeyShareDialog = sdk.getComponent("dialogs.KeyShareDialog"); - Modal.appendTrackedDialog('Key Share', 'Process Next Request', KeyShareDialog, { - matrixClient: this._matrixClient, - userId: userId, - deviceId: deviceId, - onFinished: finished, - }); - this._currentUser = userId; - this._currentDevice = deviceId; - } -} - diff --git a/src/Lifecycle.js b/src/Lifecycle.js index 22c5d48317..0494628472 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -298,6 +298,8 @@ async function _restoreFromLocalStorage(opts) { return false; } + const pickleKey = await PlatformPeg.get().getPickleKey(userId, deviceId); + console.log(`Restoring session for ${userId}`); await _doSetLoggedIn({ userId: userId, @@ -306,6 +308,7 @@ async function _restoreFromLocalStorage(opts) { homeserverUrl: hsUrl, identityServerUrl: isUrl, guest: isGuest, + pickleKey: pickleKey, }, false); return true; } else { @@ -348,9 +351,13 @@ async function _handleLoadSessionFailure(e) { * * @returns {Promise} promise which resolves to the new MatrixClient once it has been started */ -export function setLoggedIn(credentials) { +export async function setLoggedIn(credentials) { stopMatrixClient(); - return _doSetLoggedIn(credentials, true); + const pickleKey = credentials.userId && credentials.deviceId + ? await PlatformPeg.get().createPickleKey(credentials.userId, credentials.deviceId) + : null; + + return _doSetLoggedIn(Object.assign({}, credentials, {pickleKey}), true); } /** @@ -516,7 +523,9 @@ export function logout() { } _isLoggingOut = true; - MatrixClientPeg.get().logout().then(onLoggedOut, + const client = MatrixClientPeg.get(); + PlatformPeg.get().destroyPickleKey(client.getUserId(), client.getDeviceId()); + client.logout().then(onLoggedOut, (err) => { // Just throwing an error here is going to be very unhelpful // if you're trying to log out because your server's down and @@ -575,10 +584,12 @@ async function startMatrixClient(startSyncing=true) { // to work). dis.dispatch({action: 'will_start_client'}, true); + // reset things first just in case + TypingStore.sharedInstance().reset(); + ToastStore.sharedInstance().reset(); + Notifier.start(); UserActivity.sharedInstance().start(); - TypingStore.sharedInstance().reset(); // just in case - ToastStore.sharedInstance().reset(); DMRoomMap.makeShared().start(); IntegrationManagers.sharedInstance().startWatching(); ActiveWidgetStore.start(); diff --git a/src/MatrixClientPeg.ts b/src/MatrixClientPeg.ts index ff0750c501..c6ee6c546f 100644 --- a/src/MatrixClientPeg.ts +++ b/src/MatrixClientPeg.ts @@ -41,6 +41,7 @@ export interface IMatrixClientCreds { deviceId: string, accessToken: string, guest: boolean, + pickleKey?: string, } // TODO: Move this to the js-sdk @@ -197,9 +198,7 @@ class _MatrixClientPeg implements IMatrixClientPeg { // The js-sdk found a crypto DB too new for it to use const CryptoStoreTooNewDialog = sdk.getComponent("views.dialogs.CryptoStoreTooNewDialog"); - Modal.createDialog(CryptoStoreTooNewDialog, { - host: window.location.host, - }); + Modal.createDialog(CryptoStoreTooNewDialog); } // this can happen for a number of reasons, the most likely being // that the olm library was missing. It's not fatal. @@ -253,6 +252,7 @@ class _MatrixClientPeg implements IMatrixClientPeg { accessToken: creds.accessToken, userId: creds.userId, deviceId: creds.deviceId, + pickleKey: creds.pickleKey, timelineSupport: true, forceTURN: !SettingsStore.getValue('webRtcAllowPeerToPeer', false), fallbackICEServerAllowed: !!SettingsStore.getValue('fallbackICEServerAllowed'), diff --git a/src/Notifier.js b/src/Notifier.js index 2ffa92452b..cd328ba565 100644 --- a/src/Notifier.js +++ b/src/Notifier.js @@ -26,6 +26,9 @@ import * as sdk from './index'; import { _t } from './languageHandler'; import Modal from './Modal'; import SettingsStore, {SettingLevel} from "./settings/SettingsStore"; +import { + hideToast as hideNotificationsToast, +} from "./toasts/DesktopNotificationsToast"; /* * Dispatches: @@ -278,12 +281,7 @@ const Notifier = { Analytics.trackEvent('Notifier', 'Set Toolbar Hidden', hidden); - // XXX: why are we dispatching this here? - // this is nothing to do with notifier_enabled - dis.dispatch({ - action: "notifier_enabled", - value: this.isEnabled(), - }); + hideNotificationsToast(); // update the info to localStorage for persistent settings if (persistent && global.localStorage) { diff --git a/src/PasswordReset.js b/src/PasswordReset.js index 320599f6d9..9472ddc633 100644 --- a/src/PasswordReset.js +++ b/src/PasswordReset.js @@ -84,8 +84,14 @@ export default class PasswordReset { try { await this.client.setPassword({ + // Note: Though this sounds like a login type for identity servers only, it + // has a dual purpose of being used for homeservers too. type: "m.login.email.identity", + // TODO: Remove `threepid_creds` once servers support proper UIA + // See https://github.com/matrix-org/synapse/issues/5665 + // See https://github.com/matrix-org/matrix-doc/issues/2220 threepid_creds: creds, + threepidCreds: creds, }, this.password); } catch (err) { if (err.httpStatus === 401) { diff --git a/src/async-components/views/dialogs/EncryptedEventDialog.js b/src/async-components/views/dialogs/EncryptedEventDialog.js deleted file mode 100644 index 9eb4439816..0000000000 --- a/src/async-components/views/dialogs/EncryptedEventDialog.js +++ /dev/null @@ -1,206 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from "react"; -import createReactClass from 'create-react-class'; -import PropTypes from 'prop-types'; -import { _t } from '../../../languageHandler'; -import {MatrixClientPeg} from "../../../MatrixClientPeg"; -import {Key} from "../../../Keyboard"; -import * as sdk from "../../../index"; - -// XXX: This component is not cross-signing aware. -// https://github.com/vector-im/riot-web/issues/11752 tracks either updating this -// component or taking it out to pasture. -export default createReactClass({ - displayName: 'EncryptedEventDialog', - - propTypes: { - event: PropTypes.object.isRequired, - onFinished: PropTypes.func.isRequired, - }, - - getInitialState: function() { - return { device: null }; - }, - - componentDidMount: function() { - this._unmounted = false; - const client = MatrixClientPeg.get(); - - // first try to load the device from our store. - // - this.refreshDevice().then((dev) => { - if (dev) { - return dev; - } - - // tell the client to try to refresh the device list for this user - return client.downloadKeys([this.props.event.getSender()], true).then(() => { - return this.refreshDevice(); - }); - }).then((dev) => { - if (this._unmounted) { - return; - } - - this.setState({ device: dev }); - client.on("deviceVerificationChanged", this.onDeviceVerificationChanged); - }, (err)=>{ - console.log("Error downloading devices", err); - }); - }, - - componentWillUnmount: function() { - this._unmounted = true; - const client = MatrixClientPeg.get(); - if (client) { - client.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged); - } - }, - - refreshDevice: function() { - // Promise.resolve to handle transition from static result to promise; can be removed - // in future - return Promise.resolve(MatrixClientPeg.get().getEventSenderDeviceInfo(this.props.event)); - }, - - onDeviceVerificationChanged: function(userId, device) { - if (userId === this.props.event.getSender()) { - this.refreshDevice().then((dev) => { - this.setState({ device: dev }); - }); - } - }, - - onKeyDown: function(e) { - if (e.key === Key.ESCAPE) { - e.stopPropagation(); - e.preventDefault(); - this.props.onFinished(false); - } - }, - - _renderDeviceInfo: function() { - const device = this.state.device; - if (!device) { - return ({ _t('unknown device') }); - } - - let verificationStatus = ({ _t('NOT verified') }); - if (device.isBlocked()) { - verificationStatus = ({ _t('Blacklisted') }); - } else if (device.isVerified()) { - verificationStatus = _t('verified'); - } - - return ( - - - - - - - - - - - - - - - - - - - -
{ _t('Name') }{ device.getDisplayName() }
{ _t('Device ID') }{ device.deviceId }
{ _t('Verification') }{ verificationStatus }
{ _t('Ed25519 fingerprint') }{ device.getFingerprint() }
- ); - }, - - _renderEventInfo: function() { - const event = this.props.event; - - return ( - - - - - - - - - - - - - - - - - - - { - event.getContent().msgtype === 'm.bad.encrypted' ? ( - - - - - ) : null - } - - - - - -
{ _t('User ID') }{ event.getSender() }
{ _t('Curve25519 identity key') }{ event.getSenderKey() || { _t('none') } }
{ _t('Claimed Ed25519 fingerprint key') }{ event.getKeysClaimed().ed25519 || { _t('none') } }
{ _t('Algorithm') }{ event.getWireContent().algorithm || { _t('unencrypted') } }
{ _t('Decryption error') }{ event.getContent().body }
{ _t('Session ID') }{ event.getWireContent().session_id || { _t('none') } }
- ); - }, - - render: function() { - const DeviceVerifyButtons = sdk.getComponent('elements.DeviceVerifyButtons'); - - let buttons = null; - if (this.state.device) { - buttons = ( - - ); - } - - return ( -
-
- { _t('End-to-end encryption information') } -
-
-

{ _t('Event information') }

- { this._renderEventInfo() } - -

{ _t('Sender session information') }

- { this._renderDeviceInfo() } -
-
- - { buttons } -
-
- ); - }, -}); diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index e6ab07c449..d7b79c2cfa 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -201,7 +201,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent { type: 'm.id.user', user: MatrixClientPeg.get().getUserId(), }, - // https://github.com/matrix-org/synapse/issues/5665 + // TODO: Remove `user` once servers support proper UIA + // See https://github.com/matrix-org/synapse/issues/5665 user: MatrixClientPeg.get().getUserId(), password: this.state.accountPassword, }); diff --git a/src/autocomplete/EmojiProvider.tsx b/src/autocomplete/EmojiProvider.tsx index b67e26117b..3a3cec779e 100644 --- a/src/autocomplete/EmojiProvider.tsx +++ b/src/autocomplete/EmojiProvider.tsx @@ -69,7 +69,7 @@ export default class EmojiProvider extends AutocompleteProvider { constructor() { super(EMOJI_REGEX); - this.matcher = new QueryMatcher(EMOJI_SHORTNAMES, { + this.matcher = new QueryMatcher(EMOJI_SHORTNAMES, { keys: ['emoji.emoticon', 'shortname'], funcs: [ (o) => o.emoji.shortcodes.length > 1 ? o.emoji.shortcodes.slice(1).map(s => `:${s}:`).join(" ") : "", // aliases diff --git a/src/autocomplete/QueryMatcher.ts b/src/autocomplete/QueryMatcher.ts index 95bcfb25ef..2c1899d813 100644 --- a/src/autocomplete/QueryMatcher.ts +++ b/src/autocomplete/QueryMatcher.ts @@ -45,7 +45,7 @@ interface IOptions { * @param {function[]} options.funcs List of functions that when called with the * object as an arg will return a string to use as an index */ -export default class QueryMatcher { +export default class QueryMatcher { private _options: IOptions; private _keys: IOptions["keys"]; private _funcs: Required["funcs"]>; @@ -75,7 +75,11 @@ export default class QueryMatcher { this._items = new Map(); for (const object of objects) { - const keyValues = _at(object, this._keys); + // Need to use unsafe coerce here because the objects can have any + // type for their values. We assume that those values who's keys have + // been specified will be string. Also, we cannot infer all the + // types of the keys of the objects at compile. + const keyValues = _at(object, this._keys); for (const f of this._funcs) { keyValues.push(f(object)); diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index f502b4da4b..06ba3e49c9 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -43,6 +43,15 @@ import ResizeNotifier from "../../utils/ResizeNotifier"; import PlatformPeg from "../../PlatformPeg"; import { RoomListStoreTempProxy } from "../../stores/room-list/RoomListStoreTempProxy"; import { DefaultTagID } from "../../stores/room-list/models"; +import { + showToast as showSetPasswordToast, + hideToast as hideSetPasswordToast +} from "../../toasts/SetPasswordToast"; +import { + showToast as showServerLimitToast, + hideToast as hideServerLimitToast +} from "../../toasts/ServerLimitToast"; + // We need to fetch each pinned message individually (if we don't already have it) // so each pinned message may trigger a request. Limit the number per room for sanity. // NB. this is just for server notices rather than pinned messages in general. @@ -65,10 +74,6 @@ interface IProps { initialEventPixelOffset: number; leftDisabled: boolean; rightDisabled: boolean; - showCookieBar: boolean; - hasNewVersion: boolean; - userHasGeneratedPassword: boolean; - showNotifierToolbar: boolean; page_type: string; autoJoin: boolean; thirdPartyInvite?: object; @@ -86,10 +91,8 @@ interface IProps { currentUserId?: string; currentGroupId?: string; currentGroupIsNew?: boolean; - version?: string; - newVersion?: string; - newVersionReleaseNotes?: string; } + interface IState { mouseDown?: { x: number; @@ -97,8 +100,6 @@ interface IState { }; syncErrorData: any; useCompactLayout: boolean; - serverNoticeEvents: MatrixEvent[]; - userHasGeneratedPassword: boolean; } /** @@ -141,11 +142,8 @@ class LoggedInView extends React.PureComponent { this.state = { mouseDown: undefined, syncErrorData: undefined, - userHasGeneratedPassword: false, // use compact timeline view useCompactLayout: SettingsStore.getValue('useCompactLayout'), - // any currently active server notice events - serverNoticeEvents: [], }; // stash the MatrixClient in case we log out before we are unmounted @@ -182,10 +180,7 @@ class LoggedInView extends React.PureComponent { componentDidUpdate(prevProps, prevState) { // attempt to guess when a banner was opened or closed if ( - (prevProps.showCookieBar !== this.props.showCookieBar) || - (prevProps.hasNewVersion !== this.props.hasNewVersion) || - (prevState.userHasGeneratedPassword !== this.state.userHasGeneratedPassword) || - (prevProps.showNotifierToolbar !== this.props.showNotifierToolbar) + (prevProps.checkingForUpdate !== this.props.checkingForUpdate) ) { this.props.resizeNotifier.notifyBannersChanged(); } @@ -220,9 +215,11 @@ class LoggedInView extends React.PureComponent { }; _setStateFromSessionStore = () => { - this.setState({ - userHasGeneratedPassword: Boolean(this._sessionStore.getCachedPassword()), - }); + if (this._sessionStore.getCachedPassword()) { + showSetPasswordToast(); + } else { + hideSetPasswordToast(); + } }; _createResizer() { @@ -294,6 +291,8 @@ class LoggedInView extends React.PureComponent { if (oldSyncState === 'PREPARED' && syncState === 'SYNCING') { this._updateServerNoticeEvents(); + } else { + this._calculateServerLimitToast(data); } }; @@ -304,11 +303,24 @@ class LoggedInView extends React.PureComponent { } }; + _calculateServerLimitToast(syncErrorData, usageLimitEventContent?) { + const error = syncErrorData && syncErrorData.error && syncErrorData.error.errcode === "M_RESOURCE_LIMIT_EXCEEDED"; + if (error) { + usageLimitEventContent = syncErrorData.error.data; + } + + if (usageLimitEventContent) { + showServerLimitToast(usageLimitEventContent.limit_type, usageLimitEventContent.admin_contact, error); + } else { + hideServerLimitToast(); + } + } + _updateServerNoticeEvents = async () => { const roomLists = RoomListStoreTempProxy.getRoomLists(); if (!roomLists[DefaultTagID.ServerNotice]) return []; - const pinnedEvents = []; + const events = []; for (const room of roomLists[DefaultTagID.ServerNotice]) { const pinStateEvent = room.currentState.getStateEvents("m.room.pinned_events", ""); @@ -318,12 +330,18 @@ class LoggedInView extends React.PureComponent { for (const eventId of pinnedEventIds) { const timeline = await this._matrixClient.getEventTimeline(room.getUnfilteredTimelineSet(), eventId, 0); const event = timeline.getEvents().find(ev => ev.getId() === eventId); - if (event) pinnedEvents.push(event); + if (event) events.push(event); } } - this.setState({ - serverNoticeEvents: pinnedEvents, + + const usageLimitEvent = events.find((e) => { + return ( + e && e.getType() === 'm.room.message' && + e.getContent()['server_notice_type'] === 'm.server_notice.usage_limit_reached' + ); }); + + this._calculateServerLimitToast(this.state.syncErrorData, usageLimitEvent && usageLimitEvent.getContent()); }; _onPaste = (ev) => { @@ -599,12 +617,7 @@ class LoggedInView extends React.PureComponent { const GroupView = sdk.getComponent('structures.GroupView'); const MyGroups = sdk.getComponent('structures.MyGroups'); const ToastContainer = sdk.getComponent('structures.ToastContainer'); - const MatrixToolbar = sdk.getComponent('globals.MatrixToolbar'); - const CookieBar = sdk.getComponent('globals.CookieBar'); - const NewVersionBar = sdk.getComponent('globals.NewVersionBar'); const UpdateCheckBar = sdk.getComponent('globals.UpdateCheckBar'); - const PasswordNagBar = sdk.getComponent('globals.PasswordNagBar'); - const ServerLimitBar = sdk.getComponent('globals.ServerLimitBar'); let pageElement; @@ -648,40 +661,9 @@ class LoggedInView extends React.PureComponent { break; } - const usageLimitEvent = this.state.serverNoticeEvents.find((e) => { - return ( - e && e.getType() === 'm.room.message' && - e.getContent()['server_notice_type'] === 'm.server_notice.usage_limit_reached' - ); - }); - let topBar; - if (this.state.syncErrorData && this.state.syncErrorData.error.errcode === 'M_RESOURCE_LIMIT_EXCEEDED') { - topBar = ; - } else if (usageLimitEvent) { - topBar = ; - } else if (this.props.showCookieBar && - this.props.config.piwik && - navigator.doNotTrack !== "1" - ) { - const policyUrl = this.props.config.piwik.policyUrl || null; - topBar = ; - } else if (this.props.hasNewVersion) { - topBar = ; - } else if (this.props.checkingForUpdate) { + if (this.props.checkingForUpdate) { topBar = ; - } else if (this.state.userHasGeneratedPassword) { - topBar = ; - } else if (this.props.showNotifierToolbar) { - topBar = ; } let bodyClasses = 'mx_MatrixChat'; diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index b1076c6ec9..b70d6ed3eb 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -49,7 +49,6 @@ import PageTypes from '../../PageTypes'; import { getHomePageUrl } from '../../utils/pages'; import createRoom from "../../createRoom"; -import KeyRequestHandler from '../../KeyRequestHandler'; import { _t, getCurrentLanguage } from '../../languageHandler'; import SettingsStore, { SettingLevel } from "../../settings/SettingsStore"; import ThemeController from "../../settings/controllers/ThemeController"; @@ -59,8 +58,8 @@ import ResizeNotifier from "../../utils/ResizeNotifier"; import AutoDiscoveryUtils, { ValidatedServerConfig } from "../../utils/AutoDiscoveryUtils"; import DMRoomMap from '../../utils/DMRoomMap'; import { countRoomsWithNotif } from '../../RoomNotifs'; -import { ThemeWatcher } from "../../theme"; -import { FontWatcher } from '../../FontWatcher'; +import ThemeWatcher from "../../settings/watchers/ThemeWatcher"; +import { FontWatcher } from '../../settings/watchers/FontWatcher'; import { storeRoomAliasInCache } from '../../RoomAliasCache'; import { defer, IDeferred } from "../../utils/promise"; import ToastStore from "../../stores/ToastStore"; @@ -68,6 +67,11 @@ import * as StorageManager from "../../utils/StorageManager"; import type LoggedInViewType from "./LoggedInView"; import { ViewUserPayload } from "../../dispatcher/payloads/ViewUserPayload"; import { Action } from "../../dispatcher/actions"; +import { + showToast as showAnalyticsToast, + hideToast as hideAnalyticsToast +} from "../../toasts/AnalyticsToast"; +import {showToast as showNotificationsToast} from "../../toasts/DesktopNotificationsToast"; /** constants for MatrixChat.state.view */ export enum Views { @@ -169,12 +173,7 @@ interface IState { leftDisabled: boolean; middleDisabled: boolean; // the right panel's disabled state is tracked in its store. - version?: string; - newVersion?: string; - hasNewVersion: boolean; - newVersionReleaseNotes?: string; checkingForUpdate?: string; // updateCheckStatusEnum - showCookieBar: boolean; // Parameters used in the registration dance with the IS register_client_secret?: string; register_session_id?: string; @@ -184,7 +183,6 @@ interface IState { hideToSRUsers: boolean; syncError?: Error; resizeNotifier: ResizeNotifier; - showNotifierToolbar: boolean; serverConfig?: ValidatedServerConfig; ready: boolean; thirdPartyInvite?: object; @@ -228,17 +226,12 @@ export default class MatrixChat extends React.PureComponent { leftDisabled: false, middleDisabled: false, - hasNewVersion: false, - newVersionReleaseNotes: null, checkingForUpdate: null, - showCookieBar: false, - hideToSRUsers: false, syncError: null, // If the current syncing status is ERROR, the error object, otherwise null. resizeNotifier: new ResizeNotifier(), - showNotifierToolbar: false, ready: false, }; @@ -339,12 +332,6 @@ export default class MatrixChat extends React.PureComponent { }); } - if (SettingsStore.getValue("showCookieBar")) { - this.setState({ - showCookieBar: true, - }); - } - if (SettingsStore.getValue("analyticsOptIn")) { Analytics.enable(); } @@ -686,9 +673,6 @@ export default class MatrixChat extends React.PureComponent { dis.dispatch({action: 'view_my_groups'}); } break; - case 'notifier_enabled': - this.setState({showNotifierToolbar: Notifier.shouldShowToolbar()}); - break; case 'hide_left_panel': this.setState({ collapseLhs: true, @@ -736,12 +720,6 @@ export default class MatrixChat extends React.PureComponent { case 'client_started': this.onClientStarted(); break; - case 'new_version': - this.onVersion( - payload.currentVersion, payload.newVersion, - payload.releaseNotes, - ); - break; case 'check_updates': this.setState({ checkingForUpdate: payload.value }); break; @@ -761,19 +739,13 @@ export default class MatrixChat extends React.PureComponent { case 'accept_cookies': SettingsStore.setValue("analyticsOptIn", null, SettingLevel.DEVICE, true); SettingsStore.setValue("showCookieBar", null, SettingLevel.DEVICE, false); - - this.setState({ - showCookieBar: false, - }); + hideAnalyticsToast(); Analytics.enable(); break; case 'reject_cookies': SettingsStore.setValue("analyticsOptIn", null, SettingLevel.DEVICE, false); SettingsStore.setValue("showCookieBar", null, SettingLevel.DEVICE, false); - - this.setState({ - showCookieBar: false, - }); + hideAnalyticsToast(); break; } }; @@ -1262,6 +1234,10 @@ export default class MatrixChat extends React.PureComponent { } StorageManager.tryPersistStorage(); + + if (SettingsStore.getValue("showCookieBar") && this.props.config.piwik && navigator.doNotTrack !== "1") { + showAnalyticsToast(this.props.config.piwik && this.props.config.piwik.policyUrl); + } } private showScreenAfterLogin() { @@ -1389,10 +1365,13 @@ export default class MatrixChat extends React.PureComponent { this.firstSyncComplete = true; this.firstSyncPromise.resolve(); + if (Notifier.shouldShowToolbar()) { + showNotificationsToast(); + } + dis.dispatch({action: 'focus_composer'}); this.setState({ ready: true, - showNotifierToolbar: Notifier.shouldShowToolbar(), }); }); cli.on('Call.incoming', function(call) { @@ -1471,16 +1450,6 @@ export default class MatrixChat extends React.PureComponent { cli.on("Session.logged_out", () => dft.stop()); cli.on("Event.decrypted", (e, err) => dft.eventDecrypted(e, err)); - // TODO: We can remove this once cross-signing is the only way. - // https://github.com/vector-im/riot-web/issues/11908 - const krh = new KeyRequestHandler(cli); - cli.on("crypto.roomKeyRequest", (req) => { - krh.handleKeyRequest(req); - }); - cli.on("crypto.roomKeyRequestCancellation", (req) => { - krh.handleKeyRequestCancellation(req); - }); - cli.on("Room", (room) => { if (MatrixClientPeg.get().isCryptoEnabled()) { const blacklistEnabled = SettingsStore.getValueAt( @@ -1570,7 +1539,7 @@ export default class MatrixChat extends React.PureComponent { icon: "verification", props: {request}, component: sdk.getComponent("toasts.VerificationRequestToast"), - priority: ToastStore.PRIORITY_REALTIME, + priority: 90, }); } }); @@ -1844,16 +1813,6 @@ export default class MatrixChat extends React.PureComponent { this.showScreen("settings"); }; - onVersion(current: string, latest: string, releaseNotes?: string) { - this.setState({ - version: current, - newVersion: latest, - hasNewVersion: current !== latest, - newVersionReleaseNotes: releaseNotes, - checkingForUpdate: null, - }); - } - onSendEvent(roomId: string, event: MatrixEvent) { const cli = MatrixClientPeg.get(); if (!cli) { @@ -2048,7 +2007,6 @@ export default class MatrixChat extends React.PureComponent { onCloseAllSettings={this.onCloseAllSettings} onRegistered={this.onRegistered} currentRoomId={this.state.currentRoomId} - showCookieBar={this.state.showCookieBar} /> ); } else { diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index b9daac17b4..c87f4cc4dd 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -164,7 +164,10 @@ export default createReactClass({ canReact: false, canReply: false, + useIRCLayout: SettingsStore.getValue("feature_irc_ui"), + + matrixClientIsReady: this.context && this.context.isInitialSyncComplete(), }; }, @@ -235,7 +238,8 @@ export default createReactClass({ initialEventId: RoomViewStore.getInitialEventId(), isInitialEventHighlighted: RoomViewStore.isInitialEventHighlighted(), forwardingEvent: RoomViewStore.getForwardingEvent(), - shouldPeek: RoomViewStore.shouldPeek(), + // we should only peek once we have a ready client + shouldPeek: this.state.matrixClientIsReady && RoomViewStore.shouldPeek(), showingPinned: SettingsStore.getValue("PinnedEvents.isOpen", roomId), showReadReceipts: SettingsStore.getValue("showReadReceipts", roomId), }; @@ -692,6 +696,16 @@ export default createReactClass({ }); } break; + case 'sync_state': + if (!this.state.matrixClientIsReady) { + this.setState({ + matrixClientIsReady: this.context && this.context.isInitialSyncComplete(), + }, () => { + // send another "initial" RVS update to trigger peeking if needed + this._onRoomViewStoreUpdate(true); + }); + } + break; } }, @@ -1674,14 +1688,16 @@ export default createReactClass({ const ErrorBoundary = sdk.getComponent("elements.ErrorBoundary"); if (!this.state.room) { - const loading = this.state.roomLoading || this.state.peekLoading; + const loading = !this.state.matrixClientIsReady || this.state.roomLoading || this.state.peekLoading; if (loading) { + // Assume preview loading if we don't have a ready client or a room ID (still resolving the alias) + const previewLoading = !this.state.matrixClientIsReady || !this.state.roomId || this.state.peekLoading; return (
- []; + countSeen: number; +} + +export default class ToastContainer extends React.Component<{}, IState> { + constructor(props, context) { + super(props, context); + this.state = { + toasts: ToastStore.sharedInstance().getToasts(), + countSeen: ToastStore.sharedInstance().getCountSeen(), + }; // Start listening here rather than in componentDidMount because // toasts may dismiss themselves in their didMount if they find @@ -35,7 +43,10 @@ export default class ToastContainer extends React.Component { } _onToastStoreUpdate = () => { - this.setState({toasts: ToastStore.sharedInstance().getToasts()}); + this.setState({ + toasts: ToastStore.sharedInstance().getToasts(), + countSeen: ToastStore.sharedInstance().getCountSeen(), + }); }; render() { @@ -51,8 +62,8 @@ export default class ToastContainer extends React.Component { }); let countIndicator; - if (isStacked) { - countIndicator = `(1/${totalCount})`; + if (isStacked || this.state.countSeen > 0) { + countIndicator = ` (${this.state.countSeen + 1}/${this.state.countSeen + totalCount})`; } const toastProps = Object.assign({}, props, { diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.js b/src/components/views/auth/InteractiveAuthEntryComponents.js index 655452fcee..c6d61e38b3 100644 --- a/src/components/views/auth/InteractiveAuthEntryComponents.js +++ b/src/components/views/auth/InteractiveAuthEntryComponents.js @@ -538,6 +538,7 @@ export const MsisdnAuthEntry = createReactClass({ type: MsisdnAuthEntry.LOGIN_TYPE, // TODO: Remove `threepid_creds` once servers support proper UIA // See https://github.com/vector-im/riot-web/issues/10312 + // See https://github.com/matrix-org/matrix-doc/issues/2220 threepid_creds: creds, threepidCreds: creds, }); diff --git a/src/components/views/auth/PassphraseField.tsx b/src/components/views/auth/PassphraseField.tsx index c1781406d9..f09791ce26 100644 --- a/src/components/views/auth/PassphraseField.tsx +++ b/src/components/views/auth/PassphraseField.tsx @@ -36,7 +36,7 @@ interface IProps { labelStrongPassword?: string; labelAllowedButUnsafe?: string; - onChange(ev: KeyboardEvent); + onChange(ev: React.FormEvent); onValidate(result: IValidationResult); } diff --git a/src/components/views/auth/PasswordLogin.js b/src/components/views/auth/PasswordLogin.js index 790c837497..3bd9b557bc 100644 --- a/src/components/views/auth/PasswordLogin.js +++ b/src/components/views/auth/PasswordLogin.js @@ -238,7 +238,7 @@ export default class PasswordLogin extends React.Component { type="text" label={_t("Phone")} value={this.state.phoneNumber} - prefix={phoneCountry} + prefixComponent={phoneCountry} onChange={this.onPhoneNumberChanged} onBlur={this.onPhoneNumberBlur} disabled={this.props.disableSubmit} diff --git a/src/components/views/auth/RegistrationForm.js b/src/components/views/auth/RegistrationForm.js index 7bbd15d8d3..17c65fa94e 100644 --- a/src/components/views/auth/RegistrationForm.js +++ b/src/components/views/auth/RegistrationForm.js @@ -473,7 +473,7 @@ export default createReactClass({ type="text" label={phoneLabel} value={this.state.phoneNumber} - prefix={phoneCountry} + prefixComponent={phoneCountry} onChange={this.onPhoneNumberChange} onValidate={this.onPhoneNumberValidate} />; diff --git a/src/components/views/avatars/MemberAvatar.js b/src/components/views/avatars/MemberAvatar.js index d28d80e62a..b763129dd8 100644 --- a/src/components/views/avatars/MemberAvatar.js +++ b/src/components/views/avatars/MemberAvatar.js @@ -18,10 +18,10 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; import createReactClass from 'create-react-class'; -import * as Avatar from '../../../Avatar'; import * as sdk from "../../../index"; import dis from "../../../dispatcher/dispatcher"; import {Action} from "../../../dispatcher/actions"; +import {MatrixClientPeg} from "../../../MatrixClientPeg"; export default createReactClass({ displayName: 'MemberAvatar', @@ -62,10 +62,14 @@ export default createReactClass({ return { name: props.member.name, title: props.title || props.member.userId, - imageUrl: Avatar.avatarUrlForMember(props.member, - props.width, - props.height, - props.resizeMethod), + imageUrl: props.member.getAvatarUrl( + MatrixClientPeg.get().getHomeserverUrl(), + Math.floor(props.width * window.devicePixelRatio), + Math.floor(props.height * window.devicePixelRatio), + props.resizeMethod, + false, + false, + ), }; } else if (props.fallbackUserId) { return { diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index 2c835e6967..59e3d4c230 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -116,11 +116,6 @@ export default createReactClass({ this.closeMenu(); }, - e2eInfoClicked: function() { - this.props.e2eInfoCallback(); - this.closeMenu(); - }, - onReportEventClick: function() { const ReportEventDialog = sdk.getComponent("dialogs.ReportEventDialog"); Modal.createTrackedDialog('Report Event', '', ReportEventDialog, { @@ -465,15 +460,6 @@ export default createReactClass({ ); } - let e2eInfo; - if (this.props.e2eInfoCallback) { - e2eInfo = ( - - { _t('End-to-end encryption information') } - - ); - } - let reportEventButton; if (mxEvent.getSender() !== me) { reportEventButton = ( @@ -500,7 +486,6 @@ export default createReactClass({ { quoteButton } { externalURLButton } { collapseReplyThread } - { e2eInfo } { reportEventButton }
); diff --git a/src/components/views/dialogs/CryptoStoreTooNewDialog.js b/src/components/views/dialogs/CryptoStoreTooNewDialog.js index 081e84696c..4694619601 100644 --- a/src/components/views/dialogs/CryptoStoreTooNewDialog.js +++ b/src/components/views/dialogs/CryptoStoreTooNewDialog.js @@ -42,11 +42,9 @@ export default (props) => { }; const description = - _t("You've previously used a newer version of Riot on %(host)s. " + + _t("You've previously used a newer version of Riot with this session. " + "To use this version again with end to end encryption, you will " + - "need to sign out and back in again. ", - {host: props.host}, - ); + "need to sign out and back in again."); const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); diff --git a/src/components/views/dialogs/KeyShareDialog.js b/src/components/views/dialogs/KeyShareDialog.js deleted file mode 100644 index 8ef36bb59f..0000000000 --- a/src/components/views/dialogs/KeyShareDialog.js +++ /dev/null @@ -1,178 +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. -*/ - -import Modal from '../../../Modal'; -import React from 'react'; -import createReactClass from 'create-react-class'; -import PropTypes from 'prop-types'; -import * as sdk from '../../../index'; - -import { _t, _td } from '../../../languageHandler'; - -// TODO: We can remove this once cross-signing is the only way. -// https://github.com/vector-im/riot-web/issues/11908 - -/** - * Dialog which asks the user whether they want to share their keys with - * an unverified device. - * - * onFinished is called with `true` if the key should be shared, `false` if it - * should not, and `undefined` if the dialog is cancelled. (In other words: - * truthy: do the key share. falsy: don't share the keys). - */ -export default createReactClass({ - propTypes: { - matrixClient: PropTypes.object.isRequired, - userId: PropTypes.string.isRequired, - deviceId: PropTypes.string.isRequired, - onFinished: PropTypes.func.isRequired, - }, - - getInitialState: function() { - return { - deviceInfo: null, - wasNewDevice: false, - }; - }, - - componentDidMount: function() { - this._unmounted = false; - const userId = this.props.userId; - const deviceId = this.props.deviceId; - - // give the client a chance to refresh the device list - this.props.matrixClient.downloadKeys([userId], false).then((r) => { - if (this._unmounted) { return; } - - const deviceInfo = r[userId][deviceId]; - - if (!deviceInfo) { - console.warn(`No details found for session ${userId}:${deviceId}`); - - this.props.onFinished(false); - return; - } - - const wasNewDevice = !deviceInfo.isKnown(); - - this.setState({ - deviceInfo: deviceInfo, - wasNewDevice: wasNewDevice, - }); - - // if the device was new before, it's not any more. - if (wasNewDevice) { - this.props.matrixClient.setDeviceKnown( - userId, - deviceId, - true, - ); - } - }); - }, - - componentWillUnmount: function() { - this._unmounted = true; - }, - - - _onVerifyClicked: function() { - const DeviceVerifyDialog = sdk.getComponent('views.dialogs.DeviceVerifyDialog'); - - console.log("KeyShareDialog: Starting verify dialog"); - Modal.createTrackedDialog('Key Share', 'Starting dialog', DeviceVerifyDialog, { - userId: this.props.userId, - device: this.state.deviceInfo, - onFinished: (verified) => { - if (verified) { - // can automatically share the keys now. - this.props.onFinished(true); - } - }, - }, null, /* priority = */ false, /* static = */ true); - }, - - _onShareClicked: function() { - console.log("KeyShareDialog: User clicked 'share'"); - this.props.onFinished(true); - }, - - _onIgnoreClicked: function() { - console.log("KeyShareDialog: User clicked 'ignore'"); - this.props.onFinished(false); - }, - - _renderContent: function() { - const displayName = this.state.deviceInfo.getDisplayName() || - this.state.deviceInfo.deviceId; - - let text; - if (this.state.wasNewDevice) { - text = _td("You added a new session '%(displayName)s', which is" - + " requesting encryption keys."); - } else { - text = _td("Your unverified session '%(displayName)s' is requesting" - + " encryption keys."); - } - text = _t(text, {displayName: displayName}); - - return ( -
-

{ text }

- -
- - - -
-
- ); - }, - - render: function() { - const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); - const Spinner = sdk.getComponent('views.elements.Spinner'); - - let content; - - if (this.state.deviceInfo) { - content = this._renderContent(); - } else { - content = ( -
-

{ _t('Loading session info...') }

- -
- ); - } - - return ( - - { content } - - ); - }, -}); diff --git a/src/components/views/elements/Field.js b/src/components/views/elements/Field.tsx similarity index 64% rename from src/components/views/elements/Field.js rename to src/components/views/elements/Field.tsx index 2ebb90da26..771d2182ea 100644 --- a/src/components/views/elements/Field.js +++ b/src/components/views/elements/Field.tsx @@ -15,10 +15,10 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; import classNames from 'classnames'; import * as sdk from '../../../index'; import { debounce } from 'lodash'; +import {IFieldState, IValidationResult} from "../elements/Validation"; // Invoke validation from user input (when typing, etc.) at most once every N ms. const VALIDATION_THROTTLE_MS = 200; @@ -29,58 +29,93 @@ function getId() { return `${BASE_ID}_${count++}`; } -export default class Field extends React.PureComponent { - static propTypes = { - // The field's ID, which binds the input and label together. Immutable. - id: PropTypes.string, - // The element to create. Defaults to "input". - // To define options for a select, use - element: PropTypes.oneOf(["input", "select", "textarea"]), - // The field's type (when used as an ). Defaults to "text". - type: PropTypes.string, - // id of a element for suggestions - list: PropTypes.string, - // The field's label string. - label: PropTypes.string, - // The field's placeholder string. Defaults to the label. - placeholder: PropTypes.string, - // The field's value. - // This is a controlled component, so the value is required. - value: PropTypes.string.isRequired, - // Optional component to include inside the field before the input. - prefix: PropTypes.node, - // Optional component to include inside the field after the input. - postfix: PropTypes.node, - // The callback called whenever the contents of the field - // changes. Returns an object with `valid` boolean field - // and a `feedback` react component field to provide feedback - // to the user. - onValidate: PropTypes.func, - // If specified, overrides the value returned by onValidate. - flagInvalid: PropTypes.bool, - // If specified, contents will appear as a tooltip on the element and - // validation feedback tooltips will be suppressed. - tooltipContent: PropTypes.node, - // If specified alongside tooltipContent, the class name to apply to the - // tooltip itself. - tooltipClassName: PropTypes.string, - // If specified, an additional class name to apply to the field container - className: PropTypes.string, - // All other props pass through to the . - }; +interface IProps extends React.InputHTMLAttributes { + // The field's ID, which binds the input and label together. Immutable. + id?: string, + // The element to create. Defaults to "input". + // To define options for a select, use + element?: "input" | "select" | "textarea", + // The field's type (when used as an ). Defaults to "text". + type?: string, + // id of a element for suggestions + list?: string, + // The field's label string. + label?: string, + // The field's placeholder string. Defaults to the label. + placeholder?: string, + // The field's value. + // This is a controlled component, so the value is required. + value: string, + // Optional component to include inside the field before the input. + prefixComponent?: React.ReactNode, + // Optional component to include inside the field after the input. + postfixComponent?: React.ReactNode, + // The callback called whenever the contents of the field + // changes. Returns an object with `valid` boolean field + // and a `feedback` react component field to provide feedback + // to the user. + onValidate?: (input: IFieldState) => Promise, + // If specified, overrides the value returned by onValidate. + flagInvalid?: boolean, + // If specified, contents will appear as a tooltip on the element and + // validation feedback tooltips will be suppressed. + tooltipContent?: React.ReactNode, + // If specified alongside tooltipContent, the class name to apply to the + // tooltip itself. + tooltipClassName?: string, + // If specified, an additional class name to apply to the field container + className?: string, + // All other props pass through to the . +} + +interface IState { + valid: boolean, + feedback: React.ReactNode, + feedbackVisible: boolean, + focused: boolean, +} + +export default class Field extends React.PureComponent { + private id: string; + private input: HTMLInputElement; + + private static defaultProps = { + element: "input", + type: "text", + } + + /* + * This was changed from throttle to debounce: this is more traditional for + * form validation since it means that the validation doesn't happen at all + * until the user stops typing for a bit (debounce defaults to not running on + * the leading edge). If we're doing an HTTP hit on each validation, we have more + * incentive to prevent validating input that's very unlikely to be valid. + * We may find that we actually want different behaviour for registration + * fields, in which case we can add some options to control it. + */ + private validateOnChange = debounce(() => { + this.validate({ + focused: true, + }); + }, VALIDATION_THROTTLE_MS); constructor(props) { super(props); this.state = { valid: undefined, feedback: undefined, + feedbackVisible: false, focused: false, }; this.id = this.props.id || getId(); } - onFocus = (ev) => { + public focus() { + this.input.focus(); + } + + private onFocus = (ev) => { this.setState({ focused: true, }); @@ -93,7 +128,7 @@ export default class Field extends React.PureComponent { } }; - onChange = (ev) => { + private onChange = (ev) => { this.validateOnChange(); // Parent component may have supplied its own `onChange` as well if (this.props.onChange) { @@ -101,7 +136,7 @@ export default class Field extends React.PureComponent { } }; - onBlur = (ev) => { + private onBlur = (ev) => { this.setState({ focused: false, }); @@ -114,11 +149,7 @@ export default class Field extends React.PureComponent { } }; - focus() { - this.input.focus(); - } - - async validate({ focused, allowEmpty = true }) { + private async validate({ focused, allowEmpty = true }: {focused: boolean, allowEmpty?: boolean}) { if (!this.props.onValidate) { return; } @@ -149,56 +180,42 @@ export default class Field extends React.PureComponent { } } - /* - * This was changed from throttle to debounce: this is more traditional for - * form validation since it means that the validation doesn't happen at all - * until the user stops typing for a bit (debounce defaults to not running on - * the leading edge). If we're doing an HTTP hit on each validation, we have more - * incentive to prevent validating input that's very unlikely to be valid. - * We may find that we actually want different behaviour for registration - * fields, in which case we can add some options to control it. - */ - validateOnChange = debounce(() => { - this.validate({ - focused: true, - }); - }, VALIDATION_THROTTLE_MS); - render() { + + public render() { const { - element, prefix, postfix, className, onValidate, children, + element, prefixComponent, postfixComponent, className, onValidate, children, tooltipContent, flagInvalid, tooltipClassName, list, ...inputProps} = this.props; - const inputElement = element || "input"; - // Set some defaults for the element - inputProps.type = inputProps.type || "text"; - inputProps.ref = input => this.input = input; + const ref = input => this.input = input; inputProps.placeholder = inputProps.placeholder || inputProps.label; inputProps.id = this.id; // this overwrites the id from props inputProps.onFocus = this.onFocus; inputProps.onChange = this.onChange; inputProps.onBlur = this.onBlur; - inputProps.list = list; - const fieldInput = React.createElement(inputElement, inputProps, children); + // Appease typescript's inference + const inputProps_ = {...inputProps, ref, list}; + + const fieldInput = React.createElement(this.props.element, inputProps_, children); let prefixContainer = null; - if (prefix) { - prefixContainer = {prefix}; + if (prefixComponent) { + prefixContainer = {prefixComponent}; } let postfixContainer = null; - if (postfix) { - postfixContainer = {postfix}; + if (postfixComponent) { + postfixContainer = {postfixComponent}; } const hasValidationFlag = flagInvalid !== null && flagInvalid !== undefined; - const fieldClasses = classNames("mx_Field", `mx_Field_${inputElement}`, className, { + const fieldClasses = classNames("mx_Field", `mx_Field_${this.props.element}`, className, { // If we have a prefix element, leave the label always at the top left and // don't animate it, as it looks a bit clunky and would add complexity to do // properly. - mx_Field_labelAlwaysTopLeft: prefix, + mx_Field_labelAlwaysTopLeft: prefixComponent, mx_Field_valid: onValidate && this.state.valid === true, mx_Field_invalid: hasValidationFlag ? flagInvalid diff --git a/src/components/views/elements/RoomAliasField.js b/src/components/views/elements/RoomAliasField.js index ee18913971..04bbe1c3de 100644 --- a/src/components/views/elements/RoomAliasField.js +++ b/src/components/views/elements/RoomAliasField.js @@ -47,8 +47,8 @@ export default class RoomAliasField extends React.PureComponent { this._fieldRef = ref} onValidate={this._onValidate} placeholder={_t("e.g. my-room")} diff --git a/src/components/views/elements/Tooltip.js b/src/components/views/elements/Tooltip.tsx similarity index 69% rename from src/components/views/elements/Tooltip.js rename to src/components/views/elements/Tooltip.tsx index 4807ade3db..133b0ecf58 100644 --- a/src/components/views/elements/Tooltip.js +++ b/src/components/views/elements/Tooltip.tsx @@ -2,7 +2,7 @@ Copyright 2015, 2016 OpenMarket Ltd Copyright 2019 New Vector Ltd Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2019, 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -18,67 +18,68 @@ limitations under the License. */ -import React from 'react'; +import React, { Component } from 'react'; import ReactDOM from 'react-dom'; -import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import dis from '../../../dispatcher/dispatcher'; import classNames from 'classnames'; +import { ViewTooltipPayload } from '../../../dispatcher/payloads/ViewTooltipPayload'; +import { Action } from '../../../dispatcher/actions'; const MIN_TOOLTIP_HEIGHT = 25; -export default createReactClass({ - displayName: 'Tooltip', - - propTypes: { +interface IProps { // Class applied to the element used to position the tooltip - className: PropTypes.string, + className: string, // Class applied to the tooltip itself - tooltipClassName: PropTypes.string, + tooltipClassName?: string, // Whether the tooltip is visible or hidden. // The hidden state allows animating the tooltip away via CSS. // Defaults to visible if unset. - visible: PropTypes.bool, + visible?: boolean, // the react element to put into the tooltip - label: PropTypes.node, - }, + label: React.ReactNode, +} - getDefaultProps() { - return { - visible: true, - }; - }, +export default class Tooltip extends React.Component { + private tooltipContainer: HTMLElement; + private tooltip: void | Element | Component; + private parent: Element; + + + public static readonly defaultProps = { + visible: true, + }; // Create a wrapper for the tooltip outside the parent and attach it to the body element - componentDidMount: function() { + public componentDidMount() { this.tooltipContainer = document.createElement("div"); this.tooltipContainer.className = "mx_Tooltip_wrapper"; document.body.appendChild(this.tooltipContainer); - window.addEventListener('scroll', this._renderTooltip, true); + window.addEventListener('scroll', this.renderTooltip, true); - this.parent = ReactDOM.findDOMNode(this).parentNode; + this.parent = ReactDOM.findDOMNode(this).parentNode as Element; - this._renderTooltip(); - }, + this.renderTooltip(); + } - componentDidUpdate: function() { - this._renderTooltip(); - }, + public componentDidUpdate() { + this.renderTooltip(); + } // Remove the wrapper element, as the tooltip has finished using it - componentWillUnmount: function() { - dis.dispatch({ - action: 'view_tooltip', + public componentWillUnmount() { + dis.dispatch({ + action: Action.ViewTooltip, tooltip: null, parent: null, }); ReactDOM.unmountComponentAtNode(this.tooltipContainer); document.body.removeChild(this.tooltipContainer); - window.removeEventListener('scroll', this._renderTooltip, true); - }, + window.removeEventListener('scroll', this.renderTooltip, true); + } - _updatePosition(style) { + private updatePosition(style: {[key: string]: any}) { const parentBox = this.parent.getBoundingClientRect(); let offset = 0; if (parentBox.height > MIN_TOOLTIP_HEIGHT) { @@ -91,16 +92,15 @@ export default createReactClass({ style.top = (parentBox.top - 2) + window.pageYOffset + offset; style.left = 6 + parentBox.right + window.pageXOffset; return style; - }, + } - _renderTooltip: function() { + private renderTooltip() { // Add the parent's position to the tooltips, so it's correctly // positioned, also taking into account any window zoom // NOTE: The additional 6 pixels for the left position, is to take account of the // tooltips chevron - const parent = ReactDOM.findDOMNode(this).parentNode; - let style = {}; - style = this._updatePosition(style); + const parent = ReactDOM.findDOMNode(this).parentNode as Element; + const style = this.updatePosition({}); // Hide the entire container when not visible. This prevents flashing of the tooltip // if it is not meant to be visible on first mount. style.display = this.props.visible ? "block" : "none"; @@ -118,21 +118,21 @@ export default createReactClass({ ); // Render the tooltip manually, as we wish it not to be rendered within the parent - this.tooltip = ReactDOM.render(tooltip, this.tooltipContainer); + this.tooltip = ReactDOM.render(tooltip, this.tooltipContainer); // Tell the roomlist about us so it can manipulate us if it wishes - dis.dispatch({ - action: 'view_tooltip', + dis.dispatch({ + action: Action.ViewTooltip, tooltip: this.tooltip, parent: parent, }); - }, + } - render: function() { + public render() { // Render a placeholder return (
); - }, -}); + } +} diff --git a/src/components/views/globals/CookieBar.js b/src/components/views/globals/CookieBar.js deleted file mode 100644 index bf264686d0..0000000000 --- a/src/components/views/globals/CookieBar.js +++ /dev/null @@ -1,103 +0,0 @@ -/* -Copyright 2018 New Vector Ltd. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import PropTypes from 'prop-types'; -import dis from '../../../dispatcher/dispatcher'; -import { _t } from '../../../languageHandler'; -import * as sdk from '../../../index'; -import Analytics from '../../../Analytics'; - -export default class CookieBar extends React.Component { - static propTypes = { - policyUrl: PropTypes.string, - } - - constructor() { - super(); - } - - onUsageDataClicked(e) { - e.stopPropagation(); - e.preventDefault(); - Analytics.showDetailsModal(); - } - - onAccept() { - dis.dispatch({ - action: 'accept_cookies', - }); - } - - onReject() { - dis.dispatch({ - action: 'reject_cookies', - }); - } - - render() { - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); - const toolbarClasses = "mx_MatrixToolbar"; - return ( -
- -
- { this.props.policyUrl ? _t( - "Please help improve Riot.im by sending anonymous usage data. " + - "This will use a cookie " + - "(please see our Cookie Policy).", - {}, - { - 'UsageDataLink': (sub) => - { sub } - , - // XXX: We need to link to the page that explains our cookies - 'PolicyLink': (sub) => - { sub } - - , - }, - ) : _t( - "Please help improve Riot.im by sending anonymous usage data. " + - "This will use a cookie.", - {}, - { - 'UsageDataLink': (sub) => - { sub } - , - }, - ) } -
- - { _t("Yes, I want to help!") } - - - {_t('Close')} - -
- ); - } -} diff --git a/src/components/views/globals/MatrixToolbar.js b/src/components/views/globals/MatrixToolbar.js deleted file mode 100644 index 758e4d62aa..0000000000 --- a/src/components/views/globals/MatrixToolbar.js +++ /dev/null @@ -1,45 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import createReactClass from 'create-react-class'; -import { _t } from '../../../languageHandler'; -import Notifier from '../../../Notifier'; -import AccessibleButton from '../../../components/views/elements/AccessibleButton'; - -export default createReactClass({ - displayName: 'MatrixToolbar', - - hideToolbar: function() { - Notifier.setToolbarHidden(true); - }, - - onClick: function() { - Notifier.setEnabled(true); - }, - - render: function() { - return ( -
- -
- { _t('You are not receiving desktop notifications') } { _t('Enable them now') } -
- {_t('Close')} -
- ); - }, -}); diff --git a/src/components/views/globals/NewVersionBar.js b/src/components/views/globals/NewVersionBar.js deleted file mode 100644 index dedccdc6b6..0000000000 --- a/src/components/views/globals/NewVersionBar.js +++ /dev/null @@ -1,108 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket 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 createReactClass from 'create-react-class'; -import * as sdk from '../../../index'; -import Modal from '../../../Modal'; -import PlatformPeg from '../../../PlatformPeg'; -import { _t } from '../../../languageHandler'; - -/** - * Check a version string is compatible with the Changelog - * dialog ([vectorversion]-react-[react-sdk-version]-js-[js-sdk-version]) - */ -function checkVersion(ver) { - const parts = ver.split('-'); - return parts.length == 5 && parts[1] == 'react' && parts[3] == 'js'; -} - -export default createReactClass({ - propTypes: { - version: PropTypes.string.isRequired, - newVersion: PropTypes.string.isRequired, - releaseNotes: PropTypes.string, - }, - - displayReleaseNotes: function(releaseNotes) { - const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog'); - Modal.createTrackedDialog('Display release notes', '', QuestionDialog, { - title: _t("What's New"), - description:
{releaseNotes}
, - button: _t("Update"), - onFinished: (update) => { - if (update && PlatformPeg.get()) { - PlatformPeg.get().installUpdate(); - } - }, - }); - }, - - displayChangelog: function() { - const ChangelogDialog = sdk.getComponent('dialogs.ChangelogDialog'); - Modal.createTrackedDialog('Display Changelog', '', ChangelogDialog, { - version: this.props.version, - newVersion: this.props.newVersion, - onFinished: (update) => { - if (update && PlatformPeg.get()) { - PlatformPeg.get().installUpdate(); - } - }, - }); - }, - - onUpdateClicked: function() { - PlatformPeg.get().installUpdate(); - }, - - render: function() { - let action_button; - // If we have release notes to display, we display them. Otherwise, - // we display the Changelog Dialog which takes two versions and - // automatically tells you what's changed (provided the versions - // are in the right format) - if (this.props.releaseNotes) { - action_button = ( - - ); - } else if (checkVersion(this.props.version) && checkVersion(this.props.newVersion)) { - action_button = ( - - ); - } else if (PlatformPeg.get()) { - action_button = ( - - ); - } - return ( -
- -
- {_t("A new version of Riot is available.")} -
- {action_button} -
- ); - }, -}); diff --git a/src/components/views/globals/PasswordNagBar.js b/src/components/views/globals/PasswordNagBar.js deleted file mode 100644 index 74735ca5ea..0000000000 --- a/src/components/views/globals/PasswordNagBar.js +++ /dev/null @@ -1,53 +0,0 @@ -/* -Copyright 2017 Vector Creations Ltd -Copyright 2018 New Vector Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import createReactClass from 'create-react-class'; -import * as sdk from '../../../index'; -import Modal from '../../../Modal'; -import { _t } from '../../../languageHandler'; - -export default createReactClass({ - onUpdateClicked: function() { - const SetPasswordDialog = sdk.getComponent('dialogs.SetPasswordDialog'); - Modal.createTrackedDialog('Set Password Dialog', 'Password Nag Bar', SetPasswordDialog); - }, - - render: function() { - const toolbarClasses = "mx_MatrixToolbar mx_MatrixToolbar_clickable"; - return ( -
- -
- { _t( - "To return to your account in future you need to set a password", - {}, - { 'u': (sub) => { sub } }, - ) } -
- -
- ); - }, -}); diff --git a/src/components/views/globals/ServerLimitBar.js b/src/components/views/globals/ServerLimitBar.js deleted file mode 100644 index 7d414a2826..0000000000 --- a/src/components/views/globals/ServerLimitBar.js +++ /dev/null @@ -1,99 +0,0 @@ -/* -Copyright 2018 New Vector Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; -import classNames from 'classnames'; -import { _td } from '../../../languageHandler'; -import { messageForResourceLimitError } from '../../../utils/ErrorUtils'; - -export default createReactClass({ - propTypes: { - // 'hard' if the logged in user has been locked out, 'soft' if they haven't - kind: PropTypes.string, - adminContact: PropTypes.string, - // The type of limit that has been hit. - limitType: PropTypes.string.isRequired, - }, - - getDefaultProps: function() { - return { - kind: 'hard', - }; - }, - - render: function() { - const toolbarClasses = { - 'mx_MatrixToolbar': true, - }; - - let adminContact; - let limitError; - if (this.props.kind === 'hard') { - toolbarClasses['mx_MatrixToolbar_error'] = true; - - adminContact = messageForResourceLimitError( - this.props.limitType, - this.props.adminContact, - { - '': _td("Please contact your service administrator to continue using the service."), - }, - ); - limitError = messageForResourceLimitError( - this.props.limitType, - this.props.adminContact, - { - 'monthly_active_user': _td("This homeserver has hit its Monthly Active User limit."), - '': _td("This homeserver has exceeded one of its resource limits."), - }, - ); - } else { - toolbarClasses['mx_MatrixToolbar_info'] = true; - adminContact = messageForResourceLimitError( - this.props.limitType, - this.props.adminContact, - { - '': _td("Please contact your service administrator to get this limit increased."), - }, - ); - limitError = messageForResourceLimitError( - this.props.limitType, - this.props.adminContact, - { - 'monthly_active_user': _td( - "This homeserver has hit its Monthly Active User limit so " + - "some users will not be able to log in.", - ), - '': _td( - "This homeserver has exceeded one of its resource limits so " + - "some users will not be able to log in.", - ), - }, - {'b': sub => {sub}}, - ); - } - return ( -
-
- {limitError} - {' '} - {adminContact} -
-
- ); - }, -}); diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js index 48b9c58cb8..95eb37b588 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.js @@ -1,7 +1,7 @@ /* Copyright 2019 New Vector Ltd Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2019, 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -22,11 +22,9 @@ import PropTypes from 'prop-types'; import { _t } from '../../../languageHandler'; import * as sdk from '../../../index'; import dis from '../../../dispatcher/dispatcher'; -import Modal from '../../../Modal'; import {aboveLeftOf, ContextMenu, ContextMenuButton, useContextMenu} from '../../structures/ContextMenu'; import { isContentActionable, canEditContent } from '../../../utils/EventUtils'; import RoomContext from "../../../contexts/RoomContext"; -import SettingsStore from '../../../settings/SettingsStore'; const OptionsButton = ({mxEvent, getTile, getReplyThread, permalinkCreator, onFocusChange}) => { const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu(); @@ -41,18 +39,6 @@ const OptionsButton = ({mxEvent, getTile, getReplyThread, permalinkCreator, onFo const tile = getTile && getTile(); const replyThread = getReplyThread && getReplyThread(); - const onCryptoClick = () => { - Modal.createTrackedDialogAsync('Encrypted Event Dialog', '', - import('../../../async-components/views/dialogs/EncryptedEventDialog'), - {event: mxEvent}, - ); - }; - - let e2eInfoCallback = null; - if (mxEvent.isEncrypted() && !SettingsStore.getValue("feature_cross_signing")) { - e2eInfoCallback = onCryptoClick; - } - const buttonRect = button.current.getBoundingClientRect(); contextMenu = ; diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index ee0b40c0de..a822875a18 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -802,6 +802,8 @@ export default createReactClass({ const groupTimestamp = !this.props.useIRCLayout ? linkedTimestamp : null; const ircTimestamp = this.props.useIRCLayout ? linkedTimestamp : null; + const groupPadlock = !this.props.useIRCLayout && !isBubbleMessage && this._renderE2EPadlock(); + const ircPadlock = this.props.useIRCLayout && !isBubbleMessage && this._renderE2EPadlock(); switch (this.props.tileShape) { case 'notif': { @@ -873,9 +875,10 @@ export default createReactClass({ { ircTimestamp } { avatar } { sender } + { ircPadlock }
{ groupTimestamp } - { !isBubbleMessage && this._renderE2EPadlock() } + { groupPadlock } { thread } { sender } + { ircPadlock }
{ groupTimestamp } - { !isBubbleMessage && this._renderE2EPadlock() } + { groupPadlock } { thread } { hover: false, notificationState: this.getNotificationState(), }; + + this.props.room.on("Room.receipt", this.handleRoomEventUpdate); + this.props.room.on("Room.timeline", this.handleRoomEventUpdate); + this.props.room.on("Room.redaction", this.handleRoomEventUpdate); + MatrixClientPeg.get().on("Event.decrypted", this.handleRoomEventUpdate); } public componentWillUnmount() { - // TODO: Listen for changes to the badge count and update as needed + if (this.props.room) { + this.props.room.removeListener("Room.receipt", this.handleRoomEventUpdate); + this.props.room.removeListener("Room.timeline", this.handleRoomEventUpdate); + this.props.room.removeListener("Room.redaction", this.handleRoomEventUpdate); + } + if (MatrixClientPeg.get()) { + MatrixClientPeg.get().removeListener("Event.decrypted", this.handleRoomEventUpdate); + } } // XXX: This is a bit of an awful-looking hack. We should probably be using state for @@ -99,7 +113,15 @@ export default class RoomTile2 extends React.Component { return getEffectiveMembership(this.props.room.getMyMembership()) === EffectiveMembership.Invite; } - // TODO: Make use of this function when the notification state needs updating. + private handleRoomEventUpdate = (event: MatrixEvent) => { + const roomId = event.getRoomId(); + + // Sanity check: should never happen + if (roomId !== this.props.room.roomId) return; + + this.updateNotificationState(); + }; + private updateNotificationState() { this.setState({notificationState: this.getNotificationState()}); } @@ -214,7 +236,7 @@ export default class RoomTile2 extends React.Component { let tooltip = null; if (false) { // isCollapsed if (this.state.hover) { - tooltip = + tooltip = } } diff --git a/src/components/views/settings/ChangePassword.js b/src/components/views/settings/ChangePassword.js index c7eccf2145..a4ff65d8f9 100644 --- a/src/components/views/settings/ChangePassword.js +++ b/src/components/views/settings/ChangePassword.js @@ -141,6 +141,12 @@ export default createReactClass({ _changePassword: function(cli, oldPassword, newPassword) { const authDict = { type: 'm.login.password', + identifier: { + type: 'm.id.user', + user: cli.credentials.userId, + }, + // TODO: Remove `user` once servers support proper UIA + // See https://github.com/matrix-org/synapse/issues/5665 user: cli.credentials.userId, password: oldPassword, }; diff --git a/src/components/views/settings/EnableNotificationsButton.js b/src/components/views/settings/EnableNotificationsButton.js deleted file mode 100644 index e4b348dfbd..0000000000 --- a/src/components/views/settings/EnableNotificationsButton.js +++ /dev/null @@ -1,75 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from "react"; -import createReactClass from 'create-react-class'; -import Notifier from "../../../Notifier"; -import dis from "../../../dispatcher/dispatcher"; -import { _t } from '../../../languageHandler'; - -export default createReactClass({ - displayName: 'EnableNotificationsButton', - - componentDidMount: function() { - this.dispatcherRef = dis.register(this.onAction); - }, - - componentWillUnmount: function() { - dis.unregister(this.dispatcherRef); - }, - - onAction: function(payload) { - if (payload.action !== "notifier_enabled") { - return; - } - this.forceUpdate(); - }, - - enabled: function() { - return Notifier.isEnabled(); - }, - - onClick: function() { - const self = this; - if (!Notifier.supportsDesktopNotifications()) { - return; - } - if (!Notifier.isEnabled()) { - Notifier.setEnabled(true, function() { - self.forceUpdate(); - }); - } else { - Notifier.setEnabled(false); - } - this.forceUpdate(); - }, - - render: function() { - if (this.enabled()) { - return ( - - ); - } else { - return ( - - ); - } - }, -}); diff --git a/src/components/views/settings/account/PhoneNumbers.js b/src/components/views/settings/account/PhoneNumbers.js index ad2dabd8ae..02e995ac45 100644 --- a/src/components/views/settings/account/PhoneNumbers.js +++ b/src/components/views/settings/account/PhoneNumbers.js @@ -267,7 +267,7 @@ export default class PhoneNumbers extends React.Component { label={_t("Phone Number")} autoComplete="off" disabled={this.state.verifying} - prefix={phoneCountry} + prefixComponent={phoneCountry} value={this.state.newPhoneNumber} onChange={this._onChangeNewPhoneNumber} /> diff --git a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.js b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx similarity index 73% rename from src/components/views/settings/tabs/user/AppearanceUserSettingsTab.js rename to src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx index 5b49dd0abd..2fc9c3e6c4 100644 --- a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx @@ -20,34 +20,64 @@ import React from 'react'; import {_t} from "../../../../../languageHandler"; import SettingsStore, {SettingLevel} from "../../../../../settings/SettingsStore"; import * as sdk from "../../../../../index"; -import {enumerateThemes, ThemeWatcher} from "../../../../../theme"; +import { enumerateThemes } from "../../../../../theme"; +import ThemeWatcher from "../../../../../settings/watchers/ThemeWatcher"; import Field from "../../../elements/Field"; import Slider from "../../../elements/Slider"; import AccessibleButton from "../../../elements/AccessibleButton"; import dis from "../../../../../dispatcher/dispatcher"; -import { FontWatcher } from "../../../../../FontWatcher"; +import { FontWatcher } from "../../../../../settings/watchers/FontWatcher"; +import { RecheckThemePayload } from '../../../../../dispatcher/payloads/RecheckThemePayload'; +import { Action } from '../../../../../dispatcher/actions'; +import { IValidationResult, IFieldState } from '../../../elements/Validation'; -export default class AppearanceUserSettingsTab extends React.Component { - constructor() { - super(); +interface IProps { +} + +interface IThemeState { + theme: string, + useSystemTheme: boolean, +} + +export interface CustomThemeMessage { + isError: boolean, + text: string +}; + +interface IState extends IThemeState { + // String displaying the current selected fontSize. + // Needs to be string for things like '17.' without + // trailing 0s. + fontSize: string, + customThemeUrl: string, + customThemeMessage: CustomThemeMessage, + useCustomFontSize: boolean, +} + +export default class AppearanceUserSettingsTab extends React.Component { + + private themeTimer: NodeJS.Timeout; + + constructor(props: IProps) { + super(props); this.state = { - fontSize: SettingsStore.getValue("fontSize", null), - ...this._calculateThemeState(), + fontSize: SettingsStore.getValue("fontSize", null).toString(), + ...this.calculateThemeState(), customThemeUrl: "", customThemeMessage: {isError: false, text: ""}, useCustomFontSize: SettingsStore.getValue("useCustomFontSize"), }; } - _calculateThemeState() { + private calculateThemeState(): IThemeState { // We have to mirror the logic from ThemeWatcher.getEffectiveTheme so we // show the right values for things. - const themeChoice = SettingsStore.getValueAt(SettingLevel.ACCOUNT, "theme"); - const systemThemeExplicit = SettingsStore.getValueAt( + const themeChoice: string = SettingsStore.getValueAt(SettingLevel.ACCOUNT, "theme"); + const systemThemeExplicit: boolean = SettingsStore.getValueAt( SettingLevel.DEVICE, "use_system_theme", null, false, true); - const themeExplicit = SettingsStore.getValueAt( + const themeExplicit: string = SettingsStore.getValueAt( SettingLevel.DEVICE, "theme", null, false, true); // If the user has enabled system theme matching, use that. @@ -73,15 +103,15 @@ export default class AppearanceUserSettingsTab extends React.Component { }; } - _onThemeChange = (e) => { + private onThemeChange(e: React.ChangeEvent): void { const newTheme = e.target.value; if (this.state.theme === newTheme) return; // doing getValue in the .catch will still return the value we failed to set, // so remember what the value was before we tried to set it so we can revert - const oldTheme = SettingsStore.getValue('theme'); + const oldTheme: string = SettingsStore.getValue('theme'); SettingsStore.setValue("theme", null, SettingLevel.ACCOUNT, newTheme).catch(() => { - dis.dispatch({action: 'recheck_theme'}); + dis.dispatch({action: Action.RecheckTheme}); this.setState({theme: oldTheme}); }); this.setState({theme: newTheme}); @@ -91,23 +121,21 @@ export default class AppearanceUserSettingsTab extends React.Component { // XXX: The local echoed value appears to be unreliable, in particular // when settings custom themes(!) so adding forceTheme to override // the value from settings. - dis.dispatch({action: 'recheck_theme', forceTheme: newTheme}); + dis.dispatch({action: Action.RecheckTheme, forceTheme: newTheme}); }; - _onUseSystemThemeChanged = (checked) => { + private onUseSystemThemeChanged(checked: boolean) { this.setState({useSystemTheme: checked}); SettingsStore.setValue("use_system_theme", null, SettingLevel.DEVICE, checked); - dis.dispatch({action: 'recheck_theme'}); + dis.dispatch({action: Action.RecheckTheme}); }; - _onFontSizeChanged = (size) => { - this.setState({fontSize: size}); + private onFontSizeChanged(size: number) { + this.setState({fontSize: size.toString()}); SettingsStore.setValue("fontSize", null, SettingLevel.DEVICE, size); }; - _onValidateFontSize = ({value}) => { - console.log({value}); - + private async onValidateFontSize({value}: Pick): Promise { const parsedSize = parseFloat(value); const min = FontWatcher.MIN_SIZE; const max = FontWatcher.MAX_SIZE; @@ -127,17 +155,18 @@ export default class AppearanceUserSettingsTab extends React.Component { return {valid: true, feedback: _t('Use between %(min)s pt and %(max)s pt', {min, max})}; } - _onAddCustomTheme = async () => { - let currentThemes = SettingsStore.getValue("custom_themes"); + private async onAddCustomTheme() { + let currentThemes: string[] = SettingsStore.getValue("custom_themes"); if (!currentThemes) currentThemes = []; currentThemes = currentThemes.map(c => c); // cheap clone - if (this._themeTimer) { - clearTimeout(this._themeTimer); + if (this.themeTimer) { + clearTimeout(this.themeTimer); } try { const r = await fetch(this.state.customThemeUrl); + // XXX: need some schema for this const themeInfo = await r.json(); if (!themeInfo || typeof(themeInfo['name']) !== 'string' || typeof(themeInfo['colors']) !== 'object') { this.setState({customThemeMessage: {text: _t("Invalid theme schema."), isError: true}}); @@ -153,42 +182,32 @@ export default class AppearanceUserSettingsTab extends React.Component { await SettingsStore.setValue("custom_themes", null, SettingLevel.ACCOUNT, currentThemes); this.setState({customThemeUrl: "", customThemeMessage: {text: _t("Theme added!"), isError: false}}); - this._themeTimer = setTimeout(() => { + this.themeTimer = setTimeout(() => { this.setState({customThemeMessage: {text: "", isError: false}}); }, 3000); }; - _onCustomThemeChange = (e) => { + private onCustomThemeChange(e: React.ChangeEvent) { this.setState({customThemeUrl: e.target.value}); }; - render() { - return ( -
-
{_t("Appearance")}
- {this._renderThemeSection()} - {SettingsStore.isFeatureEnabled("feature_font_scaling") ? this._renderFontSection() : null} -
- ); - } - - _renderThemeSection() { + private renderThemeSection() { const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag"); const LabelledToggleSwitch = sdk.getComponent("views.elements.LabelledToggleSwitch"); const themeWatcher = new ThemeWatcher(); - let systemThemeSection; + let systemThemeSection: JSX.Element; if (themeWatcher.isSystemThemeSupported()) { systemThemeSection =
; } - let customThemeForm; + let customThemeForm: JSX.Element; if (SettingsStore.isFeatureEnabled("feature_custom_themes")) { let messageElement = null; if (this.state.customThemeMessage.text) { @@ -200,17 +219,17 @@ export default class AppearanceUserSettingsTab extends React.Component { } customThemeForm = (
-
+ {_t("Add theme")} @@ -220,7 +239,8 @@ export default class AppearanceUserSettingsTab extends React.Component { ); } - const themes = Object.entries(enumerateThemes()) + // XXX: replace any type here + const themes = Object.entries(enumerateThemes()) .map(p => ({id: p[0], name: p[1]})); // convert pairs to objects for code readability const builtInThemes = themes.filter(p => !p.id.startsWith("custom-")); const customThemes = themes.filter(p => !builtInThemes.includes(p)) @@ -232,7 +252,7 @@ export default class AppearanceUserSettingsTab extends React.Component { {systemThemeSection} {orderedThemes.map(theme => { @@ -245,7 +265,7 @@ export default class AppearanceUserSettingsTab extends React.Component { ); } - _renderFontSection() { + private renderFontSection() { const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag"); return
{_t("Font size")} @@ -253,9 +273,9 @@ export default class AppearanceUserSettingsTab extends React.Component {
Aa
{}} + value={parseInt(this.state.fontSize, 10)} + onSelectionChange={this.onFontSizeChanged} + displayFunc={value => ""} disabled={this.state.useCustomFontSize} />
Aa
@@ -263,7 +283,7 @@ export default class AppearanceUserSettingsTab extends React.Component { this.setState({useCustomFontSize: checked})} + onChange={(checked) => this.setState({useCustomFontSize: checked})} /> this.setState({fontSize: value.target.value})} disabled={!this.state.useCustomFontSize} />
; } + + render() { + return ( +
+
{_t("Appearance")}
+ {this.renderThemeSection()} + {SettingsStore.isFeatureEnabled("feature_font_scaling") ? this.renderFontSection() : null} +
+ ); + } } diff --git a/src/components/views/toasts/BulkUnverifiedSessionsToast.js b/src/components/views/toasts/BulkUnverifiedSessionsToast.js deleted file mode 100644 index 99ff529c35..0000000000 --- a/src/components/views/toasts/BulkUnverifiedSessionsToast.js +++ /dev/null @@ -1,56 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import PropTypes from 'prop-types'; -import { _t } from '../../../languageHandler'; -import dis from "../../../dispatcher/dispatcher"; -import { MatrixClientPeg } from '../../../MatrixClientPeg'; -import DeviceListener from '../../../DeviceListener'; -import FormButton from '../elements/FormButton'; -import { replaceableComponent } from '../../../utils/replaceableComponent'; - -@replaceableComponent("views.toasts.BulkUnverifiedSessionsToast") -export default class BulkUnverifiedSessionsToast extends React.PureComponent { - static propTypes = { - deviceIds: PropTypes.array, - } - - _onLaterClick = () => { - DeviceListener.sharedInstance().dismissUnverifiedSessions(this.props.deviceIds); - }; - - _onReviewClick = async () => { - DeviceListener.sharedInstance().dismissUnverifiedSessions(this.props.deviceIds); - - dis.dispatch({ - action: 'view_user_info', - userId: MatrixClientPeg.get().getUserId(), - }); - }; - - render() { - return (
-
- {_t("Verify all your sessions to ensure your account & messages are safe")} -
-
- - -
-
); - } -} diff --git a/src/components/views/toasts/GenericToast.tsx b/src/components/views/toasts/GenericToast.tsx new file mode 100644 index 0000000000..9d69330857 --- /dev/null +++ b/src/components/views/toasts/GenericToast.tsx @@ -0,0 +1,42 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React, {ReactChild} from "react"; + +import FormButton from "../elements/FormButton"; + +interface IProps { + description: ReactChild; + acceptLabel: string; + rejectLabel?: string; + + onAccept(); + onReject?(); +} + +const GenericToast: React.FC = ({description, acceptLabel, rejectLabel, onAccept, onReject}) => { + return
+
+ { description } +
+
+ {onReject && rejectLabel && } + +
+
; +}; + +export default GenericToast; diff --git a/src/components/views/toasts/SetupEncryptionToast.js b/src/components/views/toasts/SetupEncryptionToast.js deleted file mode 100644 index b5510e85b6..0000000000 --- a/src/components/views/toasts/SetupEncryptionToast.js +++ /dev/null @@ -1,88 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import PropTypes from 'prop-types'; -import Modal from '../../../Modal'; -import * as sdk from "../../../index"; -import { _t } from '../../../languageHandler'; -import DeviceListener from '../../../DeviceListener'; -import SetupEncryptionDialog from "../dialogs/SetupEncryptionDialog"; -import { accessSecretStorage } from '../../../CrossSigningManager'; - -export default class SetupEncryptionToast extends React.PureComponent { - static propTypes = { - toastKey: PropTypes.string.isRequired, - kind: PropTypes.oneOf([ - 'set_up_encryption', - 'verify_this_session', - 'upgrade_encryption', - ]).isRequired, - }; - - _onLaterClick = () => { - DeviceListener.sharedInstance().dismissEncryptionSetup(); - }; - - _onSetupClick = async () => { - if (this.props.kind === "verify_this_session") { - Modal.createTrackedDialog('Verify session', 'Verify session', SetupEncryptionDialog, - {}, null, /* priority = */ false, /* static = */ true); - } else { - const Spinner = sdk.getComponent("elements.Spinner"); - const modal = Modal.createDialog( - Spinner, null, 'mx_Dialog_spinner', /* priority */ false, /* static */ true, - ); - try { - await accessSecretStorage(); - } finally { - modal.close(); - } - } - }; - - getDescription() { - switch (this.props.kind) { - case 'set_up_encryption': - case 'upgrade_encryption': - return _t('Verify yourself & others to keep your chats safe'); - case 'verify_this_session': - return _t('Other users may not trust it'); - } - } - - getSetupCaption() { - switch (this.props.kind) { - case 'set_up_encryption': - return _t('Set up'); - case 'upgrade_encryption': - return _t('Upgrade'); - case 'verify_this_session': - return _t('Verify'); - } - } - - render() { - const FormButton = sdk.getComponent("elements.FormButton"); - return (
-
{this.getDescription()}
-
- - -
-
); - } -} diff --git a/src/components/views/toasts/UnverifiedSessionToast.js b/src/components/views/toasts/UnverifiedSessionToast.js deleted file mode 100644 index 38cd9f20df..0000000000 --- a/src/components/views/toasts/UnverifiedSessionToast.js +++ /dev/null @@ -1,66 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import PropTypes from 'prop-types'; -import { _t } from '../../../languageHandler'; -import { MatrixClientPeg } from '../../../MatrixClientPeg'; -import Modal from '../../../Modal'; -import DeviceListener from '../../../DeviceListener'; -import NewSessionReviewDialog from '../dialogs/NewSessionReviewDialog'; -import FormButton from '../elements/FormButton'; -import { replaceableComponent } from '../../../utils/replaceableComponent'; - -@replaceableComponent("views.toasts.UnverifiedSessionToast") -export default class UnverifiedSessionToast extends React.PureComponent { - static propTypes = { - deviceId: PropTypes.string, - } - - _onLaterClick = () => { - DeviceListener.sharedInstance().dismissUnverifiedSessions([this.props.deviceId]); - }; - - _onReviewClick = async () => { - const cli = MatrixClientPeg.get(); - Modal.createTrackedDialog('New Session Review', 'Starting dialog', NewSessionReviewDialog, { - userId: cli.getUserId(), - device: cli.getStoredDevice(cli.getUserId(), this.props.deviceId), - onFinished: (r) => { - if (!r) { - /* This'll come back false if the user clicks "this wasn't me" and saw a warning dialog */ - DeviceListener.sharedInstance().dismissUnverifiedSessions([this.props.deviceId]); - } - }, - }, null, /* priority = */ false, /* static = */ true); - }; - - render() { - const cli = MatrixClientPeg.get(); - const device = cli.getStoredDevice(cli.getUserId(), this.props.deviceId); - - return (
-
- {_t( - "Verify the new login accessing your account: %(name)s", { name: device.getDisplayName()})} -
-
- - -
-
); - } -} diff --git a/src/components/views/toasts/VerificationRequestToast.js b/src/components/views/toasts/VerificationRequestToast.tsx similarity index 86% rename from src/components/views/toasts/VerificationRequestToast.js rename to src/components/views/toasts/VerificationRequestToast.tsx index 421dd7bea1..38e7e31989 100644 --- a/src/components/views/toasts/VerificationRequestToast.js +++ b/src/components/views/toasts/VerificationRequestToast.tsx @@ -14,8 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; -import PropTypes from 'prop-types'; +import React from "react"; + import * as sdk from "../../../index"; import { _t } from '../../../languageHandler'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; @@ -24,8 +24,23 @@ import {userLabelForEventRoom} from "../../../utils/KeyVerificationStateObserver import dis from "../../../dispatcher/dispatcher"; import ToastStore from "../../../stores/ToastStore"; import Modal from "../../../Modal"; +import GenericToast from "./GenericToast"; +import {VerificationRequest} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; +import {DeviceInfo} from "matrix-js-sdk/src/crypto/deviceinfo"; + +interface IProps { + toastKey: string; + request: VerificationRequest; +} + +interface IState { + counter: number; + device?: DeviceInfo; +} + +export default class VerificationRequestToast extends React.PureComponent { + private intervalHandle: NodeJS.Timeout; -export default class VerificationRequestToast extends React.PureComponent { constructor(props) { super(props); this.state = {counter: Math.ceil(props.request.timeout / 1000)}; @@ -34,7 +49,7 @@ export default class VerificationRequestToast extends React.PureComponent { async componentDidMount() { const {request} = this.props; if (request.timeout && request.timeout > 0) { - this._intervalHandle = setInterval(() => { + this.intervalHandle = setInterval(() => { let {counter} = this.state; counter = Math.max(0, counter - 1); this.setState({counter}); @@ -56,7 +71,7 @@ export default class VerificationRequestToast extends React.PureComponent { } componentWillUnmount() { - clearInterval(this._intervalHandle); + clearInterval(this.intervalHandle); const {request} = this.props; request.off("change", this._checkRequestIsPending); } @@ -110,7 +125,6 @@ export default class VerificationRequestToast extends React.PureComponent { }; render() { - const FormButton = sdk.getComponent("elements.FormButton"); const {request} = this.props; let nameLabel; if (request.isSelfVerification) { @@ -133,20 +147,16 @@ export default class VerificationRequestToast extends React.PureComponent { } } } - const declineLabel = this.state.counter == 0 ? + const declineLabel = this.state.counter === 0 ? _t("Decline") : _t("Decline (%(counter)s)", {counter: this.state.counter}); - return (
-
{nameLabel}
-
- - -
-
); + + return ; } } - -VerificationRequestToast.propTypes = { - request: PropTypes.object.isRequired, - toastKey: PropTypes.string.isRequired, -}; diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts index a2f9c3efe3..ba72daa00d 100644 --- a/src/dispatcher/actions.ts +++ b/src/dispatcher/actions.ts @@ -38,5 +38,15 @@ export enum Action { * Open the user settings. No additional payload information required. */ ViewUserSettings = "view_user_settings", + + /** + * Sets the current tooltip. Should be use with ViewTooltipPayload. + */ + ViewTooltip = "view_tooltip", + + /** + * Forces the theme to reload. No additional payload information required. + */ + RecheckTheme = "recheck_theme", } diff --git a/res/css/views/dialogs/_EncryptedEventDialog.scss b/src/dispatcher/payloads/RecheckThemePayload.ts similarity index 55% rename from res/css/views/dialogs/_EncryptedEventDialog.scss rename to src/dispatcher/payloads/RecheckThemePayload.ts index ff73df509d..06f7012049 100644 --- a/res/css/views/dialogs/_EncryptedEventDialog.scss +++ b/src/dispatcher/payloads/RecheckThemePayload.ts @@ -1,5 +1,5 @@ /* -Copyright 2016 OpenMarket Ltd +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. @@ -14,21 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_EncryptedEventDialog .mx_DeviceVerifyButtons { - float: right; - padding: 0px; - margin-right: 42px; - display: flex; - flex-wrap: wrap; - justify-content: space-between; -} +import { ActionPayload } from "../payloads"; +import { Action } from "../actions"; -.mx_EncryptedEventDialog .mx_MemberDeviceInfo_textButton { - @mixin mx_DialogButton; - background-color: $primary-bg-color; - color: $accent-color; -} +export interface RecheckThemePayload extends ActionPayload { + action: Action.RecheckTheme, -.mx_EncryptedEventDialog button { - margin-top: 0px; + /** + * Optionally specify the exact theme which is to be loaded. + */ + forceTheme?: string; } diff --git a/src/dispatcher/payloads/ViewTooltipPayload.ts b/src/dispatcher/payloads/ViewTooltipPayload.ts new file mode 100644 index 0000000000..8778287128 --- /dev/null +++ b/src/dispatcher/payloads/ViewTooltipPayload.ts @@ -0,0 +1,35 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { ActionPayload } from "../payloads"; +import { Action } from "../actions"; +import { Component } from "react"; + +export interface ViewTooltipPayload extends ActionPayload { + action: Action.ViewTooltip, + + /* + * The tooltip to render. If it's null the tooltip will not be rendered + * We need the void type because of typescript headaches. + */ + tooltip: null | void | Element | Component; + + /* + * The parent under which to render the tooltip. Can be null to remove + * the parent type. + */ + parent: null | Element +} \ No newline at end of file diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 53a4aa58f1..ba55ee9d64 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -102,11 +102,6 @@ "%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(time)s", "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s", "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s", - "Verify this session": "Verify this session", - "Encryption upgrade available": "Encryption upgrade available", - "Set up encryption": "Set up encryption", - "Review where you’re logged in": "Review where you’re logged in", - "New login. Was this you?": "New login. Was this you?", "Who would you like to add to this community?": "Who would you like to add to this community?", "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID", "Invite new community members": "Invite new community members", @@ -396,6 +391,42 @@ "Common names and surnames are easy to guess": "Common names and surnames are easy to guess", "Straight rows of keys are easy to guess": "Straight rows of keys are easy to guess", "Short keyboard patterns are easy to guess": "Short keyboard patterns are easy to guess", + "Help us improve Riot": "Help us improve Riot", + "Send anonymous usage data which helps us improve Riot. This will use a cookie.": "Send anonymous usage data which helps us improve Riot. This will use a cookie.", + "I want to help": "I want to help", + "No": "No", + "Review where you’re logged in": "Review where you’re logged in", + "Verify all your sessions to ensure your account & messages are safe": "Verify all your sessions to ensure your account & messages are safe", + "Review": "Review", + "Later": "Later", + "Notifications": "Notifications", + "You are not receiving desktop notifications": "You are not receiving desktop notifications", + "Enable them now": "Enable them now", + "Close": "Close", + "Your homeserver has exceeded its user limit.": "Your homeserver has exceeded its user limit.", + "Your homeserver has exceeded one of its resource limits.": "Your homeserver has exceeded one of its resource limits.", + "Contact your 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 encryption": "Set up encryption", + "Encryption upgrade available": "Encryption upgrade available", + "Verify this session": "Verify this session", + "Set up": "Set up", + "Upgrade": "Upgrade", + "Verify": "Verify", + "Verify yourself & others to keep your chats safe": "Verify yourself & others to keep your chats safe", + "Other users may not trust it": "Other users may not trust it", + "New login. Was this you?": "New login. Was this you?", + "Verify the new login accessing your account: %(name)s": "Verify the new login accessing your account: %(name)s", + "What's new?": "What's new?", + "What's New": "What's New", + "Update": "Update", + "Restart": "Restart", + "Upgrade your Riot": "Upgrade your Riot", + "A new version of Riot is available!": "A new version of Riot is available!", "There was an error joining the room": "There was an error joining the room", "Sorry, your homeserver is too old to participate in this room.": "Sorry, your homeserver is too old to participate in this room.", "Please contact your homeserver administrator.": "Please contact your homeserver administrator.", @@ -570,15 +601,6 @@ "Headphones": "Headphones", "Folder": "Folder", "Pin": "Pin", - "Verify all your sessions to ensure your account & messages are safe": "Verify all your sessions to ensure your account & messages are safe", - "Later": "Later", - "Review": "Review", - "Verify yourself & others to keep your chats safe": "Verify yourself & others to keep your chats safe", - "Other users may not trust it": "Other users may not trust it", - "Set up": "Set up", - "Upgrade": "Upgrade", - "Verify": "Verify", - "Verify the new login accessing your account: %(name)s": "Verify the new login accessing your account: %(name)s", "From %(deviceName)s (%(deviceId)s)": "From %(deviceName)s (%(deviceId)s)", "Decline (%(counter)s)": "Decline (%(counter)s)", "Accept to continue:": "Accept to continue:", @@ -643,8 +665,6 @@ "Last seen": "Last seen", "Failed to set display name": "Failed to set display name", "Individually verify each session used by a user to mark it as trusted, not trusting cross-signed devices.": "Individually verify each session used by a user to mark it as trusted, not trusting cross-signed devices.", - "Disable Notifications": "Disable Notifications", - "Enable Notifications": "Enable Notifications", "Securely cache encrypted messages locally for them to appear in search results, using ": "Securely cache encrypted messages locally for them to appear in search results, using ", " to store messages from ": " to store messages from ", "rooms.": "rooms.", @@ -758,10 +778,10 @@ "Invalid theme schema.": "Invalid theme schema.", "Error downloading theme information.": "Error downloading theme information.", "Theme added!": "Theme added!", - "Appearance": "Appearance", "Custom theme URL": "Custom theme URL", "Add theme": "Add theme", "Theme": "Theme", + "Appearance": "Appearance", "Flair": "Flair", "Failed to change password. Is your password correct?": "Failed to change password. Is your password correct?", "Success": "Success", @@ -776,7 +796,6 @@ "Account management": "Account management", "Deactivating your account is a permanent action - be careful!": "Deactivating your account is a permanent action - be careful!", "Deactivate Account": "Deactivate Account", - "Warning": "Warning", "General": "General", "Discovery": "Discovery", "Deactivate account": "Deactivate account", @@ -815,7 +834,6 @@ "Ban list rules - %(roomName)s": "Ban list rules - %(roomName)s", "Server rules": "Server rules", "User rules": "User rules", - "Close": "Close", "You have not ignored anyone.": "You have not ignored anyone.", "You are currently ignoring:": "You are currently ignoring:", "You are not subscribed to any lists": "You are not subscribed to any lists", @@ -836,7 +854,6 @@ "If this isn't what you want, please use a different tool to ignore users.": "If this isn't what you want, please use a different tool to ignore users.", "Room ID or address of ban list": "Room ID or address of ban list", "Subscribe": "Subscribe", - "Notifications": "Notifications", "Start automatically after system login": "Start automatically after system login", "Always show the window menu bar": "Always show the window menu bar", "Show tray icon and minimize window to it on close": "Show tray icon and minimize window to it on close", @@ -1287,7 +1304,6 @@ "Verify by emoji": "Verify by emoji", "Almost there! Is your other session showing the same shield?": "Almost there! Is your other session showing the same shield?", "Almost there! Is %(displayName)s showing the same shield?": "Almost there! Is %(displayName)s showing the same shield?", - "No": "No", "Yes": "Yes", "Verify all users in a room to ensure it's secure.": "Verify all users in a room to ensure it's secure.", "In encrypted rooms, verify all users to ensure it’s secure.": "In encrypted rooms, verify all users to ensure it’s secure.", @@ -1381,20 +1397,6 @@ "Something went wrong when trying to get your communities.": "Something went wrong when trying to get your communities.", "Display your community flair in rooms configured to show it.": "Display your community flair in rooms configured to show it.", "You're not currently a member of any communities.": "You're not currently a member of any communities.", - "Please help improve Riot.im by sending anonymous usage data. This will use a cookie (please see our Cookie Policy).": "Please help improve Riot.im by sending anonymous usage data. This will use a cookie (please see our Cookie Policy).", - "Please help improve Riot.im by sending anonymous usage data. This will use a cookie.": "Please help improve Riot.im by sending anonymous usage data. This will use a cookie.", - "Yes, I want to help!": "Yes, I want to help!", - "You are not receiving desktop notifications": "You are not receiving desktop notifications", - "Enable them now": "Enable them now", - "What's New": "What's New", - "Update": "Update", - "What's new?": "What's new?", - "A new version of Riot is available.": "A new version of Riot is available.", - "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", - "Please contact your service administrator to get this limit increased.": "Please contact your service administrator to get this limit increased.", - "This homeserver has hit its Monthly Active User limit so some users will not be able to log in.": "This homeserver has hit its Monthly Active User limit so some users will not be able to log in.", - "This homeserver has exceeded one of its resource limits so some users will not be able to log in.": "This homeserver has exceeded one of its resource limits so some users will not be able to log in.", "Error encountered (%(errorDetail)s).": "Error encountered (%(errorDetail)s).", "Checking for an update...": "Checking for an update...", "No update available.": "No update available.", @@ -1598,7 +1600,7 @@ "Create Room": "Create Room", "Sign out": "Sign out", "To avoid losing your chat history, you must export your room keys before logging out. You will need to go back to the newer version of Riot to do this": "To avoid losing your chat history, you must export your room keys before logging out. You will need to go back to the newer version of Riot to do this", - "You've previously used a newer version of Riot on %(host)s. To use this version again with end to end encryption, you will need to sign out and back in again. ": "You've previously used a newer version of Riot on %(host)s. To use this version again with end to end encryption, you will need to sign out and back in again. ", + "You've previously used a newer version of Riot with this session. To use this version again with end to end encryption, you will need to sign out and back in again.": "You've previously used a newer version of Riot with this session. To use this version again with end to end encryption, you will need to sign out and back in again.", "Incompatible Database": "Incompatible Database", "Continue With Encryption Disabled": "Continue With Encryption Disabled", "Confirm your account deactivation by using Single Sign On to prove your identity.": "Confirm your account deactivation by using Single Sign On to prove your identity.", @@ -1669,13 +1671,6 @@ "Start a conversation with someone using their name, username (like ) or email address.": "Start a conversation with someone using their name, username (like ) or email address.", "Go": "Go", "Invite someone using their name, username (like ), email address or share this room.": "Invite someone using their name, username (like ), email address or share this room.", - "You added a new session '%(displayName)s', which is requesting encryption keys.": "You added a new session '%(displayName)s', which is requesting encryption keys.", - "Your unverified session '%(displayName)s' is requesting encryption keys.": "Your unverified session '%(displayName)s' is requesting encryption keys.", - "Start verification": "Start verification", - "Share without verifying": "Share without verifying", - "Ignore request": "Ignore request", - "Loading session info...": "Loading session info...", - "Encryption key request": "Encryption key request", "a new master key signature": "a new master key signature", "a new cross-signing key signature": "a new cross-signing key signature", "a device cross-signing signature": "a device cross-signing signature", @@ -1856,7 +1851,6 @@ "Share Message": "Share Message", "Source URL": "Source URL", "Collapse Reply Thread": "Collapse Reply Thread", - "End-to-end encryption information": "End-to-end encryption information", "Report Content": "Report Content", "Failed to set Direct Message status of room": "Failed to set Direct Message status of room", "Failed to forget room %(errCode)s": "Failed to forget room %(errCode)s", @@ -2176,22 +2170,6 @@ "Room Autocomplete": "Room Autocomplete", "Users": "Users", "User Autocomplete": "User Autocomplete", - "unknown device": "unknown device", - "NOT verified": "NOT verified", - "Blacklisted": "Blacklisted", - "verified": "verified", - "Device ID": "Device ID", - "Verification": "Verification", - "Ed25519 fingerprint": "Ed25519 fingerprint", - "User ID": "User ID", - "Curve25519 identity key": "Curve25519 identity key", - "none": "none", - "Claimed Ed25519 fingerprint key": "Claimed Ed25519 fingerprint key", - "Algorithm": "Algorithm", - "unencrypted": "unencrypted", - "Decryption error": "Decryption error", - "Event information": "Event information", - "Sender session information": "Sender session information", "Passphrases must match": "Passphrases must match", "Passphrase must not be empty": "Passphrase must not be empty", "Export room keys": "Export room keys", diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index d372c38405..fac7c92b65 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -405,7 +405,7 @@ export default class EventIndex extends EventEmitter { continue; } - console.log("EventIndex: Error crawling events:", e); + console.log("EventIndex: Error crawling using checkpoint:", checkpoint, ",", e); this.crawlerCheckpoints.push(checkpoint); continue; } @@ -507,7 +507,13 @@ export default class EventIndex extends EventEmitter { try { for (let i = 0; i < redactionEvents.length; i++) { const ev = redactionEvents[i]; - await indexManager.deleteEvent(ev.getAssociatedId()); + const eventId = ev.getAssociatedId(); + + if (eventId) { + await indexManager.deleteEvent(eventId); + } else { + console.warn("EventIndex: Redaction event doesn't contain a valid associated event id", ev); + } } const eventsAlreadyAdded = await indexManager.addHistoricEvents( diff --git a/src/settings/Settings.js b/src/settings/Settings.js index 8df75b2c8b..e6aa112c5f 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -180,7 +180,7 @@ export const SETTINGS = { "fontSize": { displayName: _td("Font size"), supportedLevels: LEVELS_ACCOUNT_SETTINGS, - default: 16, + default: 15, controller: new FontSizeController(), }, "useCustomFontSize": { diff --git a/src/FontWatcher.js b/src/settings/watchers/FontWatcher.ts similarity index 51% rename from src/FontWatcher.js rename to src/settings/watchers/FontWatcher.ts index 006df202ad..dce9e77e9e 100644 --- a/src/FontWatcher.js +++ b/src/settings/watchers/FontWatcher.ts @@ -14,38 +14,42 @@ See the License for the specific language governing permissions and limitations under the License. */ -import dis from './dispatcher/dispatcher'; -import SettingsStore, {SettingLevel} from './settings/SettingsStore'; +import dis from '../../dispatcher/dispatcher'; +import SettingsStore, {SettingLevel} from '../SettingsStore'; +import IWatcher from "./Watcher"; +import { toPx } from '../../utils/units'; -export class FontWatcher { - static MIN_SIZE = 13; - static MAX_SIZE = 20; +export class FontWatcher implements IWatcher { + public static readonly MIN_SIZE = 13; + public static readonly MAX_SIZE = 20; + + private dispatcherRef: string; constructor() { - this._dispatcherRef = null; + this.dispatcherRef = null; } - start() { - this._setRootFontSize(SettingsStore.getValue("fontSize")); - this._dispatcherRef = dis.register(this._onAction); + public start() { + this.setRootFontSize(SettingsStore.getValue("fontSize")); + this.dispatcherRef = dis.register(this.onAction); } - stop() { - dis.unregister(this._dispatcherRef); + public stop() { + dis.unregister(this.dispatcherRef); } - _onAction = (payload) => { + private onAction = (payload) => { if (payload.action === 'update-font-size') { - this._setRootFontSize(payload.size); + this.setRootFontSize(payload.size); } }; - _setRootFontSize = (size) => { + private setRootFontSize = (size) => { const fontSize = Math.max(Math.min(FontWatcher.MAX_SIZE, size), FontWatcher.MIN_SIZE); - if (fontSize != size) { + if (fontSize !== size) { SettingsStore.setValue("fontSize", null, SettingLevel.Device, fontSize); } - document.querySelector(":root").style.fontSize = fontSize + "px"; + (document.querySelector(":root")).style.fontSize = toPx(fontSize); }; } diff --git a/src/settings/watchers/ThemeWatcher.ts b/src/settings/watchers/ThemeWatcher.ts new file mode 100644 index 0000000000..ce0db881c0 --- /dev/null +++ b/src/settings/watchers/ThemeWatcher.ts @@ -0,0 +1,138 @@ +/* +Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> +Copyright 2019, 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import SettingsStore, { SettingLevel } from '../SettingsStore'; +import dis from '../../dispatcher/dispatcher'; +import { Action } from '../../dispatcher/actions'; +import ThemeController from "../controllers/ThemeController"; +import { setTheme } from "../../theme"; +import { ActionPayload } from '../../dispatcher/payloads'; + +export default class ThemeWatcher { + // XXX: I think this is unused. + static _instance = null; + + private themeWatchRef: string; + private systemThemeWatchRef: string; + private dispatcherRef: string; + + private preferDark: MediaQueryList; + private preferLight: MediaQueryList; + + private currentTheme: string; + + constructor() { + this.themeWatchRef = null; + this.systemThemeWatchRef = null; + this.dispatcherRef = null; + + // we have both here as each may either match or not match, so by having both + // we can get the tristate of dark/light/unsupported + this.preferDark = (global).matchMedia("(prefers-color-scheme: dark)"); + this.preferLight = (global).matchMedia("(prefers-color-scheme: light)"); + + this.currentTheme = this.getEffectiveTheme(); + } + + public start() { + this.themeWatchRef = SettingsStore.watchSetting("theme", null, this.onChange); + this.systemThemeWatchRef = SettingsStore.watchSetting("use_system_theme", null, this.onChange); + if (this.preferDark.addEventListener) { + this.preferDark.addEventListener('change', this.onChange); + this.preferLight.addEventListener('change', this.onChange); + } + this.dispatcherRef = dis.register(this.onAction); + } + + public stop() { + if (this.preferDark.addEventListener) { + this.preferDark.removeEventListener('change', this.onChange); + this.preferLight.removeEventListener('change', this.onChange); + } + SettingsStore.unwatchSetting(this.systemThemeWatchRef); + SettingsStore.unwatchSetting(this.themeWatchRef); + dis.unregister(this.dispatcherRef); + } + + private onChange = () => { + this.recheck(); + }; + + private onAction = (payload: ActionPayload) => { + if (payload.action === Action.RecheckTheme) { + // XXX forceTheme + this.recheck(payload.forceTheme); + } + }; + + // XXX: forceTheme param added here as local echo appears to be unreliable + // https://github.com/vector-im/riot-web/issues/11443 + public recheck(forceTheme?: string) { + const oldTheme = this.currentTheme; + this.currentTheme = forceTheme === undefined ? this.getEffectiveTheme() : forceTheme; + if (oldTheme !== this.currentTheme) { + setTheme(this.currentTheme); + } + } + + public getEffectiveTheme(): string { + // Dev note: Much of this logic is replicated in the AppearanceUserSettingsTab + + // XXX: checking the isLight flag here makes checking it in the ThemeController + // itself completely redundant since we just override the result here and we're + // now effectively just using the ThemeController as a place to store the static + // variable. The system theme setting probably ought to have an equivalent + // controller that honours the same flag, although probablt better would be to + // have the theme logic in one place rather than split between however many + // different places. + if (ThemeController.isLogin) return 'light'; + + // If the user has specifically enabled the system matching option (excluding default), + // then use that over anything else. We pick the lowest possible level for the setting + // to ensure the ordering otherwise works. + const systemThemeExplicit = SettingsStore.getValueAt( + SettingLevel.DEVICE, "use_system_theme", null, false, true); + if (systemThemeExplicit) { + console.log("returning explicit system theme"); + if (this.preferDark.matches) return 'dark'; + if (this.preferLight.matches) return 'light'; + } + + // If the user has specifically enabled the theme (without the system matching option being + // enabled specifically and excluding the default), use that theme. We pick the lowest possible + // level for the setting to ensure the ordering otherwise works. + const themeExplicit = SettingsStore.getValueAt( + SettingLevel.DEVICE, "theme", null, false, true); + if (themeExplicit) { + console.log("returning explicit theme: " + themeExplicit); + return themeExplicit; + } + + // If the user hasn't really made a preference in either direction, assume the defaults of the + // settings and use those. + if (SettingsStore.getValue('use_system_theme')) { + if (this.preferDark.matches) return 'dark'; + if (this.preferLight.matches) return 'light'; + } + console.log("returning theme value"); + return SettingsStore.getValue('theme'); + } + + public isSystemThemeSupported() { + return this.preferDark.matches || this.preferLight.matches; + } +} diff --git a/src/settings/watchers/Watcher.ts b/src/settings/watchers/Watcher.ts new file mode 100644 index 0000000000..a9f6f3f2c8 --- /dev/null +++ b/src/settings/watchers/Watcher.ts @@ -0,0 +1,20 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +export default interface IWatcher { + start(): void + stop(): void +} \ No newline at end of file diff --git a/src/stores/RoomViewStore.js b/src/stores/RoomViewStore.js index 3d82d086d7..6e5007895c 100644 --- a/src/stores/RoomViewStore.js +++ b/src/stores/RoomViewStore.js @@ -46,7 +46,6 @@ const INITIAL_STATE = { forwardingEvent: null, quotingEvent: null, - matrixClientIsReady: false, }; /** @@ -60,9 +59,6 @@ class RoomViewStore extends Store { // Initialise state this._state = INITIAL_STATE; - if (MatrixClientPeg.get()) { - this._state.matrixClientIsReady = MatrixClientPeg.get().isInitialSyncComplete(); - } } _setState(newState) { @@ -157,11 +153,6 @@ class RoomViewStore extends Store { }, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true); break; } - case 'sync_state': - this._setState({ - matrixClientIsReady: MatrixClientPeg.get() && MatrixClientPeg.get().isInitialSyncComplete(), - }); - break; } } @@ -224,6 +215,7 @@ class RoomViewStore extends Store { storeRoomAliasInCache(payload.room_alias, result.room_id); roomId = result.room_id; } catch (err) { + console.error("RVS failed to get room id for alias: ", err); dis.dispatch({ action: 'view_room_error', room_id: null, @@ -272,9 +264,8 @@ class RoomViewStore extends Store { err: err, }); let msg = err.message ? err.message : JSON.stringify(err); - // XXX: We are relying on the error message returned by browsers here. - // This isn't great, but it does generalize the error being shown to users. - if (msg && msg.startsWith("CORS request rejected")) { + console.log("Failed to join room:", msg); + if (err.name === "ConnectionError") { msg = _t("There was an error joining the room"); } if (err.errcode === 'M_INCOMPATIBLE_ROOM_VERSION') { @@ -375,7 +366,7 @@ class RoomViewStore extends Store { } shouldPeek() { - return this._state.shouldPeek && this._state.matrixClientIsReady; + return this._state.shouldPeek; } } diff --git a/src/stores/ToastStore.js b/src/stores/ToastStore.js deleted file mode 100644 index 8901736739..0000000000 --- a/src/stores/ToastStore.js +++ /dev/null @@ -1,73 +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 EventEmitter from 'events'; - -/** - * Holds the active toasts - */ -export default class ToastStore extends EventEmitter { - static PRIORITY_REALTIME = 0; - static PRIORITY_DEFAULT = 1; - static PRIORITY_LOW = 2; - - static sharedInstance() { - if (!global.mx_ToastStore) global.mx_ToastStore = new ToastStore(); - return global.mx_ToastStore; - } - - constructor() { - super(); - this._dispatcherRef = null; - this._toasts = []; - } - - reset() { - this._toasts = []; - } - - /** - * Add or replace a toast - * If a toast with the same toastKey already exists, the given toast will replace it - * Toasts are always added underneath any toasts of the same priority, so existing - * toasts stay at the top unless a higher priority one arrives (better to not change the - * toast unless necessary). - * - * @param {boject} newToast The new toast - */ - addOrReplaceToast(newToast) { - if (newToast.priority === undefined) newToast.priority = ToastStore.PRIORITY_DEFAULT; - - const oldIndex = this._toasts.findIndex(t => t.key === newToast.key); - if (oldIndex === -1) { - let newIndex = this._toasts.length; - while (newIndex > 0 && this._toasts[newIndex - 1].priority > newToast.priority) --newIndex; - this._toasts.splice(newIndex, 0, newToast); - } else { - this._toasts[oldIndex] = newToast; - } - this.emit('update'); - } - - dismissToast(key) { - this._toasts = this._toasts.filter(t => t.key !== key); - this.emit('update'); - } - - getToasts() { - return this._toasts; - } -} diff --git a/src/stores/ToastStore.ts b/src/stores/ToastStore.ts new file mode 100644 index 0000000000..55c48c3937 --- /dev/null +++ b/src/stores/ToastStore.ts @@ -0,0 +1,93 @@ +/* +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 EventEmitter from "events"; +import React, {JSXElementConstructor} from "react"; + +export interface IToast> { + key: string; + // higher priority number will be shown on top of lower priority + priority: number; + title: string; + icon?: string; + component: C; + props?: React.ComponentProps; +} + +/** + * Holds the active toasts + */ +export default class ToastStore extends EventEmitter { + private toasts: IToast[] = []; + // The count of toasts which have been seen & dealt with in this stack + // where the count resets when the stack of toasts clears. + private countSeen = 0; + + static sharedInstance() { + if (!window.mx_ToastStore) window.mx_ToastStore = new ToastStore(); + return window.mx_ToastStore; + } + + reset() { + this.toasts = []; + this.countSeen = 0; + } + + /** + * Add or replace a toast + * If a toast with the same toastKey already exists, the given toast will replace it + * Toasts are always added underneath any toasts of the same priority, so existing + * toasts stay at the top unless a higher priority one arrives (better to not change the + * toast unless necessary). + * + * @param {object} newToast The new toast + */ + addOrReplaceToast>(newToast: IToast) { + const oldIndex = this.toasts.findIndex(t => t.key === newToast.key); + if (oldIndex === -1) { + let newIndex = this.toasts.length; + while (newIndex > 0 && this.toasts[newIndex - 1].priority < newToast.priority) --newIndex; + this.toasts.splice(newIndex, 0, newToast); + } else { + this.toasts[oldIndex] = newToast; + } + this.emit('update'); + } + + dismissToast(key) { + if (this.toasts[0] && this.toasts[0].key === key) { + this.countSeen++; + } + + const length = this.toasts.length; + this.toasts = this.toasts.filter(t => t.key !== key); + if (length !== this.toasts.length) { + if (this.toasts.length === 0) { + this.countSeen = 0; + } + + this.emit('update'); + } + } + + getToasts() { + return this.toasts; + } + + getCountSeen() { + return this.countSeen; + } +} diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index 881b8fd3cf..108922a598 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -26,6 +26,7 @@ import { ITagMap, ITagSortingMap, ListAlgorithm, SortAlgorithm } from "./algorit import { getListAlgorithmInstance } from "./algorithms/list-ordering"; import { ActionPayload } from "../../dispatcher/payloads"; import defaultDispatcher from "../../dispatcher/dispatcher"; +import { readReceiptChangeIsFor } from "../../utils/read-receipts"; interface IState { tagsEnabled?: boolean; @@ -135,15 +136,10 @@ class _RoomListStore extends AsyncStore { if (payload.action === 'MatrixActions.Room.receipt') { // First see if the receipt event is for our own user. If it was, trigger // a room update (we probably read the room on a different device). - // noinspection JSObjectNullOrUndefined - this.matrixClient can't be null by this point in the lifecycle - const myUserId = this.matrixClient.getUserId(); - for (const eventId of Object.keys(payload.event.getContent())) { - const receiptUsers = Object.keys(payload.event.getContent()[eventId]['m.read'] || {}); - if (receiptUsers.includes(myUserId)) { - // TODO: Update room now that it's been read - console.log(payload); - return; - } + if (readReceiptChangeIsFor(payload.event, this.matrixClient)) { + // TODO: Update room now that it's been read + console.log(payload); + return; } } else if (payload.action === 'MatrixActions.Room.tags') { // TODO: Update room from tags diff --git a/src/theme.js b/src/theme.js index 72b6e93443..ccb753d601 100644 --- a/src/theme.js +++ b/src/theme.js @@ -19,114 +19,8 @@ import {_t} from "./languageHandler"; export const DEFAULT_THEME = "light"; import Tinter from "./Tinter"; -import dis from "./dispatcher/dispatcher"; -import SettingsStore, {SettingLevel} from "./settings/SettingsStore"; -import ThemeController from "./settings/controllers/ThemeController"; - -export class ThemeWatcher { - static _instance = null; - - constructor() { - this._themeWatchRef = null; - this._systemThemeWatchRef = null; - this._dispatcherRef = null; - - // we have both here as each may either match or not match, so by having both - // we can get the tristate of dark/light/unsupported - this._preferDark = global.matchMedia("(prefers-color-scheme: dark)"); - this._preferLight = global.matchMedia("(prefers-color-scheme: light)"); - - this._currentTheme = this.getEffectiveTheme(); - } - - start() { - this._themeWatchRef = SettingsStore.watchSetting("theme", null, this._onChange); - this._systemThemeWatchRef = SettingsStore.watchSetting("use_system_theme", null, this._onChange); - if (this._preferDark.addEventListener) { - this._preferDark.addEventListener('change', this._onChange); - this._preferLight.addEventListener('change', this._onChange); - } - this._dispatcherRef = dis.register(this._onAction); - } - - stop() { - if (this._preferDark.addEventListener) { - this._preferDark.removeEventListener('change', this._onChange); - this._preferLight.removeEventListener('change', this._onChange); - } - SettingsStore.unwatchSetting(this._systemThemeWatchRef); - SettingsStore.unwatchSetting(this._themeWatchRef); - dis.unregister(this._dispatcherRef); - } - - _onChange = () => { - this.recheck(); - }; - - _onAction = (payload) => { - if (payload.action === 'recheck_theme') { - // XXX forceTheme - this.recheck(payload.forceTheme); - } - }; - - // XXX: forceTheme param added here as local echo appears to be unreliable - // https://github.com/vector-im/riot-web/issues/11443 - recheck(forceTheme) { - const oldTheme = this._currentTheme; - this._currentTheme = forceTheme === undefined ? this.getEffectiveTheme() : forceTheme; - if (oldTheme !== this._currentTheme) { - setTheme(this._currentTheme); - } - } - - getEffectiveTheme() { - // Dev note: Much of this logic is replicated in the AppearanceUserSettingsTab - - // XXX: checking the isLight flag here makes checking it in the ThemeController - // itself completely redundant since we just override the result here and we're - // now effectively just using the ThemeController as a place to store the static - // variable. The system theme setting probably ought to have an equivalent - // controller that honours the same flag, although probablt better would be to - // have the theme logic in one place rather than split between however many - // different places. - if (ThemeController.isLogin) return 'light'; - - // If the user has specifically enabled the system matching option (excluding default), - // then use that over anything else. We pick the lowest possible level for the setting - // to ensure the ordering otherwise works. - const systemThemeExplicit = SettingsStore.getValueAt( - SettingLevel.DEVICE, "use_system_theme", null, false, true); - if (systemThemeExplicit) { - console.log("returning explicit system theme"); - if (this._preferDark.matches) return 'dark'; - if (this._preferLight.matches) return 'light'; - } - - // If the user has specifically enabled the theme (without the system matching option being - // enabled specifically and excluding the default), use that theme. We pick the lowest possible - // level for the setting to ensure the ordering otherwise works. - const themeExplicit = SettingsStore.getValueAt( - SettingLevel.DEVICE, "theme", null, false, true); - if (themeExplicit) { - console.log("returning explicit theme: " + themeExplicit); - return themeExplicit; - } - - // If the user hasn't really made a preference in either direction, assume the defaults of the - // settings and use those. - if (SettingsStore.getValue('use_system_theme')) { - if (this._preferDark.matches) return 'dark'; - if (this._preferLight.matches) return 'light'; - } - console.log("returning theme value"); - return SettingsStore.getValue('theme'); - } - - isSystemThemeSupported() { - return this._preferDark.matches || this._preferLight.matches; - } -} +import SettingsStore from "./settings/SettingsStore"; +import ThemeWatcher from "./settings/watchers/ThemeWatcher"; export function enumerateThemes() { const BUILTIN_THEMES = { diff --git a/src/toasts/AnalyticsToast.tsx b/src/toasts/AnalyticsToast.tsx new file mode 100644 index 0000000000..7cd59222dd --- /dev/null +++ b/src/toasts/AnalyticsToast.tsx @@ -0,0 +1,77 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from "react"; + +import { _t } from "../languageHandler"; +import dis from "../dispatcher/dispatcher"; +import Analytics from "../Analytics"; +import AccessibleButton from "../components/views/elements/AccessibleButton"; +import GenericToast from "../components/views/toasts/GenericToast"; +import ToastStore from "../stores/ToastStore"; + +const onAccept = () => { + console.log("DEBUG onAccept AnalyticsToast"); + dis.dispatch({ + action: 'accept_cookies', + }); +}; + +const onReject = () => { + console.log("DEBUG onReject AnalyticsToast"); + dis.dispatch({ + action: "reject_cookies", + }); +}; + +const onUsageDataClicked = () => { + Analytics.showDetailsModal(); +}; + +const TOAST_KEY = "analytics"; + +export const showToast = (policyUrl?: string) => { + ToastStore.sharedInstance().addOrReplaceToast({ + key: TOAST_KEY, + title: _t("Help us improve Riot"), + props: { + description: _t( + "Send anonymous usage data which helps us improve Riot. " + + "This will use a cookie.", + {}, + { + "UsageDataLink": (sub) => ( + { sub } + ), + // XXX: We need to link to the page that explains our cookies + "PolicyLink": (sub) => policyUrl ? ( + { sub } + ) : sub, + }, + ), + acceptLabel: _t("I want to help"), + onAccept, + rejectLabel: _t("No"), + onReject, + }, + component: GenericToast, + priority: 10, + }); +}; + +export const hideToast = () => { + ToastStore.sharedInstance().dismissToast(TOAST_KEY); +}; diff --git a/src/toasts/BulkUnverifiedSessionsToast.ts b/src/toasts/BulkUnverifiedSessionsToast.ts new file mode 100644 index 0000000000..41717e0804 --- /dev/null +++ b/src/toasts/BulkUnverifiedSessionsToast.ts @@ -0,0 +1,58 @@ +/* +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 dis from "../dispatcher/dispatcher"; +import { MatrixClientPeg } from '../MatrixClientPeg'; +import DeviceListener from '../DeviceListener'; +import GenericToast from "../components/views/toasts/GenericToast"; +import ToastStore from "../stores/ToastStore"; + +const TOAST_KEY = "reviewsessions"; + +export const showToast = (deviceIds: Set) => { + const onAccept = () => { + DeviceListener.sharedInstance().dismissUnverifiedSessions(deviceIds); + + dis.dispatch({ + action: 'view_user_info', + userId: MatrixClientPeg.get().getUserId(), + }); + }; + + const onReject = () => { + DeviceListener.sharedInstance().dismissUnverifiedSessions(deviceIds); + }; + + ToastStore.sharedInstance().addOrReplaceToast({ + key: TOAST_KEY, + title: _t("Review where you’re logged in"), + icon: "verification_warning", + props: { + description: _t("Verify all your sessions to ensure your account & messages are safe"), + acceptLabel: _t("Review"), + onAccept, + rejectLabel: _t("Later"), + onReject, + }, + component: GenericToast, + priority: 50, + }); +}; + +export const hideToast = () => { + ToastStore.sharedInstance().dismissToast(TOAST_KEY); +}; diff --git a/src/toasts/DesktopNotificationsToast.ts b/src/toasts/DesktopNotificationsToast.ts new file mode 100644 index 0000000000..413e82e20b --- /dev/null +++ b/src/toasts/DesktopNotificationsToast.ts @@ -0,0 +1,50 @@ +/* +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 Notifier from "../Notifier"; +import GenericToast from "../components/views/toasts/GenericToast"; +import ToastStore from "../stores/ToastStore"; + +const onAccept = () => { + Notifier.setEnabled(true); +}; + +const onReject = () => { + Notifier.setToolbarHidden(true); +}; + +const TOAST_KEY = "desktopnotifications"; + +export const showToast = () => { + ToastStore.sharedInstance().addOrReplaceToast({ + key: TOAST_KEY, + title: _t("Notifications"), + props: { + description: _t("You are not receiving desktop notifications"), + acceptLabel: _t("Enable them now"), + onAccept, + rejectLabel: _t("Close"), + onReject, + }, + component: GenericToast, + priority: 30, + }); +}; + +export const hideToast = () => { + ToastStore.sharedInstance().dismissToast(TOAST_KEY); +}; diff --git a/src/toasts/ServerLimitToast.tsx b/src/toasts/ServerLimitToast.tsx new file mode 100644 index 0000000000..d35140be3d --- /dev/null +++ b/src/toasts/ServerLimitToast.tsx @@ -0,0 +1,50 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from "react"; + +import { _t, _td } from "../languageHandler"; +import GenericToast from "../components/views/toasts/GenericToast"; +import ToastStore from "../stores/ToastStore"; +import {messageForResourceLimitError} from "../utils/ErrorUtils"; + +const TOAST_KEY = "serverlimit"; + +export const showToast = (limitType: string, adminContact?: string, syncError?: boolean) => { + const errorText = messageForResourceLimitError(limitType, adminContact, { + 'monthly_active_user': _td("Your homeserver has exceeded its user limit."), + '': _td("Your homeserver has exceeded one of its resource limits."), + }); + const contactText = messageForResourceLimitError(limitType, adminContact, { + '': _td("Contact your server admin."), + }); + + ToastStore.sharedInstance().addOrReplaceToast({ + key: TOAST_KEY, + title: _t("Warning"), + props: { + description: {errorText} {contactText}, + acceptLabel: _t("Ok"), + onAccept: hideToast, + }, + component: GenericToast, + priority: 70, + }); +}; + +export const hideToast = () => { + ToastStore.sharedInstance().dismissToast(TOAST_KEY); +}; diff --git a/src/toasts/SetPasswordToast.ts b/src/toasts/SetPasswordToast.ts new file mode 100644 index 0000000000..88cc317978 --- /dev/null +++ b/src/toasts/SetPasswordToast.ts @@ -0,0 +1,47 @@ +/* +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/src/toasts/SetupEncryptionToast.ts b/src/toasts/SetupEncryptionToast.ts new file mode 100644 index 0000000000..d35bbf1c88 --- /dev/null +++ b/src/toasts/SetupEncryptionToast.ts @@ -0,0 +1,106 @@ +/* +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 Modal from "../Modal"; +import * as sdk from "../index"; +import { _t } from "../languageHandler"; +import DeviceListener from "../DeviceListener"; +import SetupEncryptionDialog from "../components/views/dialogs/SetupEncryptionDialog"; +import { accessSecretStorage } from "../CrossSigningManager"; +import ToastStore from "../stores/ToastStore"; +import GenericToast from "../components/views/toasts/GenericToast"; + +const TOAST_KEY = "setupencryption"; + +const getTitle = (kind: Kind) => { + switch (kind) { + case Kind.SET_UP_ENCRYPTION: + return _t("Set up encryption"); + case Kind.UPGRADE_ENCRYPTION: + return _t("Encryption upgrade available"); + case Kind.VERIFY_THIS_SESSION: + return _t("Verify this session"); + } +}; + +const getSetupCaption = (kind: Kind) => { + switch (kind) { + case Kind.SET_UP_ENCRYPTION: + return _t("Set up"); + case Kind.UPGRADE_ENCRYPTION: + return _t("Upgrade"); + case Kind.VERIFY_THIS_SESSION: + return _t("Verify"); + } +}; + +const getDescription = (kind: Kind) => { + switch (kind) { + case Kind.SET_UP_ENCRYPTION: + case Kind.UPGRADE_ENCRYPTION: + return _t("Verify yourself & others to keep your chats safe"); + case Kind.VERIFY_THIS_SESSION: + return _t("Other users may not trust it"); + } +}; + +export enum Kind { + SET_UP_ENCRYPTION = "set_up_encryption", + UPGRADE_ENCRYPTION = "upgrade_encryption", + VERIFY_THIS_SESSION = "verify_this_session", +} + +const onReject = () => { + DeviceListener.sharedInstance().dismissEncryptionSetup(); +}; + +export const showToast = (kind: Kind) => { + const onAccept = async () => { + if (kind === Kind.VERIFY_THIS_SESSION) { + Modal.createTrackedDialog("Verify session", "Verify session", SetupEncryptionDialog, + {}, null, /* priority = */ false, /* static = */ true); + } else { + const Spinner = sdk.getComponent("elements.Spinner"); + const modal = Modal.createDialog( + Spinner, null, "mx_Dialog_spinner", /* priority */ false, /* static */ true, + ); + try { + await accessSecretStorage(); + } finally { + modal.close(); + } + } + }; + + ToastStore.sharedInstance().addOrReplaceToast({ + key: TOAST_KEY, + title: getTitle(kind), + icon: "verification_warning", + props: { + description: getDescription(kind), + acceptLabel: getSetupCaption(kind), + onAccept, + rejectLabel: _t("Later"), + onReject, + }, + component: GenericToast, + priority: kind === Kind.VERIFY_THIS_SESSION ? 95 : 40, + }); +}; + +export const hideToast = () => { + ToastStore.sharedInstance().dismissToast(TOAST_KEY); +}; diff --git a/src/toasts/UnverifiedSessionToast.ts b/src/toasts/UnverifiedSessionToast.ts new file mode 100644 index 0000000000..635356b9db --- /dev/null +++ b/src/toasts/UnverifiedSessionToast.ts @@ -0,0 +1,70 @@ +/* +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 { MatrixClientPeg } from '../MatrixClientPeg'; +import Modal from '../Modal'; +import DeviceListener from '../DeviceListener'; +import NewSessionReviewDialog from '../components/views/dialogs/NewSessionReviewDialog'; +import ToastStore from "../stores/ToastStore"; +import GenericToast from "../components/views/toasts/GenericToast"; + +function toastKey(deviceId: string) { + return "unverified_session_" + deviceId; +} + +export const showToast = (deviceId: string) => { + const cli = MatrixClientPeg.get(); + + const onAccept = () => { + Modal.createTrackedDialog('New Session Review', 'Starting dialog', NewSessionReviewDialog, { + userId: cli.getUserId(), + device: cli.getStoredDevice(cli.getUserId(), deviceId), + onFinished: (r) => { + if (!r) { + /* This'll come back false if the user clicks "this wasn't me" and saw a warning dialog */ + DeviceListener.sharedInstance().dismissUnverifiedSessions([deviceId]); + } + }, + }, null, /* priority = */ false, /* static = */ true); + }; + + const onReject = () => { + DeviceListener.sharedInstance().dismissUnverifiedSessions([deviceId]); + }; + + const device = cli.getStoredDevice(cli.getUserId(), deviceId); + + ToastStore.sharedInstance().addOrReplaceToast({ + key: toastKey(deviceId), + title: _t("New login. Was this you?"), + icon: "verification_warning", + props: { + description: _t( + "Verify the new login accessing your account: %(name)s", { name: device.getDisplayName()}), + acceptLabel: _t("Verify"), + onAccept, + rejectLabel: _t("Later"), + onReject, + }, + component: GenericToast, + priority: 80, + }); +}; + +export const hideToast = (deviceId: string) => { + ToastStore.sharedInstance().dismissToast(deviceId); +}; diff --git a/src/toasts/UpdateToast.tsx b/src/toasts/UpdateToast.tsx new file mode 100644 index 0000000000..3d4b55a4ff --- /dev/null +++ b/src/toasts/UpdateToast.tsx @@ -0,0 +1,90 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from "react"; + +import { _t } from "../languageHandler"; +import GenericToast from "../components/views/toasts/GenericToast"; +import ToastStore from "../stores/ToastStore"; +import QuestionDialog from "../components/views/dialogs/QuestionDialog"; +import ChangelogDialog from "../components/views/dialogs/ChangelogDialog"; +import PlatformPeg from "../PlatformPeg"; +import Modal from "../Modal"; + +const TOAST_KEY = "update"; + +/* + * Check a version string is compatible with the Changelog + * dialog ([riot-version]-react-[react-sdk-version]-js-[js-sdk-version]) + */ +function checkVersion(ver) { + const parts = ver.split('-'); + return parts.length === 5 && parts[1] === 'react' && parts[3] === 'js'; +} + +function installUpdate() { + PlatformPeg.get().installUpdate(); +} + +export const showToast = (version: string, newVersion: string, releaseNotes?: string) => { + let onAccept; + let acceptLabel = _t("What's new?"); + if (releaseNotes) { + onAccept = () => { + Modal.createTrackedDialog('Display release notes', '', QuestionDialog, { + title: _t("What's New"), + description:
{releaseNotes}
, + button: _t("Update"), + onFinished: (update) => { + if (update && PlatformPeg.get()) { + PlatformPeg.get().installUpdate(); + } + }, + }); + }; + } else if (checkVersion(version) && checkVersion(newVersion)) { + onAccept = () => { + Modal.createTrackedDialog('Display Changelog', '', ChangelogDialog, { + version, + newVersion, + onFinished: (update) => { + if (update && PlatformPeg.get()) { + PlatformPeg.get().installUpdate(); + } + }, + }); + }; + } else { + onAccept = installUpdate; + acceptLabel = _t("Restart"); + } + + ToastStore.sharedInstance().addOrReplaceToast({ + key: TOAST_KEY, + title: _t("Upgrade your Riot"), + props: { + description: _t("A new version of Riot is available!"), + acceptLabel, + onAccept, + }, + component: GenericToast, + priority: 20, + }); +}; + +export const hideToast = () => { + ToastStore.sharedInstance().dismissToast(TOAST_KEY); +}; diff --git a/src/utils/read-receipts.ts b/src/utils/read-receipts.ts new file mode 100644 index 0000000000..f05c3cc5f2 --- /dev/null +++ b/src/utils/read-receipts.ts @@ -0,0 +1,34 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { MatrixClient } from "matrix-js-sdk/src/client"; + +/** + * Determines if a read receipt update event includes the client's own user. + * @param event The event to check. + * @param client The client to check against. + * @returns True if the read receipt update includes the client, false otherwise. + */ +export function readReceiptChangeIsFor(event: MatrixEvent, client: MatrixClient): boolean { + const myUserId = client.getUserId(); + for (const eventId of Object.keys(event.getContent())) { + const receiptUsers = Object.keys(event.getContent()[eventId]['m.read'] || {}); + if (receiptUsers.includes(myUserId)) { + return true; + } + } +} diff --git a/test/end-to-end-tests/src/scenario.js b/test/end-to-end-tests/src/scenario.js index f575fb392e..2191d630ac 100644 --- a/test/end-to-end-tests/src/scenario.js +++ b/test/end-to-end-tests/src/scenario.js @@ -17,6 +17,7 @@ limitations under the License. const {range} = require('./util'); const signup = require('./usecases/signup'); +const toastScenarios = require('./scenarios/toast'); const roomDirectoryScenarios = require('./scenarios/directory'); const lazyLoadingScenarios = require('./scenarios/lazy-loading'); const e2eEncryptionScenarios = require('./scenarios/e2e-encryption'); @@ -37,6 +38,7 @@ module.exports = async function scenario(createSession, restCreator) { const alice = await createUser("alice"); const bob = await createUser("bob"); + await toastScenarios(alice, bob); await roomDirectoryScenarios(alice, bob); await e2eEncryptionScenarios(alice, bob); console.log("create REST users:"); diff --git a/test/end-to-end-tests/src/scenarios/toast.js b/test/end-to-end-tests/src/scenarios/toast.js new file mode 100644 index 0000000000..1206ef40b0 --- /dev/null +++ b/test/end-to-end-tests/src/scenarios/toast.js @@ -0,0 +1,49 @@ +/* +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. +*/ + +const {assertNoToasts, acceptToast, rejectToast} = require("../usecases/toasts"); + +module.exports = async function toastScenarios(alice, bob) { + console.log(" checking and clearing toasts:"); + + alice.log.startGroup(`clears toasts`); + alice.log.step(`reject desktop notifications toast`); + await rejectToast(alice, "Notifications"); + alice.log.done(); + + alice.log.step(`accepts analytics toast`); + await acceptToast(alice, "Help us improve Riot"); + alice.log.done(); + + alice.log.step(`checks no remaining toasts`); + await assertNoToasts(alice); + alice.log.done(); + alice.log.endGroup(); + + bob.log.startGroup(`clears toasts`); + bob.log.step(`reject desktop notifications toast`); + await rejectToast(bob, "Notifications"); + bob.log.done(); + + bob.log.step(`reject analytics toast`); + await rejectToast(bob, "Help us improve Riot"); + bob.log.done(); + + bob.log.step(`checks no remaining toasts`); + await assertNoToasts(bob); + bob.log.done(); + bob.log.endGroup(); +}; diff --git a/test/end-to-end-tests/src/session.js b/test/end-to-end-tests/src/session.js index 55c2ed440c..907ee2fb8e 100644 --- a/test/end-to-end-tests/src/session.js +++ b/test/end-to-end-tests/src/session.js @@ -122,8 +122,8 @@ module.exports = class RiotSession { await input.type(text); } - query(selector, timeout = DEFAULT_TIMEOUT) { - return this.page.waitForSelector(selector, {visible: true, timeout}); + query(selector, timeout = DEFAULT_TIMEOUT, hidden = false) { + return this.page.waitForSelector(selector, {visible: true, timeout, hidden}); } async queryAll(selector) { diff --git a/test/end-to-end-tests/src/usecases/dialog.js b/test/end-to-end-tests/src/usecases/dialog.js index d4ae97dff9..15ac50bb18 100644 --- a/test/end-to-end-tests/src/usecases/dialog.js +++ b/test/end-to-end-tests/src/usecases/dialog.js @@ -20,7 +20,7 @@ const assert = require('assert'); async function assertDialog(session, expectedTitle) { const titleElement = await session.query(".mx_Dialog .mx_Dialog_title"); const dialogHeader = await session.innerText(titleElement); - assert(dialogHeader, expectedTitle); + assert.equal(dialogHeader, expectedTitle); } async function acceptDialog(session, expectedTitle) { diff --git a/test/end-to-end-tests/src/usecases/toasts.js b/test/end-to-end-tests/src/usecases/toasts.js new file mode 100644 index 0000000000..db78352f2b --- /dev/null +++ b/test/end-to-end-tests/src/usecases/toasts.js @@ -0,0 +1,47 @@ +/* +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. +*/ + +const assert = require('assert'); + +async function assertNoToasts(session) { + try { + await session.query('.mx_Toast_toast', 1000, true); + } catch (e) { + const h2Element = await session.query('.mx_Toast_title h2', 1000); + const toastTitle = await session.innerText(h2Element); + throw new Error(`"${toastTitle}" toast found when none expected`); + } +} + +async function assertToast(session, expectedTitle) { + const h2Element = await session.query('.mx_Toast_title h2'); + const toastTitle = await session.innerText(h2Element); + assert.equal(toastTitle, expectedTitle); +} + +async function acceptToast(session, expectedTitle) { + await assertToast(session, expectedTitle); + const btn = await session.query('.mx_Toast_buttons .mx_AccessibleButton_kind_primary'); + await btn.click(); +} + +async function rejectToast(session, expectedTitle) { + await assertToast(session, expectedTitle); + const btn = await session.query('.mx_Toast_buttons .mx_AccessibleButton_kind_danger'); + await btn.click(); +} + +module.exports = {assertNoToasts, assertToast, acceptToast, rejectToast}; diff --git a/yarn.lock b/yarn.lock index d08979f5e9..d9423fc5ab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -25,28 +25,28 @@ dependencies: "@babel/highlight" "^7.8.3" -"@babel/compat-data@^7.8.6", "@babel/compat-data@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.9.0.tgz#04815556fc90b0c174abd2c0c1bb966faa036a6c" - integrity sha512-zeFQrr+284Ekvd9e7KAX954LkapWiOmQtsfHirhxqfdlX6MEC32iRE+pqUGlYIBchdevaCwvzxWGSy/YBNI85g== +"@babel/compat-data@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.9.6.tgz#3f604c40e420131affe6f2c8052e9a275ae2049b" + integrity sha512-5QPTrNen2bm7RBc7dsOmcA5hbrS4O2Vhmk5XOL4zWW/zD/hV0iinpefDlkm+tBBy8kDtFaaeEvmAqt+nURAV2g== dependencies: - browserslist "^4.9.1" + browserslist "^4.11.1" invariant "^2.2.4" semver "^5.5.0" "@babel/core@>=7.2.2", "@babel/core@^7.1.0", "@babel/core@^7.7.5": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.9.0.tgz#ac977b538b77e132ff706f3b8a4dbad09c03c56e" - integrity sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w== + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.9.6.tgz#d9aa1f580abf3b2286ef40b6904d390904c63376" + integrity sha512-nD3deLvbsApbHAHttzIssYqgb883yU/d9roe4RZymBCDaZryMJDbptVpEpeQuRh4BJ+SYI8le9YGxKvFEvl1Wg== dependencies: "@babel/code-frame" "^7.8.3" - "@babel/generator" "^7.9.0" + "@babel/generator" "^7.9.6" "@babel/helper-module-transforms" "^7.9.0" - "@babel/helpers" "^7.9.0" - "@babel/parser" "^7.9.0" + "@babel/helpers" "^7.9.6" + "@babel/parser" "^7.9.6" "@babel/template" "^7.8.6" - "@babel/traverse" "^7.9.0" - "@babel/types" "^7.9.0" + "@babel/traverse" "^7.9.6" + "@babel/types" "^7.9.6" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.1" @@ -56,12 +56,12 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@^7.4.0", "@babel/generator@^7.8.3", "@babel/generator@^7.9.0", "@babel/generator@^7.9.5": - version "7.9.5" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.5.tgz#27f0917741acc41e6eaaced6d68f96c3fa9afaf9" - integrity sha512-GbNIxVB3ZJe3tLeDm1HSn2AhuD/mVcyLDpgtLXa5tplmWrJdF/elxB56XNqCuD6szyNkDi6wuoKXln3QeBmCHQ== +"@babel/generator@^7.4.0", "@babel/generator@^7.8.3", "@babel/generator@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.6.tgz#5408c82ac5de98cda0d77d8124e99fa1f2170a43" + integrity sha512-+htwWKJbH2bL72HRluF8zumBxzuX0ZZUFl3JLNyoUjM/Ho8wnVpPXM6aUz8cfKDqQ/h7zHqKt4xzJteUosckqQ== dependencies: - "@babel/types" "^7.9.5" + "@babel/types" "^7.9.6" jsesc "^2.5.1" lodash "^4.17.13" source-map "^0.5.0" @@ -98,27 +98,27 @@ "@babel/helper-annotate-as-pure" "^7.8.3" "@babel/types" "^7.9.0" -"@babel/helper-compilation-targets@^7.8.7": - version "7.8.7" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.7.tgz#dac1eea159c0e4bd46e309b5a1b04a66b53c1dde" - integrity sha512-4mWm8DCK2LugIS+p1yArqvG1Pf162upsIsjE7cNBjez+NjliQpVhj20obE520nao0o14DaTnFJv+Fw5a0JpoUw== +"@babel/helper-compilation-targets@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.9.6.tgz#1e05b7ccc9d38d2f8b40b458b380a04dcfadd38a" + integrity sha512-x2Nvu0igO0ejXzx09B/1fGBxY9NXQlBW2kZsSxCJft+KHN8t9XWzIvFxtPHnBOAXpVsdxZKZFbRUC8TsNKajMw== dependencies: - "@babel/compat-data" "^7.8.6" - browserslist "^4.9.1" + "@babel/compat-data" "^7.9.6" + browserslist "^4.11.1" invariant "^2.2.4" levenary "^1.1.1" semver "^5.5.0" -"@babel/helper-create-class-features-plugin@^7.8.3": - version "7.9.5" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.9.5.tgz#79753d44017806b481017f24b02fd4113c7106ea" - integrity sha512-IipaxGaQmW4TfWoXdqjY0TzoXQ1HRS0kPpEgvjosb3u7Uedcq297xFqDQiCcQtRRwzIMif+N1MLVI8C5a4/PAA== +"@babel/helper-create-class-features-plugin@^7.8.3", "@babel/helper-create-class-features-plugin@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.9.6.tgz#965c8b0a9f051801fd9d3b372ca0ccf200a90897" + integrity sha512-6N9IeuyHvMBRyjNYOMJHrhwtu4WJMrYf8hVbEHD3pbbbmNOk1kmXSQs7bA4dYDUaIx4ZEzdnvo6NwC3WHd/Qow== dependencies: "@babel/helper-function-name" "^7.9.5" "@babel/helper-member-expression-to-functions" "^7.8.3" "@babel/helper-optimise-call-expression" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-replace-supers" "^7.8.6" + "@babel/helper-replace-supers" "^7.9.6" "@babel/helper-split-export-declaration" "^7.8.3" "@babel/helper-create-regexp-features-plugin@^7.8.3", "@babel/helper-create-regexp-features-plugin@^7.8.8": @@ -227,15 +227,15 @@ "@babel/traverse" "^7.8.3" "@babel/types" "^7.8.3" -"@babel/helper-replace-supers@^7.8.3", "@babel/helper-replace-supers@^7.8.6": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz#5ada744fd5ad73203bf1d67459a27dcba67effc8" - integrity sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA== +"@babel/helper-replace-supers@^7.8.3", "@babel/helper-replace-supers@^7.8.6", "@babel/helper-replace-supers@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.9.6.tgz#03149d7e6a5586ab6764996cd31d6981a17e1444" + integrity sha512-qX+chbxkbArLyCImk3bWV+jB5gTNU/rsze+JlcF6Nf8tVTigPJSI1o1oBow/9Resa1yehUO9lIipsmu9oG4RzA== dependencies: "@babel/helper-member-expression-to-functions" "^7.8.3" "@babel/helper-optimise-call-expression" "^7.8.3" - "@babel/traverse" "^7.8.6" - "@babel/types" "^7.8.6" + "@babel/traverse" "^7.9.6" + "@babel/types" "^7.9.6" "@babel/helper-simple-access@^7.8.3": version "7.8.3" @@ -267,14 +267,14 @@ "@babel/traverse" "^7.8.3" "@babel/types" "^7.8.3" -"@babel/helpers@^7.9.0": - version "7.9.2" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.9.2.tgz#b42a81a811f1e7313b88cba8adc66b3d9ae6c09f" - integrity sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA== +"@babel/helpers@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.9.6.tgz#092c774743471d0bb6c7de3ad465ab3d3486d580" + integrity sha512-tI4bUbldloLcHWoRUMAj4g1bF313M/o6fBKhIsb3QnGVPwRm9JsNf/gqMkQ7zjqReABiffPV6RWj7hEglID5Iw== dependencies: "@babel/template" "^7.8.3" - "@babel/traverse" "^7.9.0" - "@babel/types" "^7.9.0" + "@babel/traverse" "^7.9.6" + "@babel/types" "^7.9.6" "@babel/highlight@^7.8.3": version "7.9.0" @@ -285,10 +285,10 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.7.0", "@babel/parser@^7.8.6", "@babel/parser@^7.9.0": - version "7.9.4" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.4.tgz#68a35e6b0319bbc014465be43828300113f2f2e8" - integrity sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA== +"@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.7.0", "@babel/parser@^7.8.6", "@babel/parser@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.6.tgz#3b1bbb30dabe600cd72db58720998376ff653bc7" + integrity sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q== "@babel/plugin-proposal-async-generator-functions@^7.8.3": version "7.8.3" @@ -356,10 +356,10 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-numeric-separator" "^7.8.3" -"@babel/plugin-proposal-object-rest-spread@^7.7.4", "@babel/plugin-proposal-object-rest-spread@^7.9.5": - version "7.9.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.5.tgz#3fd65911306d8746014ec0d0cf78f0e39a149116" - integrity sha512-VP2oXvAf7KCYTthbUHwBlewbl1Iq059f6seJGsxMizaCdgHIeczOr7FBqELhSqfkIl04Fi8okzWzl63UKbQmmg== +"@babel/plugin-proposal-object-rest-spread@^7.7.4", "@babel/plugin-proposal-object-rest-spread@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.6.tgz#7a093586fcb18b08266eb1a7177da671ac575b63" + integrity sha512-Ga6/fhGqA9Hj+y6whNpPv8psyaK5xzrQwSPsGPloVkvmH+PqW1ixdnfJ9uIO06OjQNYol3PMnfmJ8vfZtkzF+A== dependencies: "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-object-rest-spread" "^7.8.0" @@ -615,34 +615,34 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-modules-amd@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.0.tgz#19755ee721912cf5bb04c07d50280af3484efef4" - integrity sha512-vZgDDF003B14O8zJy0XXLnPH4sg+9X5hFBBGN1V+B2rgrB+J2xIypSN6Rk9imB2hSTHQi5OHLrFWsZab1GMk+Q== +"@babel/plugin-transform-modules-amd@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.6.tgz#8539ec42c153d12ea3836e0e3ac30d5aae7b258e" + integrity sha512-zoT0kgC3EixAyIAU+9vfaUVKTv9IxBDSabgHoUCBP6FqEJ+iNiN7ip7NBKcYqbfUDfuC2mFCbM7vbu4qJgOnDw== dependencies: "@babel/helper-module-transforms" "^7.9.0" "@babel/helper-plugin-utils" "^7.8.3" - babel-plugin-dynamic-import-node "^2.3.0" + babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-commonjs@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.0.tgz#e3e72f4cbc9b4a260e30be0ea59bdf5a39748940" - integrity sha512-qzlCrLnKqio4SlgJ6FMMLBe4bySNis8DFn1VkGmOcxG9gqEyPIOzeQrA//u0HAKrWpJlpZbZMPB1n/OPa4+n8g== +"@babel/plugin-transform-modules-commonjs@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.6.tgz#64b7474a4279ee588cacd1906695ca721687c277" + integrity sha512-7H25fSlLcn+iYimmsNe3uK1at79IE6SKW9q0/QeEHTMC9MdOZ+4bA+T1VFB5fgOqBWoqlifXRzYD0JPdmIrgSQ== dependencies: "@babel/helper-module-transforms" "^7.9.0" "@babel/helper-plugin-utils" "^7.8.3" "@babel/helper-simple-access" "^7.8.3" - babel-plugin-dynamic-import-node "^2.3.0" + babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-systemjs@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.0.tgz#e9fd46a296fc91e009b64e07ddaa86d6f0edeb90" - integrity sha512-FsiAv/nao/ud2ZWy4wFacoLOm5uxl0ExSQ7ErvP7jpoihLR6Cq90ilOFyX9UXct3rbtKsAiZ9kFt5XGfPe/5SQ== +"@babel/plugin-transform-modules-systemjs@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.6.tgz#207f1461c78a231d5337a92140e52422510d81a4" + integrity sha512-NW5XQuW3N2tTHim8e1b7qGy7s0kZ2OH3m5octc49K1SdAKGxYxeIx7hiIz05kS1R2R+hOWcsr1eYwcGhrdHsrg== dependencies: "@babel/helper-hoist-variables" "^7.8.3" "@babel/helper-module-transforms" "^7.9.0" "@babel/helper-plugin-utils" "^7.8.3" - babel-plugin-dynamic-import-node "^2.3.0" + babel-plugin-dynamic-import-node "^2.3.3" "@babel/plugin-transform-modules-umd@^7.9.0": version "7.9.0" @@ -746,9 +746,9 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-transform-runtime@^7.8.3": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.9.0.tgz#45468c0ae74cc13204e1d3b1f4ce6ee83258af0b" - integrity sha512-pUu9VSf3kI1OqbWINQ7MaugnitRss1z533436waNXp+0N3ur3zfut37sXiQMxkuCF4VUjwZucen/quskCh7NHw== + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.9.6.tgz#3ba804438ad0d880a17bca5eaa0cdf1edeedb2fd" + integrity sha512-qcmiECD0mYOjOIt8YHNsAP1SxPooC/rDmfmiSK9BNY72EitdSc7l44WTEklaWuFtbOEBjNhWWyph/kOImbNJ4w== dependencies: "@babel/helper-module-imports" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" @@ -793,11 +793,11 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-transform-typescript@^7.9.0": - version "7.9.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.9.4.tgz#4bb4dde4f10bbf2d787fce9707fb09b483e33359" - integrity sha512-yeWeUkKx2auDbSxRe8MusAG+n4m9BFY/v+lPjmQDgOFX5qnySkUY5oXzkp6FwPdsYqnKay6lorXYdC0n3bZO7w== + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.9.6.tgz#2248971416a506fc78278fc0c0ea3179224af1e9" + integrity sha512-8OvsRdvpt3Iesf2qsAn+YdlwAJD7zJ+vhFZmDCa4b8dTp7MmHtKk5FF2mCsGxjZwuwsy/yIIay/nLmxST1ctVQ== dependencies: - "@babel/helper-create-class-features-plugin" "^7.8.3" + "@babel/helper-create-class-features-plugin" "^7.9.6" "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-typescript" "^7.8.3" @@ -810,12 +810,12 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/preset-env@^7.7.6": - version "7.9.5" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.9.5.tgz#8ddc76039bc45b774b19e2fc548f6807d8a8919f" - integrity sha512-eWGYeADTlPJH+wq1F0wNfPbVS1w1wtmMJiYk55Td5Yu28AsdR9AsC97sZ0Qq8fHqQuslVSIYSGJMcblr345GfQ== + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.9.6.tgz#df063b276c6455ec6fcfc6e53aacc38da9b0aea6" + integrity sha512-0gQJ9RTzO0heXOhzftog+a/WyOuqMrAIugVYxMYf83gh1CQaQDjMtsOpqOwXyDL/5JcWsrCm8l4ju8QC97O7EQ== dependencies: - "@babel/compat-data" "^7.9.0" - "@babel/helper-compilation-targets" "^7.8.7" + "@babel/compat-data" "^7.9.6" + "@babel/helper-compilation-targets" "^7.9.6" "@babel/helper-module-imports" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-proposal-async-generator-functions" "^7.8.3" @@ -823,7 +823,7 @@ "@babel/plugin-proposal-json-strings" "^7.8.3" "@babel/plugin-proposal-nullish-coalescing-operator" "^7.8.3" "@babel/plugin-proposal-numeric-separator" "^7.8.3" - "@babel/plugin-proposal-object-rest-spread" "^7.9.5" + "@babel/plugin-proposal-object-rest-spread" "^7.9.6" "@babel/plugin-proposal-optional-catch-binding" "^7.8.3" "@babel/plugin-proposal-optional-chaining" "^7.9.0" "@babel/plugin-proposal-unicode-property-regex" "^7.8.3" @@ -850,9 +850,9 @@ "@babel/plugin-transform-function-name" "^7.8.3" "@babel/plugin-transform-literals" "^7.8.3" "@babel/plugin-transform-member-expression-literals" "^7.8.3" - "@babel/plugin-transform-modules-amd" "^7.9.0" - "@babel/plugin-transform-modules-commonjs" "^7.9.0" - "@babel/plugin-transform-modules-systemjs" "^7.9.0" + "@babel/plugin-transform-modules-amd" "^7.9.6" + "@babel/plugin-transform-modules-commonjs" "^7.9.6" + "@babel/plugin-transform-modules-systemjs" "^7.9.6" "@babel/plugin-transform-modules-umd" "^7.9.0" "@babel/plugin-transform-named-capturing-groups-regex" "^7.8.3" "@babel/plugin-transform-new-target" "^7.8.3" @@ -868,8 +868,8 @@ "@babel/plugin-transform-typeof-symbol" "^7.8.4" "@babel/plugin-transform-unicode-regex" "^7.8.3" "@babel/preset-modules" "^0.1.3" - "@babel/types" "^7.9.5" - browserslist "^4.9.1" + "@babel/types" "^7.9.6" + browserslist "^4.11.1" core-js-compat "^3.6.2" invariant "^2.2.2" levenary "^1.1.1" @@ -926,17 +926,17 @@ source-map-support "^0.5.16" "@babel/runtime-corejs3@^7.8.3": - version "7.9.2" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.9.2.tgz#26fe4aa77e9f1ecef9b776559bbb8e84d34284b7" - integrity sha512-HHxmgxbIzOfFlZ+tdeRKtaxWOMUoCG5Mu3wKeUmOxjYrwb3AAHgnmtCUbPPK11/raIWLIBK250t8E2BPO0p7jA== + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.9.6.tgz#67aded13fffbbc2cb93247388cf84d77a4be9a71" + integrity sha512-6toWAfaALQjt3KMZQc6fABqZwUDDuWzz+cAfPhqyEnzxvdWOAkjwPNxgF8xlmo7OWLsSjaKjsskpKHRLaMArOA== dependencies: core-js-pure "^3.0.0" regenerator-runtime "^0.13.4" "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4": - version "7.9.2" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06" - integrity sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q== + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.6.tgz#a9102eb5cadedf3f31d08a9ecf294af7827ea29f" + integrity sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ== dependencies: regenerator-runtime "^0.13.4" @@ -949,25 +949,25 @@ "@babel/parser" "^7.8.6" "@babel/types" "^7.8.6" -"@babel/traverse@^7.1.0", "@babel/traverse@^7.4.3", "@babel/traverse@^7.7.0", "@babel/traverse@^7.8.3", "@babel/traverse@^7.8.6", "@babel/traverse@^7.9.0": - version "7.9.5" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.5.tgz#6e7c56b44e2ac7011a948c21e283ddd9d9db97a2" - integrity sha512-c4gH3jsvSuGUezlP6rzSJ6jf8fYjLj3hsMZRx/nX0h+fmHN0w+ekubRrHPqnMec0meycA2nwCsJ7dC8IPem2FQ== +"@babel/traverse@^7.1.0", "@babel/traverse@^7.4.3", "@babel/traverse@^7.7.0", "@babel/traverse@^7.8.3", "@babel/traverse@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.6.tgz#5540d7577697bf619cc57b92aa0f1c231a94f442" + integrity sha512-b3rAHSjbxy6VEAvlxM8OV/0X4XrG72zoxme6q1MOoe2vd0bEc+TwayhuC1+Dfgqh1QEG+pj7atQqvUprHIccsg== dependencies: "@babel/code-frame" "^7.8.3" - "@babel/generator" "^7.9.5" + "@babel/generator" "^7.9.6" "@babel/helper-function-name" "^7.9.5" "@babel/helper-split-export-declaration" "^7.8.3" - "@babel/parser" "^7.9.0" - "@babel/types" "^7.9.5" + "@babel/parser" "^7.9.6" + "@babel/types" "^7.9.6" debug "^4.1.0" globals "^11.1.0" lodash "^4.17.13" -"@babel/types@^7.0.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.7.0", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0", "@babel/types@^7.9.5": - version "7.9.5" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.5.tgz#89231f82915a8a566a703b3b20133f73da6b9444" - integrity sha512-XjnvNqenk818r5zMaba+sLQjnbda31UfUURv3ei0qPQw4u+j2jMyJ5b11y8ZHYTRSI3NnInQkkkRT4fLqqPdHg== +"@babel/types@^7.0.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.7.0", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0", "@babel/types@^7.9.5", "@babel/types@^7.9.6": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.6.tgz#2c5502b427251e9de1bd2dff95add646d95cc9f7" + integrity sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA== dependencies: "@babel/helper-validator-identifier" "^7.9.5" lodash "^4.17.13" @@ -1142,12 +1142,14 @@ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b" integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== -"@peculiar/asn1-schema@^1.0.5": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-1.0.5.tgz#aa5a2c51225d213d1d6a5499ada926da3f556ff5" - integrity sha512-rzzorGYnQNmVHleLvC8gJSbbdNYtg+EB9s075dHvwpxs10teXHYnRmTWhCVuWjbSVSofwdm7IYPtMTWTbcNUWA== +"@peculiar/asn1-schema@^2.0.1", "@peculiar/asn1-schema@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.0.3.tgz#c6c097e842ebb8a07d198b68cd49f2cf9f3571b5" + integrity sha512-STqC+Tfx2dTiIGRmokjsKOeqsfhoV6WaBwFr7BVicSfHLAVSPrZXiugyD8AELrjQdJ9INWpL3N7YSJyU5a1ZwA== dependencies: + "@types/asn1js" "^0.0.1" asn1js "^2.0.26" + pvtsutils "^1.0.10" tslib "^1.11.1" "@peculiar/json-schema@^1.1.10": @@ -1158,23 +1160,30 @@ tslib "^1.11.1" "@peculiar/webcrypto@^1.0.22": - version "1.0.27" - resolved "https://registry.yarnpkg.com/@peculiar/webcrypto/-/webcrypto-1.0.27.tgz#f32c58656267c8f8419a6b574322573a1b83a683" - integrity sha512-sERMakD19gNhwBVXGGoJjBfc28bDbd2YWaio7/x8jKtvwMKNuljM7ANQ6LzEkEvqFAyjf3bhBZktJ6UXy/0Plg== + version "1.1.1" + resolved "https://registry.yarnpkg.com/@peculiar/webcrypto/-/webcrypto-1.1.1.tgz#4c7498e4861878e299ef058bce1208a4d063d0ff" + integrity sha512-Bu2XgOvzirnLcojZYs4KQ8hOLf2ETpa0NL6btQt5NgsAwctI6yVkzgYP+EcG7Mm579RBP+V0LM5rXyMlTVx23A== dependencies: - "@peculiar/asn1-schema" "^1.0.5" + "@peculiar/asn1-schema" "^2.0.3" "@peculiar/json-schema" "^1.1.10" pvtsutils "^1.0.10" - tslib "^1.11.1" - webcrypto-core "^1.0.19-next.0" + tslib "^1.11.2" + webcrypto-core "^1.1.0" "@sinonjs/commons@^1.7.0": - version "1.7.2" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.7.2.tgz#505f55c74e0272b43f6c52d81946bed7058fc0e2" - integrity sha512-+DUO6pnp3udV/v2VfUWgaY5BIE1IfT7lLfeDzPVeMT1XKkaAp9LgSI9x5RtrFQoZ9Oi0PgXQQHPaoKu7dCjVxw== + version "1.8.0" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.0.tgz#c8d68821a854c555bba172f3b06959a0039b236d" + integrity sha512-wEj54PfsZ5jGSwMX68G8ZXFawcSglQSXqCftWX3ec8MDUzQdHgcKvw97awHbY0efQEL5iKUOAmmVtoYgmrSG4Q== dependencies: type-detect "4.0.8" +"@types/asn1js@^0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@types/asn1js/-/asn1js-0.0.1.tgz#ef8b9f9708cb1632a1c3a9cd27717caabe793bc2" + integrity sha1-74uflwjLFjKhw6nNJ3F8qr55O8I= + dependencies: + "@types/pvutils" "*" + "@types/babel__core@^7.1.0": version "7.1.7" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.7.tgz#1dacad8840364a57c98d0dd4855c6dd3752c6b89" @@ -1202,9 +1211,9 @@ "@babel/types" "^7.0.0" "@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.0.10" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.10.tgz#d9a99f017317d9b3d1abc2ced45d3bca68df0daf" - integrity sha512-74fNdUGrWsgIB/V9kTO5FGHPWYY6Eqn+3Z7L6Hc4e/BxjYV7puvBqp5HwsVYYfLm6iURYBNCx4Ut37OF9yitCw== + version "7.0.11" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.11.tgz#1ae3010e8bf8851d324878b42acec71986486d18" + integrity sha512-ddHK5icION5U6q11+tV2f9Mo6CZVuT8GJKld2q9LqHSZbvLbH34Kcu2yFGckZut453+eQU6btIA3RihmnRgI+Q== dependencies: "@babel/types" "^7.3.0" @@ -1241,9 +1250,9 @@ "@types/node" "*" "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff" - integrity sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg== + version "2.0.2" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.2.tgz#79d7a78bad4219f4c03d6557a1c72d9ca6ba62d5" + integrity sha512-rsZg7eL+Xcxsxk2XlBt9KcG8nOp9iYdKCOikY9x2RFJCyOdNj4MKPQty0e8oZr29vVAzKXr1BmR+kZauti3o1w== "@types/istanbul-lib-report@*": version "3.0.0" @@ -1253,9 +1262,9 @@ "@types/istanbul-lib-coverage" "*" "@types/istanbul-reports@^1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz#7a8cbf6a406f36c8add871625b278eaf0b0d255a" - integrity sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA== + version "1.1.2" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz#e875cc689e47bce549ec81f3df5e6f6f11cfaeb2" + integrity sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw== dependencies: "@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-report" "*" @@ -1265,6 +1274,11 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339" integrity sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA== +"@types/lodash@^4.14.152": + version "4.14.152" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.152.tgz#7e7679250adce14e749304cdb570969f77ec997c" + integrity sha512-Vwf9YF2x1GE3WNeUMjT5bTHa2DqgUo87ocdgTScupY2JclZ5Nn7W2RLM/N0+oreexUk8uaVugR81NnTY/jNNXg== + "@types/minimatch@*": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" @@ -1276,15 +1290,25 @@ integrity sha512-jhMOZSS0UGYTS9pqvt6q3wtT3uvOSve5piTEmTMx3zzTuBLvSIMxSIBIc3d5lajVD5h4xc41AMZD2M5orN3PxA== "@types/node@*": - version "13.11.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-13.11.0.tgz#390ea202539c61c8fa6ba4428b57e05bc36dc47b" - integrity sha512-uM4mnmsIIPK/yeO+42F2RQhGUIs39K2RFmugcJANppXe6J1nvH87PvzPZYpza7Xhhs8Yn9yIAVdLZ84z61+0xQ== + version "14.0.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.5.tgz#3d03acd3b3414cf67faf999aed11682ed121f22b" + integrity sha512-90hiq6/VqtQgX8Sp0EzeIsv3r+ellbGj4URKj5j30tLlZvRUpnAe9YbYnjl3pJM93GyXU0tghHhvXHq+5rnCKA== + +"@types/node@^12.12.41": + version "12.12.42" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.42.tgz#d0d1149336bd07540dd1ea576692829d575dec34" + integrity sha512-R/9QdYFLL9dE9l5cWWzWIZByVGFd7lk7JVOJ7KD+E1SJ4gni7XJRLz9QTjyYQiHIqEAgku9VgxdLjMlhhUaAFg== "@types/prop-types@*": version "15.7.3" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== +"@types/pvutils@*": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@types/pvutils/-/pvutils-0.0.2.tgz#e21684962cfa58ac920fd576d90556032dc86009" + integrity sha512-CgQAm7pjyeF3Gnv78ty4RBVIfluB+Td+2DR8iPaU0prF18pkzptHHP+DoKPfpsJYknKsVZyVsJEu5AuGgAqQ5w== + "@types/qrcode@^1.3.4": version "1.3.4" resolved "https://registry.yarnpkg.com/@types/qrcode/-/qrcode-1.3.4.tgz#984d97bb72caa558d470158701081ccb712f616b" @@ -1292,7 +1316,14 @@ dependencies: "@types/node" "*" -"@types/react@*": +"@types/react-dom@^16.9.8": + version "16.9.8" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.8.tgz#fe4c1e11dfc67155733dfa6aa65108b4971cb423" + integrity sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA== + dependencies: + "@types/react" "*" + +"@types/react@*", "@types/react@^16.9": version "16.9.35" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.35.tgz#a0830d172e8aadd9bd41709ba2281a3124bbd368" integrity sha512-q0n0SsWcGc8nDqH2GJfWQWUOmZSJhXV64CjVN5SvcNti3TdEaA3AH0D8DwNmMdzjMAC/78tB8nAZIlV8yTz+zQ== @@ -1300,14 +1331,6 @@ "@types/prop-types" "*" csstype "^2.2.0" -"@types/react@16.9": - version "16.9.32" - resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.32.tgz#f6368625b224604148d1ddf5920e4fefbd98d383" - integrity sha512-fmejdp0CTH00mOJmxUPPbWCEBWPvRIL4m8r0qD+BSDUqmutPyGQCHifzMpMzdvZwROdEdL78IuZItntFWgPXHQ== - dependencies: - "@types/prop-types" "*" - csstype "^2.2.0" - "@types/stack-utils@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" @@ -1340,9 +1363,9 @@ integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw== "@types/yargs@^13.0.0": - version "13.0.8" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-13.0.8.tgz#a38c22def2f1c2068f8971acb3ea734eb3c64a99" - integrity sha512-XAvHLwG7UQ+8M4caKIH0ZozIOYay5fQkAgyIXegXT9jPtdIGdhga+sUEdAr1CiG46aB+c64xQEYyEzlwWVTNzA== + version "13.0.9" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-13.0.9.tgz#44028e974343c7afcf3960f1a2b1099c39a7b5e1" + integrity sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg== dependencies: "@types/yargs-parser" "*" @@ -1352,26 +1375,26 @@ integrity sha512-GQLOT+SN20a+AI51y3fAimhyTF4Y0RG+YP3gf91OibIZ7CJmPFgoZi+ZR5a+vRbS01LbQosITWum4ATmJ1Z6Pg== "@typescript-eslint/experimental-utils@^2.5.0": - version "2.27.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.27.0.tgz#801a952c10b58e486c9a0b36cf21e2aab1e9e01a" - integrity sha512-vOsYzjwJlY6E0NJRXPTeCGqjv5OHgRU1kzxHKWJVPjDYGbPgLudBXjIlc+OD1hDBZ4l1DLbOc5VjofKahsu9Jw== + version "2.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz#d3524b644cdb40eebceca67f8cf3e4cc9c8f980f" + integrity sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "2.27.0" + "@typescript-eslint/typescript-estree" "2.34.0" eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/typescript-estree@2.27.0": - version "2.27.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.27.0.tgz#a288e54605412da8b81f1660b56c8b2e42966ce8" - integrity sha512-t2miCCJIb/FU8yArjAvxllxbTiyNqaXJag7UOpB5DVoM3+xnjeOngtqlJkLRnMtzaRcJhe3CIR9RmL40omubhg== +"@typescript-eslint/typescript-estree@2.34.0": + version "2.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz#14aeb6353b39ef0732cc7f1b8285294937cf37d5" + integrity sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg== dependencies: debug "^4.1.1" eslint-visitor-keys "^1.1.0" glob "^7.1.6" is-glob "^4.0.1" lodash "^4.17.15" - semver "^6.3.0" + semver "^7.3.2" tsutils "^3.17.1" "@webassemblyjs/ast@1.9.0": @@ -1557,7 +1580,7 @@ acorn@^5.5.3: resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e" integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg== -acorn@^6.0.1, acorn@^6.0.7, acorn@^6.2.1: +acorn@^6.0.1, acorn@^6.0.7, acorn@^6.4.1: version "6.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== @@ -1610,9 +1633,9 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1: integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ== ajv@^6.1.0, ajv@^6.10.2, ajv@^6.5.5, ajv@^6.9.1: - version "6.12.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.0.tgz#06d60b96d87b8454a5adaba86e7854da629db4b7" - integrity sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw== + version "6.12.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd" + integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ== dependencies: fast-deep-equal "^3.1.1" fast-json-stable-stringify "^2.0.0" @@ -1709,7 +1732,7 @@ array-find-index@^1.0.1: resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= -array-includes@^3.0.3, array-includes@^3.1.1: +array-includes@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.1.tgz#cdd67e6852bdf9c1215460786732255ed2459348" integrity sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ== @@ -1835,17 +1858,17 @@ atob@^2.1.2: integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== autoprefixer@^9.0.0: - version "9.7.6" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.7.6.tgz#63ac5bbc0ce7934e6997207d5bb00d68fa8293a4" - integrity sha512-F7cYpbN7uVVhACZTeeIeealwdGM6wMtfWARVLTy5xmKtgVdBNJvbDRoCK3YO1orcs7gv/KwYlb3iXwu9Ug9BkQ== + version "9.8.0" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.0.tgz#68e2d2bef7ba4c3a65436f662d0a56a741e56511" + integrity sha512-D96ZiIHXbDmU02dBaemyAg53ez+6F5yZmapmgKcjm35yEe1uVDYI8hGW3VYoGRaG290ZFf91YxHrR518vC0u/A== dependencies: - browserslist "^4.11.1" - caniuse-lite "^1.0.30001039" + browserslist "^4.12.0" + caniuse-lite "^1.0.30001061" chalk "^2.4.2" normalize-range "^0.1.2" num2fraction "^1.2.2" - postcss "^7.0.27" - postcss-value-parser "^4.0.3" + postcss "^7.0.30" + postcss-value-parser "^4.1.0" await-lock@^2.0.1: version "2.0.1" @@ -1858,9 +1881,9 @@ aws-sign2@~0.7.0: integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= aws4@^1.8.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e" - integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug== + version "1.10.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.0.tgz#a17b3a8ea811060e74d47d306122400ad4497ae2" + integrity sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA== babel-eslint@^10.0.3: version "10.1.0" @@ -1887,10 +1910,10 @@ babel-jest@^24.9.0: chalk "^2.4.2" slash "^2.0.0" -babel-plugin-dynamic-import-node@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz#f00f507bdaa3c3e3ff6e7e5e98d90a7acab96f7f" - integrity sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ== +babel-plugin-dynamic-import-node@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" + integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== dependencies: object.assign "^4.1.0" @@ -1997,14 +2020,19 @@ bluebird@^3.5.0, bluebird@^3.5.5: integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== blueimp-canvas-to-blob@^3.5.0: - version "3.18.0" - resolved "https://registry.yarnpkg.com/blueimp-canvas-to-blob/-/blueimp-canvas-to-blob-3.18.0.tgz#15f67cd1469f0be4d90c4619a0499a76bb835f79" - integrity sha512-AkYW5KQ0kTMrmcXvSVi+2TsWDXVZwrJM3g4o7r2z6OA3IlMhlAnoBNWI1ow45jfRr/co7tNch4OdNyb3WU3Pxw== + version "3.27.0" + resolved "https://registry.yarnpkg.com/blueimp-canvas-to-blob/-/blueimp-canvas-to-blob-3.27.0.tgz#a2bd5c43587b95dedf0f6998603452d1bfcc9b9e" + integrity sha512-AcIj+hCw6WquxzJuzC6KzgYmqxLFeTWe88KuY2BEIsW1zbEOfoinDAGlhyvFNGt+U3JElkVSK7anA1FaSdmmfA== -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: - version "4.11.8" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" - integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.4.0: + version "4.11.9" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828" + integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw== + +bn.js@^5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.2.tgz#c9686902d3c9a27729f43ab10f9d79c2004da7b0" + integrity sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA== boolbase@~1.0.0: version "1.0.0" @@ -2100,7 +2128,7 @@ browserify-des@^1.0.0: inherits "^2.0.1" safe-buffer "^5.1.2" -browserify-rsa@^4.0.0: +browserify-rsa@^4.0.0, browserify-rsa@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" integrity sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ= @@ -2109,17 +2137,19 @@ browserify-rsa@^4.0.0: randombytes "^2.0.1" browserify-sign@^4.0.0: - version "4.0.4" - resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298" - integrity sha1-qk62jl17ZYuqa/alfmMMvXqT0pg= + version "4.2.0" + resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.0.tgz#545d0b1b07e6b2c99211082bf1b12cce7a0b0e11" + integrity sha512-hEZC1KEeYuoHRqhGhTy6gWrpJA3ZDjFWv0DE61643ZnOXAKJb3u7yWcrU0mMc9SwAqK1n7myPGndkp0dFG7NFA== dependencies: - bn.js "^4.1.1" - browserify-rsa "^4.0.0" - create-hash "^1.1.0" - create-hmac "^1.1.2" - elliptic "^6.0.0" - inherits "^2.0.1" - parse-asn1 "^5.0.0" + bn.js "^5.1.1" + browserify-rsa "^4.0.1" + create-hash "^1.2.0" + create-hmac "^1.1.7" + elliptic "^6.5.2" + inherits "^2.0.4" + parse-asn1 "^5.1.5" + readable-stream "^3.6.0" + safe-buffer "^5.2.0" browserify-zlib@^0.2.0: version "0.2.0" @@ -2128,13 +2158,13 @@ browserify-zlib@^0.2.0: dependencies: pako "~1.0.5" -browserslist@^4.11.1, browserslist@^4.8.3, browserslist@^4.9.1: - version "4.11.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.11.1.tgz#92f855ee88d6e050e7e7311d987992014f1a1f1b" - integrity sha512-DCTr3kDrKEYNw6Jb9HFxVLQNaue8z+0ZfRBRjmCunKDEXEBajKDj2Y+Uelg+Pi29OnvaSGwjOsnRyNEkXzHg5g== +browserslist@^4.11.1, browserslist@^4.12.0, browserslist@^4.8.5: + version "4.12.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.12.0.tgz#06c6d5715a1ede6c51fc39ff67fd647f740b656d" + integrity sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg== dependencies: - caniuse-lite "^1.0.30001038" - electron-to-chromium "^1.3.390" + caniuse-lite "^1.0.30001043" + electron-to-chromium "^1.3.413" node-releases "^1.1.53" pkg-up "^2.0.0" @@ -2190,9 +2220,9 @@ buffer@^4.3.0: isarray "^1.0.0" buffer@^5.4.3: - version "5.5.0" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.5.0.tgz#9c3caa3d623c33dd1c7ef584b89b88bf9c9bc1ce" - integrity sha512-9FTEDjLjwoAkEwyMGDjYJQN2gfRgOKBKRfiglhvibGbpeeU/pQn1bJxQqm32OD/AIeEuHxU9roxXxg34Byp/Ww== + version "5.6.0" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" + integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== dependencies: base64-js "^1.0.2" ieee754 "^1.1.4" @@ -2291,10 +2321,10 @@ camelcase@^5.0.0, camelcase@^5.3.1: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -caniuse-lite@^1.0.30001038, caniuse-lite@^1.0.30001039: - version "1.0.30001039" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001039.tgz#b3814a1c38ffeb23567f8323500c09526a577bbe" - integrity sha512-SezbWCTT34eyFoWHgx8UWso7YtvtM7oosmFoXbCkdC6qJzRfBTeTgE9REtKtiuKXuMwWTZEvdnFNGAyVMorv8Q== +caniuse-lite@^1.0.30001043, caniuse-lite@^1.0.30001061: + version "1.0.30001065" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001065.tgz#e8d7fef61cdfd8a7107493ad6bf551a4eb59c68f" + integrity sha512-DDxCLgJ266YnAHQv0jS1wdOaihRFF52Zgmlag39sQJVy2H46oROpJp4hITstqhdB8qnHSrKNoAEkQA9L/oYF9A== capture-exit@^2.0.0: version "2.0.0" @@ -2378,10 +2408,10 @@ chokidar@^2.1.8: optionalDependencies: fsevents "^1.2.7" -chokidar@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.1.tgz#c84e5b3d18d9a4d77558fef466b1bf16bbeb3450" - integrity sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg== +chokidar@^3.3.1, chokidar@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.0.tgz#b30611423ce376357c765b9b8f904b9fba3c0be8" + integrity sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ== dependencies: anymatch "~3.1.1" braces "~3.0.2" @@ -2389,7 +2419,7 @@ chokidar@^3.3.1: is-binary-path "~2.1.0" is-glob "~4.0.1" normalize-path "~3.0.0" - readdirp "~3.3.0" + readdirp "~3.4.0" optionalDependencies: fsevents "~2.1.2" @@ -2441,9 +2471,9 @@ cli-cursor@^2.1.0: restore-cursor "^2.0.0" cli-width@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" - integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= + version "2.2.1" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" + integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw== cliui@^4.0.0: version "4.1.0" @@ -2618,17 +2648,17 @@ copy-descriptor@^0.1.0: integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= core-js-compat@^3.6.2: - version "3.6.4" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.4.tgz#938476569ebb6cda80d339bcf199fae4f16fff17" - integrity sha512-zAa3IZPvsJ0slViBQ2z+vgyyTuhd3MFn1rBQjZSKVEgB0UMYhUkCj9jJUVPgGTGqWvsBVmfnruXgTcNyTlEiSA== + version "3.6.5" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.5.tgz#2a51d9a4e25dfd6e690251aa81f99e3c05481f1c" + integrity sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng== dependencies: - browserslist "^4.8.3" + browserslist "^4.8.5" semver "7.0.0" core-js-pure@^3.0.0: - version "3.6.4" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.4.tgz#4bf1ba866e25814f149d4e9aaa08c36173506e3a" - integrity sha512-epIhRLkXdgv32xIUFaaAry2wdxZYBi6bgM7cB136dzzXXa+dFyRLTZeLUJxnd8ShrmyVXBub63n2NHo2JAt8Cw== + version "3.6.5" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813" + integrity sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA== core-js@^1.0.0: version "1.2.7" @@ -2679,7 +2709,7 @@ create-ecdh@^4.0.0: bn.js "^4.1.0" elliptic "^6.0.0" -create-hash@^1.1.0, create-hash@^1.1.2: +create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== @@ -2690,7 +2720,7 @@ create-hash@^1.1.0, create-hash@^1.1.2: ripemd160 "^2.0.1" sha.js "^2.4.0" -create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: +create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== @@ -2941,9 +2971,9 @@ diff-dom@^4.1.3: updates "^8.5.2" diff-match-patch@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.4.tgz#6ac4b55237463761c4daf0dc603eb869124744b1" - integrity sha512-Uv3SW8bmH9nAtHKaKSanOQmj2DnlH65fUpcrMdfdaOxUG02QQ4YGZ8AE7kKOMisF7UqvOlGKVYWRvezdncW9lg== + version "1.0.5" + resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.5.tgz#abb584d5f10cd1196dfc55aa03701592ae3f7b37" + integrity sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw== diff-sequences@^24.9.0: version "24.9.0" @@ -3064,9 +3094,9 @@ domutils@^1.5.1: domelementtype "1" domutils@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.0.0.tgz#15b8278e37bfa8468d157478c58c367718133c08" - integrity sha512-n5SelJ1axbO636c2yUtOGia/IcJtVtlhQbFiVDBZHKV5ReJO1ViX7sFEemtuyoAnBxk5meNSYgA8V4s0271efg== + version "2.1.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.1.0.tgz#7ade3201af43703fde154952e3a868eb4b635f16" + integrity sha512-CD9M0Dm1iaHfQ1R/TI+z3/JWp/pgub0j4jIQKH89ARR4ATAV2nbaOQS5XxU9maJP5jHaPdDDQSEHuE2UmpUTKg== dependencies: dom-serializer "^0.2.1" domelementtype "^2.0.1" @@ -3097,12 +3127,12 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" -electron-to-chromium@^1.3.390: - version "1.3.398" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.398.tgz#4c01e29091bf39e578ac3f66c1f157d92fa5725d" - integrity sha512-BJjxuWLKFbM5axH3vES7HKMQgAknq9PZHBkMK/rEXUQG9i1Iw5R+6hGkm6GtsQSANjSUrh/a6m32nzCNDNo/+w== +electron-to-chromium@^1.3.413: + version "1.3.451" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.451.tgz#0c075af3e2f06d706670bde0279432802ca8c83f" + integrity sha512-2fvco0F2bBIgqzO8GRP0Jt/91pdrf9KfZ5FsmkYkjERmIJG585cFeFZV4+CO6oTmU3HmCTgfcZuEa7kW8VUh3A== -elliptic@^6.0.0: +elliptic@^6.0.0, elliptic@^6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.2.tgz#05c5678d7173c049d8ca433552224a495d0e3762" integrity sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw== @@ -3183,9 +3213,9 @@ entities@^1.1.1, "entities@~ 1.1.1", entities@~1.1.1: integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== entities@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4" - integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw== + version "2.0.2" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.2.tgz#ac74db0bba8d33808bbf36809c3a5c3683531436" + integrity sha512-dmD3AvJQBUjKpcNkoqr+x+IF0SdRtPz9Vk0uTy4yWqga9ibB6s4v++QFWNohjiUGoMlF552ZvNyXDxz5iW0qmw== enzyme-adapter-react-16@^1.15.1: version "1.15.2" @@ -3362,9 +3392,9 @@ eslint-plugin-flowtype@^2.30.0: lodash "^4.17.10" eslint-plugin-jest@^23.0.4: - version "23.8.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-23.8.2.tgz#6f28b41c67ef635f803ebd9e168f6b73858eb8d4" - integrity sha512-xwbnvOsotSV27MtAe7s8uGWOori0nUsrXh2f1EnpmXua8sDfY6VZhHAhHg2sqK7HBNycRQExF074XSZ7DvfoFg== + version "23.13.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-23.13.1.tgz#b2ce83f76064ad8ba1f1f26f322b86a86e44148e" + integrity sha512-TRLJH6M6EDvGocD98a7yVThrAOCK9WJfo9phuUb0MJptcrOYZeCKzC9aOzZCD93sxXCsiJVZywaTHdI/mAi0FQ== dependencies: "@typescript-eslint/experimental-utils" "^2.5.0" @@ -3374,9 +3404,9 @@ eslint-plugin-react-hooks@^2.0.1: integrity sha512-Y2c4b55R+6ZzwtTppKwSmK/Kar8AdLiC2f9NADCuxbcTgPPg41Gyqa6b9GppgXSvCtkRw43ZE86CT5sejKC6/g== eslint-plugin-react@^7.7.0: - version "7.19.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.19.0.tgz#6d08f9673628aa69c5559d33489e855d83551666" - integrity sha512-SPT8j72CGuAP+JFbT0sJHOB80TX/pu44gQ4vXH/cq+hQTiY2PuZ6IHkqXJV6x1b28GDdo1lbInjKUrrdUf0LOQ== + version "7.20.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.20.0.tgz#f98712f0a5e57dfd3e5542ef0604b8739cd47be3" + integrity sha512-rqe1abd0vxMjmbPngo4NaYxTcR3Y4Hrmc/jg4T+sYz63yqlmJRknpEQfmWY+eDWPuMmix6iUIK+mv0zExjeLgA== dependencies: array-includes "^3.1.1" doctrine "^2.1.0" @@ -3387,7 +3417,6 @@ eslint-plugin-react@^7.7.0: object.values "^1.1.1" prop-types "^15.7.2" resolve "^1.15.1" - semver "^6.3.0" string.prototype.matchall "^4.0.2" xregexp "^4.3.0" @@ -3488,11 +3517,11 @@ esprima@^4.0.0, esprima@^4.0.1: integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== esquery@^1.0.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.2.0.tgz#a010a519c0288f2530b3404124bfb5f02e9797fe" - integrity sha512-weltsSqdeWIX9G2qQZz7KlTRJdkkOCTPgLYJUz1Hacf48R4YOwGPHO3+ORfWedqJKbq5WQmsgK90n+pFLIKt/Q== + version "1.3.1" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57" + integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ== dependencies: - estraverse "^5.0.0" + estraverse "^5.1.0" esrecurse@^4.1.0: version "4.2.1" @@ -3506,10 +3535,10 @@ estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== -estraverse@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.0.0.tgz#ac81750b482c11cca26e4b07e83ed8f75fbcdc22" - integrity sha512-j3acdrMzqrxmJTNj5dbr1YbjacrYgAxVMeF0gK16E3j494mOe7xygM/ZLIguEQ0ETwAg2hlJCtHRGav+y0Ny5A== +estraverse@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.1.0.tgz#374309d39fd935ae500e7b92e8a6b4c720e59642" + integrity sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw== estree-walker@^0.5.0: version "0.5.2" @@ -3880,10 +3909,10 @@ flux@2.1.1: fbjs "0.1.0-alpha.7" immutable "^3.7.4" -focus-lock@^0.6.6: - version "0.6.6" - resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-0.6.6.tgz#98119a755a38cfdbeda0280eaa77e307eee850c7" - integrity sha512-Dx69IXGCq1qsUExWuG+5wkiMqVM/zGx/reXSJSLogECwp3x6KeNQZ+NAetgxEFpnC41rD8U3+jRCW68+LNzdtw== +focus-lock@^0.6.7: + version "0.6.8" + resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-0.6.8.tgz#61985fadfa92f02f2ee1d90bc738efaf7f3c9f46" + integrity sha512-vkHTluRCoq9FcsrldC0ulQHiyBYgVJB2CX53I8r0nTC6KnEij7Of0jpBspjt3/CuNb6fyoj3aOh9J2HgQUM0og== focus-visible@^5.0.2: version "5.1.0" @@ -3950,17 +3979,17 @@ fs.realpath@^1.0.0: integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= fsevents@^1.2.7: - version "1.2.12" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.12.tgz#db7e0d8ec3b0b45724fd4d83d43554a8f1f0de5c" - integrity sha512-Ggd/Ktt7E7I8pxZRbGIs7vwqAPscSESMrCSkx2FtWeqmheJgCo2R74fTsZFCifr0VTPwqRpPv17+6b8Zp7th0Q== + version "1.2.13" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38" + integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw== dependencies: bindings "^1.5.0" nan "^2.12.1" fsevents@~2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805" - integrity sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA== + version "2.1.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" + integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== function-bind@^1.1.1: version "1.1.1" @@ -4151,9 +4180,9 @@ gonzales-pe@^4.2.3: minimist "^1.2.5" graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2: - version "4.2.3" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" - integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== + version "4.2.4" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" + integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== growly@^1.3.0: version "1.3.0" @@ -4227,12 +4256,13 @@ has@^1.0.1, has@^1.0.3: function-bind "^1.1.1" hash-base@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" - integrity sha1-X8hoaEfs1zSZQDMZprCj8/auSRg= + version "3.1.0" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" + integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" + inherits "^2.0.4" + readable-stream "^3.6.0" + safe-buffer "^5.2.0" hash.js@^1.0.0, hash.js@^1.0.3: version "1.1.7" @@ -4297,9 +4327,9 @@ html-encoding-sniffer@^1.0.2: whatwg-encoding "^1.0.1" html-entities@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f" - integrity sha1-DfKTUfByEWNRXfueVUPl9u7VFi8= + version "1.3.1" + resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.3.1.tgz#fb9a1a4b5b14c5daba82d3e34c6ae4fe701a0e44" + integrity sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA== html-escaper@^2.0.0: version "2.0.2" @@ -4398,9 +4428,9 @@ ignore@^4.0.3, ignore@^4.0.6: integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== ignore@^5.0.4: - version "5.1.4" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.4.tgz#84b7b3dbe64552b6ef0eca99f6743dbec6d97adf" - integrity sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A== + version "5.1.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.6.tgz#643194ad4bf2712f37852e386b6998eff0db2106" + integrity sha512-cgXgkypZBcCnOgSihyeqbo6gjIaIyDqPQB7Ra4vhE9m6kigdGoQDMHjviFhRZo3IMlRy6yElosoviMs5YxZXUA== immutable@^3.7.4: version "3.8.2" @@ -4469,7 +4499,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -4819,11 +4849,6 @@ is-plain-object@^2.0.3, is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" -is-promise@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" - integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= - is-regex@^1.0.3, is-regex@^1.0.4, is-regex@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae" @@ -5356,9 +5381,9 @@ jest@^24.9.0: integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== js-yaml@^3.13.0, js-yaml@^3.13.1: - version "3.13.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" - integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== + version "3.14.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" + integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== dependencies: argparse "^1.0.7" esprima "^4.0.0" @@ -5460,11 +5485,11 @@ jsprim@^1.2.2: verror "1.10.0" jsx-ast-utils@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz#8a9364e402448a3ce7f14d357738310d9248054f" - integrity sha512-EdIHFMm+1BPynpKOpdPqiOsvnIrInRGJD7bzPZdPkjitQEqpdpUuFpq4T0npZFKTiB3RhWFdGN+oqOJIdhDhQA== + version "2.3.0" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.3.0.tgz#edd727794ea284d7fda575015ed1b0cde0289ab6" + integrity sha512-3HNoc7nZ1hpZIKB3hJ7BlFRkzCx2BynRtfSwbkqZdpRdvAPsGMnzclPwrvDBS7/lalHTj21NwIeaEpysHBOudg== dependencies: - array-includes "^3.0.3" + array-includes "^3.1.1" object.assign "^4.1.0" kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: @@ -5662,9 +5687,9 @@ log-symbols@^2.0.0, log-symbols@^2.2.0: chalk "^2.0.1" loglevel@^1.6.4: - version "1.6.7" - resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.7.tgz#b3e034233188c68b889f5b862415306f565e2c56" - integrity sha512-cY2eLFrQSAfVPhCgH1s7JI73tMbg9YC3v3+ZHVW67sBS7UxWzNEk/ZBbSfLykBWHp33dqqtOv82gjhKEi81T/A== + version "1.6.8" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.8.tgz#8a25fb75d092230ecd4457270d80b54e28011171" + integrity sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA== lolex@^5.1.2: version "5.1.2" @@ -5906,17 +5931,17 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -mime-db@1.43.0: - version "1.43.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" - integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ== +mime-db@1.44.0: + version "1.44.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" + integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== mime-types@^2.1.12, mime-types@~2.1.19: - version "2.1.26" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" - integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ== + version "2.1.27" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" + integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== dependencies: - mime-db "1.43.0" + mime-db "1.44.0" mimic-fn@^1.0.0: version "1.2.0" @@ -6027,9 +6052,9 @@ mute-stream@0.0.7: integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= nan@^2.12.1: - version "2.14.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" - integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== + version "2.14.1" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" + integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== nanomatch@^1.2.9: version "1.2.13" @@ -6054,9 +6079,9 @@ natural-compare@^1.4.0: integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= nearley@^2.7.10: - version "2.19.1" - resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.19.1.tgz#4af4006e16645ff800e9f993c3af039857d9dbdc" - integrity sha512-xq47GIUGXxU9vQg7g/y1o1xuKnkO7ev4nRWqftmQrLkfnE/FjRqDaGOUakM8XHPn/6pW3bGjU2wgoJyId90rqg== + version "2.19.3" + resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.19.3.tgz#ae3b040e27616b5348102c436d1719209476a5a1" + integrity sha512-FpAy1PmTsUpOtgxr23g4jRNvJHYzZEW2PixXeSzksLR/ykPfwKhAodc2+9wQhY+JneWLcvkDw6q7FJIsIdF/aQ== dependencies: commander "^2.19.0" moo "^0.5.0" @@ -6142,9 +6167,9 @@ node-notifier@^5.4.2: which "^1.3.0" node-releases@^1.1.53: - version "1.1.53" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.53.tgz#2d821bfa499ed7c5dffc5e2f28c88e78a08ee3f4" - integrity sha512-wp8zyQVwef2hpZ/dJH7SfSrIPD6YoJz6BDQDpGEkcA0s3LpAQoxBIYmfIq6QAhC1DhwsyCgTaTTcONwX8qzCuQ== + version "1.1.56" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.56.tgz#bc054a417d316e3adac90eafb7e1932802f28705" + integrity sha512-EVo605FhWLygH8a64TjgpjyHYOihkxECwX1bHHr8tETJKWEiWS2YJjPbvsX2jFjnjTNEgBCmk9mLjKG1Mf11cw== normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: version "2.5.0" @@ -6232,9 +6257,12 @@ object-inspect@^1.1.0, object-inspect@^1.7.0: integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== object-is@^1.0.1, object-is@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.2.tgz#6b80eb84fe451498f65007982f035a5b445edec4" - integrity sha512-Epah+btZd5wrrfjkJZq1AOB9O6OxUQto45hzFd7lXGrpHPGE0W1k+426yrZV+k6NJOzLNNW/nVsmZdIWsAqoOQ== + version "1.1.2" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.2.tgz#c5d2e87ff9e119f78b7a088441519e2eec1573b6" + integrity sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.0.9, object-keys@^1.1.1: version "1.1.1" @@ -6259,13 +6287,12 @@ object.assign@^4.1.0: object-keys "^1.0.11" object.entries@^1.1.0, object.entries@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.1.tgz#ee1cf04153de02bb093fec33683900f57ce5399b" - integrity sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ== + version "1.1.2" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.2.tgz#bc73f00acb6b6bb16c203434b10f9a7e797d3add" + integrity sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA== dependencies: define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - function-bind "^1.1.1" + es-abstract "^1.17.5" has "^1.0.3" object.fromentries@^2.0.2: @@ -6441,7 +6468,7 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" -parse-asn1@^5.0.0: +parse-asn1@^5.0.0, parse-asn1@^5.1.5: version "5.1.5" resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.5.tgz#003271343da58dc94cace494faef3d2147ecea0e" integrity sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ== @@ -6575,7 +6602,7 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -picomatch@^2.0.4, picomatch@^2.0.7: +picomatch@^2.0.4, picomatch@^2.2.1: version "2.2.2" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== @@ -6703,11 +6730,11 @@ postcss-sass@^0.3.5: postcss "^7.0.1" postcss-scss@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-2.0.0.tgz#248b0a28af77ea7b32b1011aba0f738bda27dea1" - integrity sha512-um9zdGKaDZirMm+kZFKKVsnKPF7zF7qBAtIfTSnZXD1jZ0JNZIxdB6TxQOjCnlSzLRInVl2v3YdBh/M881C4ug== + version "2.1.1" + resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-2.1.1.tgz#ec3a75fa29a55e016b90bf3269026c53c1d2b383" + integrity sha512-jQmGnj0hSGLd9RscFw9LyuSVAa5Bl1/KBPqG1NQw9w8ND55nY4ZEsdlVuYJvLPpV+y0nwTV5v/4rHPzZRihQbA== dependencies: - postcss "^7.0.0" + postcss "^7.0.6" postcss-selector-parser@^3.1.0: version "3.1.2" @@ -6737,15 +6764,15 @@ postcss-value-parser@^3.3.0: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== -postcss-value-parser@^4.0.2, postcss-value-parser@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz#651ff4593aa9eda8d5d0d66593a2417aeaeb325d" - integrity sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg== +postcss-value-parser@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" + integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== -postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.13, postcss@^7.0.14, postcss@^7.0.2, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.7: - version "7.0.27" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.27.tgz#cc67cdc6b0daa375105b7c424a85567345fc54d9" - integrity sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ== +postcss@^7.0.1, postcss@^7.0.13, postcss@^7.0.14, postcss@^7.0.2, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.30, postcss@^7.0.6, postcss@^7.0.7: + version "7.0.30" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.30.tgz#cc9378beffe46a02cbc4506a0477d05fcea9a8e2" + integrity sha512-nu/0m+NtIzoubO+xdAlwZl/u5S5vi/y6BCsoL8D+8IxsD3XvBS8X4YEADNIVXKVuQvduiucnRv+vPIqj56EGMQ== dependencies: chalk "^2.4.2" source-map "^0.6.1" @@ -6928,9 +6955,9 @@ qrcode@^1.4.4: yargs "^13.2.4" qs@^6.5.2, qs@^6.6.0: - version "6.9.3" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.3.tgz#bfadcd296c2d549f1dffa560619132c977f5008e" - integrity sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw== + version "6.9.4" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.4.tgz#9090b290d1f91728d3c22e54843ca44aea5ab687" + integrity sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ== qs@~6.5.2: version "6.5.2" @@ -7036,12 +7063,12 @@ react-dom@^16.9.0: scheduler "^0.19.1" react-focus-lock@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/react-focus-lock/-/react-focus-lock-2.2.1.tgz#1d12887416925dc53481914b7cedd39494a3b24a" - integrity sha512-47g0xYcCTZccdzKRGufepY8oZ3W1Qg+2hn6u9SHZ0zUB6uz/4K4xJe7yYFNZ1qT6m+2JDm82F6QgKeBTbjW4PQ== + version "2.3.1" + resolved "https://registry.yarnpkg.com/react-focus-lock/-/react-focus-lock-2.3.1.tgz#9d5d85899773609c7eefa4fc54fff6a0f5f2fc47" + integrity sha512-j15cWLPzH0gOmRrUg01C09Peu8qbcdVqr6Bjyfxj80cNZmH+idk/bNBYEDSmkAtwkXI+xEYWSmHYqtaQhZ8iUQ== dependencies: "@babel/runtime" "^7.0.0" - focus-lock "^0.6.6" + focus-lock "^0.6.7" prop-types "^15.6.2" react-clientside-effect "^1.2.2" use-callback-ref "^1.2.1" @@ -7145,7 +7172,7 @@ read-pkg@^4.0.1: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.1.1: +readable-stream@^3.1.1, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -7163,12 +7190,12 @@ readdirp@^2.2.1: micromatch "^3.1.10" readable-stream "^2.0.2" -readdirp@~3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.3.0.tgz#984458d13a1e42e2e9f5841b129e162f369aff17" - integrity sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ== +readdirp@~3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.4.0.tgz#9fdccdf9e9155805449221ac645e8303ab5b9ada" + integrity sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ== dependencies: - picomatch "^2.0.7" + picomatch "^2.2.1" realpath-native@^1.1.0: version "1.1.0" @@ -7277,9 +7304,9 @@ registry-auth-token@4.0.0: safe-buffer "^5.0.1" regjsgen@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.1.tgz#48f0bf1a5ea205196929c0d9798b42d1ed98443c" - integrity sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg== + version "0.5.2" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733" + integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A== regjsparser@^0.6.4: version "0.6.4" @@ -7461,9 +7488,9 @@ resolve@1.1.7: integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= resolve@^1.10.0, resolve@^1.12.0, resolve@^1.15.1, resolve@^1.3.2, resolve@^1.8.1: - version "1.15.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" - integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w== + version "1.17.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" + integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== dependencies: path-parse "^1.0.6" @@ -7539,11 +7566,9 @@ rsvp@^4.8.4: integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== run-async@^2.2.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.0.tgz#e59054a5b86876cfae07f431d18cbaddc594f1e8" - integrity sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg== - dependencies: - is-promise "^2.1.0" + version "2.4.1" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== run-queue@^1.0.0, run-queue@^1.0.3: version "1.0.3" @@ -7559,10 +7584,10 @@ rxjs@^6.4.0, rxjs@^6.5.2: dependencies: tslib "^1.9.0" -safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" - integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" @@ -7597,9 +7622,9 @@ sane@^4.0.3: walker "~1.0.5" sanitize-html@^1.18.4: - version "1.22.1" - resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.22.1.tgz#5b36c92ab27917ddd2775396815c2bc1a6268310" - integrity sha512-++IMC00KfMQc45UWZJlhWOlS9eMrME38sFG9GXfR+k6oBo9JXSYQgTOZCl9j3v/smFTRNT9XNwz5DseFdMY+2Q== + version "1.24.0" + resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.24.0.tgz#9cd42f236512bfcf6259424e958551148c165a7f" + integrity sha512-TAIFx39V/y06jDd4YUz7ntCdMUXN5Z28pSG7sTP2BCLXwHA9+ermacDpQs35Evo4p6YSgmaPdSbGiX4Fgptuuw== dependencies: chalk "^2.4.1" htmlparser2 "^4.1.0" @@ -7639,7 +7664,7 @@ schema-utils@^1.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@6.3.0, semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: +semver@6.3.0, semver@^6.0.0, semver@^6.2.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== @@ -7649,6 +7674,11 @@ semver@7.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== +semver@^7.3.2: + version "7.3.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" + integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== + serialize-javascript@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61" @@ -7807,9 +7837,9 @@ source-map-resolve@^0.5.0: urix "^0.1.0" source-map-support@^0.5.16, source-map-support@^0.5.6, source-map-support@~0.5.12: - version "0.5.16" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" - integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ== + version "0.5.19" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" + integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" @@ -7835,22 +7865,22 @@ spawn-command@^0.0.2-1: integrity sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A= spdx-correct@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" - integrity sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q== + version "3.1.1" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" + integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== dependencies: spdx-expression-parse "^3.0.0" spdx-license-ids "^3.0.0" spdx-exceptions@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977" - integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA== + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== spdx-expression-parse@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" - integrity sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg== + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== dependencies: spdx-exceptions "^2.1.0" spdx-license-ids "^3.0.0" @@ -8034,9 +8064,9 @@ string.prototype.trim@^1.2.1: function-bind "^1.1.1" string.prototype.trimend@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.0.tgz#ee497fd29768646d84be2c9b819e292439614373" - integrity sha512-EEJnGqa/xNfIg05SxiPSqRS7S9qwDhYts1TSLR1BQfYUfPe1stofgGKvwERK9+9yf+PpfBMlpBaCHucXGPQfUA== + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" + integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== dependencies: define-properties "^1.1.3" es-abstract "^1.17.5" @@ -8060,9 +8090,9 @@ string.prototype.trimright@^2.1.1: string.prototype.trimend "^1.0.0" string.prototype.trimstart@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.0.tgz#afe596a7ce9de905496919406c9734845f01a2f2" - integrity sha512-iCP8g01NFYiiBOnwG1Xc3WZLyoo+RuBymwIlWncShXDDJYWN6DbnM3odslBJdgCdRlq94B5s63NWAZlcn2CS4w== + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" + integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== dependencies: define-properties "^1.1.3" es-abstract "^1.17.5" @@ -8150,15 +8180,15 @@ stylelint-config-standard@^18.2.0: stylelint-config-recommended "^2.2.0" stylelint-scss@^3.9.0: - version "3.16.0" - resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-3.16.0.tgz#6928fe57bcfc924110d09847c1f720472a9b7bd6" - integrity sha512-dAWs/gagdPYO3VDdvgRv5drRBMcWI4E//z3AXPAY1qYkSdXCEVJtEW+R9JtinG0U2rcJIu5XWaVddPQeaaufzw== + version "3.17.2" + resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-3.17.2.tgz#4d849a153f9241834396f5880db2c3c964def4e3" + integrity sha512-e0dmxqsofy/HZj4urcGSJw4S6yHDJxiQdT20/1ciCsd5lomisa7YM4+Qtt1EG4hsqEG1dbEeF855tec1UyqcSA== dependencies: lodash "^4.17.15" postcss-media-query-parser "^0.2.3" postcss-resolve-nested-selector "^0.1.1" postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.0.2" + postcss-value-parser "^4.1.0" stylelint@^9.10.1: version "9.10.1" @@ -8287,9 +8317,9 @@ terser-webpack-plugin@^1.4.3: worker-farm "^1.7.0" terser@^4.1.2, terser@^4.6.2: - version "4.6.10" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.10.tgz#90f5bd069ff456ddbc9503b18e52f9c493d3b7c2" - integrity sha512-qbF/3UOo11Hggsbsqm2hPa6+L4w7bkr+09FNseEe8xrcVD3APGLFqE+Oz1ZKAxjYnFsj80rLOfgAtJ0LNJjtTA== + version "4.7.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.7.0.tgz#15852cf1a08e3256a80428e865a2fa893ffba006" + integrity sha512-Lfb0RiZcjRDXCC3OSHJpEkxJ9Qeqs6mp2v4jf2MHfy8vGERmVDuvjXdd/EnP5Deme5F2yBRBymKmKHCBg2echw== dependencies: commander "^2.20.0" source-map "~0.6.1" @@ -8439,10 +8469,10 @@ trough@^1.0.0: resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== -tslib@^1.10.0, tslib@^1.11.1, tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: - version "1.11.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" - integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== +tslib@^1.10.0, tslib@^1.11.1, tslib@^1.11.2, tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: + version "1.13.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" + integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== tslint@^5.20.1: version "5.20.1" @@ -8512,9 +8542,9 @@ typedarray@^0.0.6: integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= typescript@^3.7.3: - version "3.8.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" - integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== + version "3.9.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.3.tgz#d3ac8883a97c26139e42df5e93eeece33d610b8a" + integrity sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ== ua-parser-js@^0.7.18: version "0.7.21" @@ -8530,9 +8560,9 @@ unherit@^1.0.4: xtend "^4.0.0" unhomoglyph@^1.0.2: - version "1.0.5" - resolved "https://registry.yarnpkg.com/unhomoglyph/-/unhomoglyph-1.0.5.tgz#a68c6244f0ec140bfe58293a1f66a9bd2a244343" - integrity sha512-rNAw2rGogjq4BVhsCX8K6qXrCcHmUaMCHETlUG0ujGZ3OHwnzJHwdMyzy3n/c9Y7lvlbckOd9nkW33grUVE3bg== + version "1.0.6" + resolved "https://registry.yarnpkg.com/unhomoglyph/-/unhomoglyph-1.0.6.tgz#ea41f926d0fcf598e3b8bb2980c2ddac66b081d3" + integrity sha512-7uvcWI3hWshSADBu4JpnyYbTVc7YlhF5GDW/oPD5AxIxl34k4wXR3WDkPnzLxkN32LiTCTKMQLtKVZiwki3zGg== unicode-canonical-property-names-ecmascript@^1.0.4: version "1.0.4" @@ -8695,9 +8725,9 @@ url@^0.11.0: querystring "0.2.0" use-callback-ref@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.1.tgz#898759ccb9e14be6c7a860abafa3ffbd826c89bb" - integrity sha512-C3nvxh0ZpaOxs9RCnWwAJ+7bJPwQI8LHF71LzbQ3BvzH5XkdtlkMadqElGevg5bYBDFip4sAnD4m06zAKebg1w== + version "1.2.3" + resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.3.tgz#9f939dfb5740807bbf9dd79cdd4e99d27e827756" + integrity sha512-DPBPh1i2adCZoIArRlTuKRy7yue7QogtEnfv0AKrWsY+GA+4EKe37zhRDouNnyWMoNQFYZZRF+2dLHsWE4YvJA== use-sidecar@^1.0.1: version "1.0.2" @@ -8829,25 +8859,34 @@ walker@^1.0.7, walker@~1.0.5: dependencies: makeerror "1.0.x" -watchpack@^1.6.0: - version "1.6.1" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.1.tgz#280da0a8718592174010c078c7585a74cd8cd0e2" - integrity sha512-+IF9hfUFOrYOOaKyfaI7h7dquUIOgyEMoQMLA7OP5FxegKA2+XdXThAZ9TU2kucfhDH7rfMHs1oPYziVGWRnZA== +watchpack-chokidar2@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz#9948a1866cbbd6cb824dea13a7ed691f6c8ddff0" + integrity sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA== dependencies: chokidar "^2.1.8" + +watchpack@^1.6.1: + version "1.7.2" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.2.tgz#c02e4d4d49913c3e7e122c3325365af9d331e9aa" + integrity sha512-ymVbbQP40MFTp+cNMvpyBpBtygHnPzPkHqoIwRRj/0B8KhqQwV8LaKjtbaxF2lK4vl8zN9wCxS46IFCU5K4W0g== + dependencies: graceful-fs "^4.1.2" neo-async "^2.5.0" + optionalDependencies: + chokidar "^3.4.0" + watchpack-chokidar2 "^2.0.0" -webcrypto-core@^1.0.19-next.0: - version "1.0.19" - resolved "https://registry.yarnpkg.com/webcrypto-core/-/webcrypto-core-1.0.19.tgz#521a0b082afecd914b8e968efb5dc381b8416c6f" - integrity sha512-6XHExtfMJrpkFDh9MiJ/y7ptX0dfZi0ogxFyelqxMu1eFowxivHfIp6DKzT+ZjU66xTuNfJkfkUk1bIB3tEOgA== +webcrypto-core@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/webcrypto-core/-/webcrypto-core-1.1.1.tgz#c9cd26f8dea696d7b5f5c1b0598ff16e6bdcab7c" + integrity sha512-xK61sFRUyZdSAJG7+bJox36+Tnhxw1PaMbmrLLp30HNTJ4mffqsY2jUMlmGq6OOoej3WO/SsH5serzlzBMZ+jg== dependencies: - "@peculiar/asn1-schema" "^1.0.5" + "@peculiar/asn1-schema" "^2.0.1" "@peculiar/json-schema" "^1.1.10" asn1js "^2.0.26" pvtsutils "^1.0.10" - tslib "^1.11.1" + tslib "^1.11.2" webidl-conversions@^4.0.2: version "4.0.2" @@ -8880,15 +8919,15 @@ webpack-sources@^1.4.0, webpack-sources@^1.4.1: source-map "~0.6.1" webpack@^4.20.2: - version "4.42.1" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.42.1.tgz#ae707baf091f5ca3ef9c38b884287cfe8f1983ef" - integrity sha512-SGfYMigqEfdGchGhFFJ9KyRpQKnipvEvjc1TwrXEPCM6H5Wywu10ka8o3KGrMzSMxMQKt8aCHUFh5DaQ9UmyRg== + version "4.43.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.43.0.tgz#c48547b11d563224c561dad1172c8aa0b8a678e6" + integrity sha512-GW1LjnPipFW2Y78OOab8NJlCflB7EFskMih2AHdvjbpKMeDJqEgSx24cXXXiPS65+WSwVyxtDsJH6jGX2czy+g== dependencies: "@webassemblyjs/ast" "1.9.0" "@webassemblyjs/helper-module-context" "1.9.0" "@webassemblyjs/wasm-edit" "1.9.0" "@webassemblyjs/wasm-parser" "1.9.0" - acorn "^6.2.1" + acorn "^6.4.1" ajv "^6.10.2" ajv-keywords "^3.4.1" chrome-trace-event "^1.0.2" @@ -8905,13 +8944,13 @@ webpack@^4.20.2: schema-utils "^1.0.0" tapable "^1.1.3" terser-webpack-plugin "^1.4.3" - watchpack "^1.6.0" + watchpack "^1.6.1" webpack-sources "^1.4.1" what-input@^5.2.6: - version "5.2.7" - resolved "https://registry.yarnpkg.com/what-input/-/what-input-5.2.7.tgz#81afbb6b82882cff8c43fa7ff1054aa46f288ffa" - integrity sha512-ruCP2skyygi0ZHnMicHuZP7vXnJh8uJXs9R7RX488HlWigSdzngdmKo5Ti11Iatp1dnYp55VfioP/WevPaK+xQ== + version "5.2.9" + resolved "https://registry.yarnpkg.com/what-input/-/what-input-5.2.9.tgz#e484628c00404d2ad5d747ac2f0fb22008f7757a" + integrity sha512-/tuM/4ngvfYB1QF3yekJsmFpIhkiHEDKCl/VYDikyHZVxoFn3U/lNgiNt7aqC8RerkoPUMxc9ihKsW9KwAx2Rg== whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3: version "1.0.5"