Merge branch 'develop' into bwindels/verification-right-panel
commit
5556cb5749
|
@ -123,7 +123,7 @@
|
||||||
"@peculiar/webcrypto": "^1.0.22",
|
"@peculiar/webcrypto": "^1.0.22",
|
||||||
"babel-eslint": "^10.0.3",
|
"babel-eslint": "^10.0.3",
|
||||||
"babel-jest": "^24.9.0",
|
"babel-jest": "^24.9.0",
|
||||||
"chokidar": "^2.1.2",
|
"chokidar": "^3.3.1",
|
||||||
"concurrently": "^4.0.1",
|
"concurrently": "^4.0.1",
|
||||||
"enzyme": "^3.10.0",
|
"enzyme": "^3.10.0",
|
||||||
"enzyme-adapter-react-16": "^1.15.1",
|
"enzyme-adapter-react-16": "^1.15.1",
|
||||||
|
|
|
@ -57,13 +57,13 @@
|
||||||
@import "./views/dialogs/_ConfirmUserActionDialog.scss";
|
@import "./views/dialogs/_ConfirmUserActionDialog.scss";
|
||||||
@import "./views/dialogs/_CreateGroupDialog.scss";
|
@import "./views/dialogs/_CreateGroupDialog.scss";
|
||||||
@import "./views/dialogs/_CreateRoomDialog.scss";
|
@import "./views/dialogs/_CreateRoomDialog.scss";
|
||||||
@import "./views/dialogs/_DMInviteDialog.scss";
|
|
||||||
@import "./views/dialogs/_DeactivateAccountDialog.scss";
|
@import "./views/dialogs/_DeactivateAccountDialog.scss";
|
||||||
@import "./views/dialogs/_DeviceVerifyDialog.scss";
|
@import "./views/dialogs/_DeviceVerifyDialog.scss";
|
||||||
@import "./views/dialogs/_DevtoolsDialog.scss";
|
@import "./views/dialogs/_DevtoolsDialog.scss";
|
||||||
@import "./views/dialogs/_EncryptedEventDialog.scss";
|
@import "./views/dialogs/_EncryptedEventDialog.scss";
|
||||||
@import "./views/dialogs/_GroupAddressPicker.scss";
|
@import "./views/dialogs/_GroupAddressPicker.scss";
|
||||||
@import "./views/dialogs/_IncomingSasDialog.scss";
|
@import "./views/dialogs/_IncomingSasDialog.scss";
|
||||||
|
@import "./views/dialogs/_InviteDialog.scss";
|
||||||
@import "./views/dialogs/_MessageEditHistoryDialog.scss";
|
@import "./views/dialogs/_MessageEditHistoryDialog.scss";
|
||||||
@import "./views/dialogs/_RoomSettingsDialog.scss";
|
@import "./views/dialogs/_RoomSettingsDialog.scss";
|
||||||
@import "./views/dialogs/_RoomUpgradeDialog.scss";
|
@import "./views/dialogs/_RoomUpgradeDialog.scss";
|
||||||
|
|
|
@ -51,7 +51,7 @@ limitations under the License.
|
||||||
&.mx_Toast_hasIcon {
|
&.mx_Toast_hasIcon {
|
||||||
&::after {
|
&::after {
|
||||||
content: "";
|
content: "";
|
||||||
width: 20px;
|
width: 21px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
grid-column: 1;
|
grid-column: 1;
|
||||||
grid-row: 1;
|
grid-row: 1;
|
||||||
|
@ -64,6 +64,10 @@ limitations under the License.
|
||||||
background-color: $primary-fg-color;
|
background-color: $primary-fg-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.mx_Toast_icon_verification_warning::after {
|
||||||
|
background-image: url("$(res)/img/e2e/warning.svg");
|
||||||
|
}
|
||||||
|
|
||||||
h2, .mx_Toast_body {
|
h2, .mx_Toast_body {
|
||||||
grid-column: 2;
|
grid-column: 2;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,11 +14,11 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_DMInviteDialog_addressBar {
|
.mx_InviteDialog_addressBar {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
||||||
.mx_DMInviteDialog_editor {
|
.mx_InviteDialog_editor {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
width: 100%; // Needed to make the Field inside grow
|
width: 100%; // Needed to make the Field inside grow
|
||||||
background-color: $user-tile-hover-bg-color;
|
background-color: $user-tile-hover-bg-color;
|
||||||
|
@ -28,7 +28,7 @@ limitations under the License.
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
.mx_DMInviteDialog_userTile {
|
.mx_InviteDialog_userTile {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
float: left;
|
float: left;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -61,15 +61,26 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_DMInviteDialog_goButton {
|
.mx_InviteDialog_goButton {
|
||||||
width: 48px;
|
width: 48px;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
height: 25px;
|
height: 25px;
|
||||||
line-height: 25px;
|
line-height: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_InviteDialog_buttonAndSpinner {
|
||||||
|
.mx_Spinner {
|
||||||
|
// Width and height are required to trick the layout engine.
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
margin-left: 5px;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_DMInviteDialog_section {
|
.mx_InviteDialog_section {
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
|
@ -80,7 +91,7 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_DMInviteDialog_roomTile {
|
.mx_InviteDialog_roomTile {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
|
|
||||||
|
@ -93,7 +104,7 @@ limitations under the License.
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_DMInviteDialog_roomTile_avatarStack {
|
.mx_InviteDialog_roomTile_avatarStack {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 36px;
|
width: 36px;
|
||||||
|
@ -106,7 +117,7 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_DMInviteDialog_roomTile_selected {
|
.mx_InviteDialog_roomTile_selected {
|
||||||
width: 36px;
|
width: 36px;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
border-radius: 36px;
|
border-radius: 36px;
|
||||||
|
@ -130,20 +141,20 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_DMInviteDialog_roomTile_name {
|
.mx_InviteDialog_roomTile_name {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: $primary-fg-color;
|
color: $primary-fg-color;
|
||||||
margin-left: 7px;
|
margin-left: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_DMInviteDialog_roomTile_userId {
|
.mx_InviteDialog_roomTile_userId {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: $muted-fg-color;
|
color: $muted-fg-color;
|
||||||
margin-left: 7px;
|
margin-left: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_DMInviteDialog_roomTile_time {
|
.mx_InviteDialog_roomTile_time {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: $muted-fg-color;
|
color: $muted-fg-color;
|
||||||
|
@ -151,16 +162,16 @@ limitations under the License.
|
||||||
line-height: 36px; // Height of the avatar to keep the time vertically aligned
|
line-height: 36px; // Height of the avatar to keep the time vertically aligned
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_DMInviteDialog_roomTile_highlight {
|
.mx_InviteDialog_roomTile_highlight {
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Many of these styles are stolen from mx_UserPill, but adjusted for the invite dialog.
|
// Many of these styles are stolen from mx_UserPill, but adjusted for the invite dialog.
|
||||||
.mx_DMInviteDialog_userTile {
|
.mx_InviteDialog_userTile {
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
|
|
||||||
.mx_DMInviteDialog_userTile_pill {
|
.mx_InviteDialog_userTile_pill {
|
||||||
background-color: $username-variant1-color;
|
background-color: $username-variant1-color;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -170,27 +181,27 @@ limitations under the License.
|
||||||
padding-right: 8px;
|
padding-right: 8px;
|
||||||
color: #ffffff; // this is fine without a var because it's for both themes
|
color: #ffffff; // this is fine without a var because it's for both themes
|
||||||
|
|
||||||
.mx_DMInviteDialog_userTile_avatar {
|
.mx_InviteDialog_userTile_avatar {
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
position: relative;
|
position: relative;
|
||||||
left: -5px;
|
left: -5px;
|
||||||
top: 2px;
|
top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
img.mx_DMInviteDialog_userTile_avatar {
|
img.mx_InviteDialog_userTile_avatar {
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_DMInviteDialog_userTile_name {
|
.mx_InviteDialog_userTile_name {
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_DMInviteDialog_userTile_threepidAvatar {
|
.mx_InviteDialog_userTile_threepidAvatar {
|
||||||
background-color: #ffffff; // this is fine without a var because it's for both themes
|
background-color: #ffffff; // this is fine without a var because it's for both themes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_DMInviteDialog_userTile_remove {
|
.mx_InviteDialog_userTile_remove {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
}
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
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 { MatrixClientPeg } from './MatrixClientPeg';
|
||||||
|
import SettingsStore from './settings/SettingsStore';
|
||||||
|
import * as sdk from './index';
|
||||||
|
import { _t } from './languageHandler';
|
||||||
|
import ToastStore from './stores/ToastStore';
|
||||||
|
|
||||||
|
function toastKey(device) {
|
||||||
|
return 'newsession_' + device.deviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class DeviceListener {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
MatrixClientPeg.get().on('crypto.devicesUpdated', this._onDevicesUpdated);
|
||||||
|
MatrixClientPeg.get().on('deviceVerificationChanged', this._onDeviceVerificationChanged);
|
||||||
|
this.recheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
if (MatrixClientPeg.get()) {
|
||||||
|
MatrixClientPeg.get().removeListener('crypto.devicesUpdated', this._onDevicesUpdated);
|
||||||
|
MatrixClientPeg.get().removeListener('deviceVerificationChanged', this._onDeviceVerificationChanged);
|
||||||
|
}
|
||||||
|
this._dismissed.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
dismissVerification(deviceId) {
|
||||||
|
this._dismissed.add(deviceId);
|
||||||
|
this.recheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
_onDevicesUpdated = (users) => {
|
||||||
|
if (!users.includes(MatrixClientPeg.get().getUserId())) return;
|
||||||
|
if (!SettingsStore.isFeatureEnabled("feature_cross_signing")) return;
|
||||||
|
this.recheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
_onDeviceVerificationChanged = (users) => {
|
||||||
|
if (!users.includes(MatrixClientPeg.get().getUserId())) return;
|
||||||
|
if (!SettingsStore.isFeatureEnabled("feature_cross_signing")) return;
|
||||||
|
this.recheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
async recheck() {
|
||||||
|
const cli = MatrixClientPeg.get();
|
||||||
|
|
||||||
|
if (!cli.isCryptoEnabled()) return false;
|
||||||
|
|
||||||
|
const devices = await cli.getStoredDevicesForUser(cli.getUserId());
|
||||||
|
for (const device of devices) {
|
||||||
|
if (device.deviceId == cli.deviceId) continue;
|
||||||
|
|
||||||
|
const deviceTrust = await cli.checkDeviceTrust(cli.getUserId(), device.deviceId);
|
||||||
|
if (deviceTrust.isVerified() || this._dismissed.has(device.deviceId)) {
|
||||||
|
ToastStore.sharedInstance().dismissToast(toastKey(device));
|
||||||
|
} else {
|
||||||
|
ToastStore.sharedInstance().addOrReplaceToast({
|
||||||
|
key: toastKey(device),
|
||||||
|
title: _t("New Session"),
|
||||||
|
icon: "verification_warning",
|
||||||
|
props: {deviceId: device.deviceId},
|
||||||
|
component: sdk.getComponent("toasts.NewSessionToast"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2017 Vector Creations Ltd
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -16,7 +17,10 @@ limitations under the License.
|
||||||
|
|
||||||
import * as sdk from './index';
|
import * as sdk from './index';
|
||||||
import Modal from './Modal';
|
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 {
|
export default class KeyRequestHandler {
|
||||||
constructor(matrixClient) {
|
constructor(matrixClient) {
|
||||||
this._matrixClient = matrixClient;
|
this._matrixClient = matrixClient;
|
||||||
|
@ -30,6 +34,11 @@ export default class KeyRequestHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyRequest(keyRequest) {
|
handleKeyRequest(keyRequest) {
|
||||||
|
// Ignore own device key requests if cross-signing lab enabled
|
||||||
|
if (SettingsStore.isFeatureEnabled("feature_cross_signing")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const userId = keyRequest.userId;
|
const userId = keyRequest.userId;
|
||||||
const deviceId = keyRequest.deviceId;
|
const deviceId = keyRequest.deviceId;
|
||||||
const requestId = keyRequest.requestId;
|
const requestId = keyRequest.requestId;
|
||||||
|
@ -60,6 +69,11 @@ export default class KeyRequestHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyRequestCancellation(cancellation) {
|
handleKeyRequestCancellation(cancellation) {
|
||||||
|
// Ignore own device key requests if cross-signing lab enabled
|
||||||
|
if (SettingsStore.isFeatureEnabled("feature_cross_signing")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// see if we can find the request in the queue
|
// see if we can find the request in the queue
|
||||||
const userId = cancellation.userId;
|
const userId = cancellation.userId;
|
||||||
const deviceId = cancellation.deviceId;
|
const deviceId = cancellation.deviceId;
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
Copyright 2017 Vector Creations Ltd
|
Copyright 2017 Vector Creations Ltd
|
||||||
Copyright 2018 New Vector Ltd
|
Copyright 2018 New Vector Ltd
|
||||||
|
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -35,8 +36,10 @@ import { sendLoginRequest } from "./Login";
|
||||||
import * as StorageManager from './utils/StorageManager';
|
import * as StorageManager from './utils/StorageManager';
|
||||||
import SettingsStore from "./settings/SettingsStore";
|
import SettingsStore from "./settings/SettingsStore";
|
||||||
import TypingStore from "./stores/TypingStore";
|
import TypingStore from "./stores/TypingStore";
|
||||||
|
import ToastStore from "./stores/ToastStore";
|
||||||
import {IntegrationManagers} from "./integrations/IntegrationManagers";
|
import {IntegrationManagers} from "./integrations/IntegrationManagers";
|
||||||
import {Mjolnir} from "./mjolnir/Mjolnir";
|
import {Mjolnir} from "./mjolnir/Mjolnir";
|
||||||
|
import DeviceListener from "./DeviceListener";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called at startup, to attempt to build a logged-in Matrix session. It tries
|
* Called at startup, to attempt to build a logged-in Matrix session. It tries
|
||||||
|
@ -575,6 +578,7 @@ async function startMatrixClient(startSyncing=true) {
|
||||||
Notifier.start();
|
Notifier.start();
|
||||||
UserActivity.sharedInstance().start();
|
UserActivity.sharedInstance().start();
|
||||||
TypingStore.sharedInstance().reset(); // just in case
|
TypingStore.sharedInstance().reset(); // just in case
|
||||||
|
ToastStore.sharedInstance().reset();
|
||||||
if (!SettingsStore.getValue("lowBandwidth")) {
|
if (!SettingsStore.getValue("lowBandwidth")) {
|
||||||
Presence.start();
|
Presence.start();
|
||||||
}
|
}
|
||||||
|
@ -595,6 +599,9 @@ async function startMatrixClient(startSyncing=true) {
|
||||||
await MatrixClientPeg.assign();
|
await MatrixClientPeg.assign();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This needs to be started after crypto is set up
|
||||||
|
DeviceListener.sharedInstance().start();
|
||||||
|
|
||||||
// dispatch that we finished starting up to wire up any other bits
|
// dispatch that we finished starting up to wire up any other bits
|
||||||
// of the matrix client that cannot be set prior to starting up.
|
// of the matrix client that cannot be set prior to starting up.
|
||||||
dis.dispatch({action: 'client_started'});
|
dis.dispatch({action: 'client_started'});
|
||||||
|
@ -651,6 +658,7 @@ export function stopMatrixClient(unsetClient=true) {
|
||||||
ActiveWidgetStore.stop();
|
ActiveWidgetStore.stop();
|
||||||
IntegrationManagers.sharedInstance().stopWatching();
|
IntegrationManagers.sharedInstance().stopWatching();
|
||||||
Mjolnir.sharedInstance().stop();
|
Mjolnir.sharedInstance().stop();
|
||||||
|
DeviceListener.sharedInstance().stop();
|
||||||
if (DMRoomMap.shared()) DMRoomMap.shared().stop();
|
if (DMRoomMap.shared()) DMRoomMap.shared().stop();
|
||||||
EventIndexPeg.stop();
|
EventIndexPeg.stop();
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2016 OpenMarket Ltd
|
Copyright 2016 OpenMarket Ltd
|
||||||
Copyright 2017, 2018 New Vector Ltd
|
Copyright 2017, 2018 New Vector Ltd
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -26,6 +27,7 @@ import dis from './dispatcher';
|
||||||
import DMRoomMap from './utils/DMRoomMap';
|
import DMRoomMap from './utils/DMRoomMap';
|
||||||
import { _t } from './languageHandler';
|
import { _t } from './languageHandler';
|
||||||
import SettingsStore from "./settings/SettingsStore";
|
import SettingsStore from "./settings/SettingsStore";
|
||||||
|
import {KIND_DM, KIND_INVITE} from "./components/views/dialogs/InviteDialog";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invites multiple addresses to a room
|
* Invites multiple addresses to a room
|
||||||
|
@ -36,21 +38,19 @@ import SettingsStore from "./settings/SettingsStore";
|
||||||
* @param {string[]} addrs Array of strings of addresses to invite. May be matrix IDs or 3pids.
|
* @param {string[]} addrs Array of strings of addresses to invite. May be matrix IDs or 3pids.
|
||||||
* @returns {Promise} Promise
|
* @returns {Promise} Promise
|
||||||
*/
|
*/
|
||||||
function inviteMultipleToRoom(roomId, addrs) {
|
export function inviteMultipleToRoom(roomId, addrs) {
|
||||||
const inviter = new MultiInviter(roomId);
|
const inviter = new MultiInviter(roomId);
|
||||||
return inviter.invite(addrs).then(states => Promise.resolve({states, inviter}));
|
return inviter.invite(addrs).then(states => Promise.resolve({states, inviter}));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showStartChatInviteDialog() {
|
export function showStartChatInviteDialog() {
|
||||||
if (SettingsStore.isFeatureEnabled("feature_ftue_dms")) {
|
if (SettingsStore.isFeatureEnabled("feature_ftue_dms")) {
|
||||||
const DMInviteDialog = sdk.getComponent("dialogs.DMInviteDialog");
|
// This new dialog handles the room creation internally - we don't need to worry about it.
|
||||||
Modal.createTrackedDialog('Start DM', '', DMInviteDialog, {
|
const InviteDialog = sdk.getComponent("dialogs.InviteDialog");
|
||||||
onFinished: (inviteIds) => {
|
Modal.createTrackedDialog(
|
||||||
// TODO: Replace _onStartDmFinished with less hacks
|
'Start DM', '', InviteDialog, {kind: KIND_DM},
|
||||||
if (inviteIds.length > 0) _onStartDmFinished(true, inviteIds.map(i => ({address: i})));
|
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true,
|
||||||
// else ignore and just do nothing
|
);
|
||||||
},
|
|
||||||
}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,6 +74,16 @@ export function showStartChatInviteDialog() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showRoomInviteDialog(roomId) {
|
export function showRoomInviteDialog(roomId) {
|
||||||
|
if (SettingsStore.isFeatureEnabled("feature_ftue_dms")) {
|
||||||
|
// This new dialog handles the room creation internally - we don't need to worry about it.
|
||||||
|
const InviteDialog = sdk.getComponent("dialogs.InviteDialog");
|
||||||
|
Modal.createTrackedDialog(
|
||||||
|
'Invite Users', '', InviteDialog, {kind: KIND_INVITE, roomId},
|
||||||
|
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog");
|
const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog");
|
||||||
|
|
||||||
Modal.createTrackedDialog('Chat Invite', '', AddressPickerDialog, {
|
Modal.createTrackedDialog('Chat Invite', '', AddressPickerDialog, {
|
||||||
|
|
|
@ -63,6 +63,7 @@ import { countRoomsWithNotif } from '../../RoomNotifs';
|
||||||
import { ThemeWatcher } from "../../theme";
|
import { ThemeWatcher } from "../../theme";
|
||||||
import { storeRoomAliasInCache } from '../../RoomAliasCache';
|
import { storeRoomAliasInCache } from '../../RoomAliasCache';
|
||||||
import { defer } from "../../utils/promise";
|
import { defer } from "../../utils/promise";
|
||||||
|
import ToastStore from "../../stores/ToastStore";
|
||||||
|
|
||||||
/** constants for MatrixChat.state.view */
|
/** constants for MatrixChat.state.view */
|
||||||
export const VIEWS = {
|
export const VIEWS = {
|
||||||
|
@ -1381,6 +1382,8 @@ export default createReactClass({
|
||||||
cli.on("Session.logged_out", () => dft.stop());
|
cli.on("Session.logged_out", () => dft.stop());
|
||||||
cli.on("Event.decrypted", (e, err) => dft.eventDecrypted(e, err));
|
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);
|
const krh = new KeyRequestHandler(cli);
|
||||||
cli.on("crypto.roomKeyRequest", (req) => {
|
cli.on("crypto.roomKeyRequest", (req) => {
|
||||||
krh.handleKeyRequest(req);
|
krh.handleKeyRequest(req);
|
||||||
|
@ -1453,15 +1456,12 @@ export default createReactClass({
|
||||||
console.log(`MatrixChat got a .request ${request.channel.transactionId}`, request.event.getRoomId());
|
console.log(`MatrixChat got a .request ${request.channel.transactionId}`, request.event.getRoomId());
|
||||||
if (request.pending) {
|
if (request.pending) {
|
||||||
console.log(`emitting toast for verification request with txnid ${request.channel.transactionId}`, request.event && request.event.getId());
|
console.log(`emitting toast for verification request with txnid ${request.channel.transactionId}`, request.event && request.event.getId());
|
||||||
dis.dispatch({
|
ToastStore.sharedInstance().addOrReplaceToast({
|
||||||
action: "show_toast",
|
key: 'verifreq_' + request.channel.transactionId,
|
||||||
toast: {
|
|
||||||
key: request.channel.transactionId,
|
|
||||||
title: _t("Verification Request"),
|
title: _t("Verification Request"),
|
||||||
icon: "verification",
|
icon: "verification",
|
||||||
props: {request},
|
props: {request},
|
||||||
component: sdk.getComponent("toasts.VerificationRequestToast"),
|
component: sdk.getComponent("toasts.VerificationRequestToast"),
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -173,6 +173,7 @@ export default createReactClass({
|
||||||
MatrixClientPeg.get().on("accountData", this.onAccountData);
|
MatrixClientPeg.get().on("accountData", this.onAccountData);
|
||||||
MatrixClientPeg.get().on("crypto.keyBackupStatus", this.onKeyBackupStatus);
|
MatrixClientPeg.get().on("crypto.keyBackupStatus", this.onKeyBackupStatus);
|
||||||
MatrixClientPeg.get().on("deviceVerificationChanged", this.onDeviceVerificationChanged);
|
MatrixClientPeg.get().on("deviceVerificationChanged", this.onDeviceVerificationChanged);
|
||||||
|
MatrixClientPeg.get().on("userTrustStatusChanged", this.onUserVerificationChanged);
|
||||||
// Start listening for RoomViewStore updates
|
// Start listening for RoomViewStore updates
|
||||||
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
|
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
|
||||||
this._onRoomViewStoreUpdate(true);
|
this._onRoomViewStoreUpdate(true);
|
||||||
|
@ -492,6 +493,7 @@ export default createReactClass({
|
||||||
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
|
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
|
||||||
MatrixClientPeg.get().removeListener("crypto.keyBackupStatus", this.onKeyBackupStatus);
|
MatrixClientPeg.get().removeListener("crypto.keyBackupStatus", this.onKeyBackupStatus);
|
||||||
MatrixClientPeg.get().removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged);
|
MatrixClientPeg.get().removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged);
|
||||||
|
MatrixClientPeg.get().removeListener("userTrustStatusChanged", this.onUserVerificationChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.removeEventListener('beforeunload', this.onPageUnload);
|
window.removeEventListener('beforeunload', this.onPageUnload);
|
||||||
|
@ -762,6 +764,14 @@ export default createReactClass({
|
||||||
this._updateE2EStatus(room);
|
this._updateE2EStatus(room);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onUserVerificationChanged: function(userId, _trustStatus) {
|
||||||
|
const room = this.state.room;
|
||||||
|
if (!room.currentState.getMember(userId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._updateE2EStatus(room);
|
||||||
|
},
|
||||||
|
|
||||||
_updateE2EStatus: async function(room) {
|
_updateE2EStatus: async function(room) {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
if (!cli.isRoomEncrypted(room.roomId)) {
|
if (!cli.isRoomEncrypted(room.roomId)) {
|
||||||
|
@ -782,32 +792,41 @@ export default createReactClass({
|
||||||
e2eStatus: hasUnverifiedDevices ? "warning" : "verified",
|
e2eStatus: hasUnverifiedDevices ? "warning" : "verified",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
debuglog("e2e check is warning/verified only as cross-signing is off");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* At this point, the user has encryption on and cross-signing on */
|
||||||
const e2eMembers = await room.getEncryptionTargetMembers();
|
const e2eMembers = await room.getEncryptionTargetMembers();
|
||||||
for (const member of e2eMembers) {
|
const verified = [];
|
||||||
const { userId } = member;
|
const unverified = [];
|
||||||
const userVerified = cli.checkUserTrust(userId).isCrossSigningVerified();
|
e2eMembers.map(({userId}) => userId)
|
||||||
if (!userVerified) {
|
.filter((userId) => userId !== cli.getUserId())
|
||||||
this.setState({
|
.forEach((userId) => {
|
||||||
e2eStatus: "warning",
|
(cli.checkUserTrust(userId).isCrossSigningVerified() ?
|
||||||
|
verified : unverified).push(userId)
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
}
|
debuglog("e2e verified", verified, "unverified", unverified);
|
||||||
|
|
||||||
|
/* Check all verified user devices. */
|
||||||
|
for (const userId of verified) {
|
||||||
const devices = await cli.getStoredDevicesForUser(userId);
|
const devices = await cli.getStoredDevicesForUser(userId);
|
||||||
const allDevicesVerified = devices.every(device => {
|
const allDevicesVerified = devices.every(({deviceId}) => {
|
||||||
const { deviceId } = device;
|
return cli.checkDeviceTrust(userId, deviceId).isVerified();
|
||||||
return cli.checkDeviceTrust(userId, deviceId).isCrossSigningVerified();
|
|
||||||
});
|
});
|
||||||
if (!allDevicesVerified) {
|
if (!allDevicesVerified) {
|
||||||
this.setState({
|
this.setState({
|
||||||
e2eStatus: "warning",
|
e2eStatus: "warning",
|
||||||
});
|
});
|
||||||
|
debuglog("e2e status set to warning as not all users trust all of their devices." +
|
||||||
|
" Aborted on user", userId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
e2eStatus: "verified",
|
e2eStatus: unverified.length === 0 ? "verified" : "normal",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -15,38 +15,26 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import dis from "../../dispatcher";
|
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
|
import ToastStore from "../../stores/ToastStore";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
export default class ToastContainer extends React.Component {
|
export default class ToastContainer extends React.Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.state = {toasts: []};
|
this.state = {toasts: ToastStore.sharedInstance().getToasts()};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
console.log("ToastContainer mounted");
|
ToastStore.sharedInstance().on('update', this._onToastStoreUpdate);
|
||||||
this._dispatcherRef = dis.register(this.onAction);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
dis.unregister(this._dispatcherRef);
|
ToastStore.sharedInstance().removeListener('update', this._onToastStoreUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
onAction = (payload) => {
|
_onToastStoreUpdate = () => {
|
||||||
if (payload.action === "show_toast") {
|
this.setState({toasts: ToastStore.sharedInstance().getToasts()});
|
||||||
this._addToast(payload.toast);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
_addToast(toast) {
|
|
||||||
this.setState({toasts: this.state.toasts.concat(toast)});
|
|
||||||
}
|
|
||||||
|
|
||||||
dismissTopToast = () => {
|
|
||||||
const [, ...remaining] = this.state.toasts;
|
|
||||||
this.setState({toasts: remaining});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -63,8 +51,8 @@ export default class ToastContainer extends React.Component {
|
||||||
const countIndicator = isStacked ? _t(" (1/%(totalCount)s)", {totalCount}) : null;
|
const countIndicator = isStacked ? _t(" (1/%(totalCount)s)", {totalCount}) : null;
|
||||||
|
|
||||||
const toastProps = Object.assign({}, props, {
|
const toastProps = Object.assign({}, props, {
|
||||||
dismiss: this.dismissTopToast,
|
|
||||||
key,
|
key,
|
||||||
|
toastKey: key,
|
||||||
});
|
});
|
||||||
toast = (<div className={toastClasses}>
|
toast = (<div className={toastClasses}>
|
||||||
<h2>{title}{countIndicator}</h2>
|
<h2>{title}{countIndicator}</h2>
|
||||||
|
|
|
@ -63,7 +63,7 @@ export default class MemberStatusMessageAvatar extends React.Component {
|
||||||
user.on("User._unstable_statusMessage", this._onStatusMessageCommitted);
|
user.on("User._unstable_statusMessage", this._onStatusMessageCommitted);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUmount() {
|
componentWillUnmount() {
|
||||||
const { user } = this.props.member;
|
const { user } = this.props.member;
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -19,7 +19,7 @@ import PropTypes from 'prop-types';
|
||||||
import {_t} from "../../../languageHandler";
|
import {_t} from "../../../languageHandler";
|
||||||
import * as sdk from "../../../index";
|
import * as sdk from "../../../index";
|
||||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||||
import {makeUserPermalink} from "../../../utils/permalinks/Permalinks";
|
import {makeRoomPermalink, makeUserPermalink} from "../../../utils/permalinks/Permalinks";
|
||||||
import DMRoomMap from "../../../utils/DMRoomMap";
|
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||||
import {RoomMember} from "matrix-js-sdk/src/matrix";
|
import {RoomMember} from "matrix-js-sdk/src/matrix";
|
||||||
import SdkConfig from "../../../SdkConfig";
|
import SdkConfig from "../../../SdkConfig";
|
||||||
|
@ -31,8 +31,11 @@ import dis from "../../../dispatcher";
|
||||||
import IdentityAuthClient from "../../../IdentityAuthClient";
|
import IdentityAuthClient from "../../../IdentityAuthClient";
|
||||||
import Modal from "../../../Modal";
|
import Modal from "../../../Modal";
|
||||||
import {humanizeTime} from "../../../utils/humanize";
|
import {humanizeTime} from "../../../utils/humanize";
|
||||||
|
import createRoom from "../../../createRoom";
|
||||||
|
import {inviteMultipleToRoom} from "../../../RoomInvite";
|
||||||
|
|
||||||
// TODO: [TravisR] Make this generic for all kinds of invites
|
export const KIND_DM = "dm";
|
||||||
|
export const KIND_INVITE = "invite";
|
||||||
|
|
||||||
const INITIAL_ROOMS_SHOWN = 3; // Number of rooms to show at first
|
const INITIAL_ROOMS_SHOWN = 3; // Number of rooms to show at first
|
||||||
const INCREMENT_ROOMS_SHOWN = 5; // Number of rooms to add when 'show more' is clicked
|
const INCREMENT_ROOMS_SHOWN = 5; // Number of rooms to add when 'show more' is clicked
|
||||||
|
@ -138,11 +141,11 @@ class DMUserTile extends React.PureComponent {
|
||||||
const avatarSize = 20;
|
const avatarSize = 20;
|
||||||
const avatar = this.props.member.isEmail
|
const avatar = this.props.member.isEmail
|
||||||
? <img
|
? <img
|
||||||
className='mx_DMInviteDialog_userTile_avatar mx_DMInviteDialog_userTile_threepidAvatar'
|
className='mx_InviteDialog_userTile_avatar mx_InviteDialog_userTile_threepidAvatar'
|
||||||
src={require("../../../../res/img/icon-email-pill-avatar.svg")}
|
src={require("../../../../res/img/icon-email-pill-avatar.svg")}
|
||||||
width={avatarSize} height={avatarSize} />
|
width={avatarSize} height={avatarSize} />
|
||||||
: <BaseAvatar
|
: <BaseAvatar
|
||||||
className='mx_DMInviteDialog_userTile_avatar'
|
className='mx_InviteDialog_userTile_avatar'
|
||||||
url={getHttpUriForMxc(
|
url={getHttpUriForMxc(
|
||||||
MatrixClientPeg.get().getHomeserverUrl(), this.props.member.getMxcAvatarUrl(),
|
MatrixClientPeg.get().getHomeserverUrl(), this.props.member.getMxcAvatarUrl(),
|
||||||
avatarSize, avatarSize, "crop")}
|
avatarSize, avatarSize, "crop")}
|
||||||
|
@ -152,13 +155,13 @@ class DMUserTile extends React.PureComponent {
|
||||||
height={avatarSize} />;
|
height={avatarSize} />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className='mx_DMInviteDialog_userTile'>
|
<span className='mx_InviteDialog_userTile'>
|
||||||
<span className='mx_DMInviteDialog_userTile_pill'>
|
<span className='mx_InviteDialog_userTile_pill'>
|
||||||
{avatar}
|
{avatar}
|
||||||
<span className='mx_DMInviteDialog_userTile_name'>{this.props.member.name}</span>
|
<span className='mx_InviteDialog_userTile_name'>{this.props.member.name}</span>
|
||||||
</span>
|
</span>
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
className='mx_DMInviteDialog_userTile_remove'
|
className='mx_InviteDialog_userTile_remove'
|
||||||
onClick={this._onRemove}
|
onClick={this._onRemove}
|
||||||
>
|
>
|
||||||
<img src={require("../../../../res/img/icon-pill-remove.svg")} alt={_t('Remove')} width={8} height={8} />
|
<img src={require("../../../../res/img/icon-pill-remove.svg")} alt={_t('Remove')} width={8} height={8} />
|
||||||
|
@ -209,7 +212,7 @@ class DMRoomTile extends React.PureComponent {
|
||||||
|
|
||||||
// Highlight the word the user entered
|
// Highlight the word the user entered
|
||||||
const substr = str.substring(i, filterStr.length + i);
|
const substr = str.substring(i, filterStr.length + i);
|
||||||
result.push(<span className='mx_DMInviteDialog_roomTile_highlight' key={i + 'bold'}>{substr}</span>);
|
result.push(<span className='mx_InviteDialog_roomTile_highlight' key={i + 'bold'}>{substr}</span>);
|
||||||
i += substr.length;
|
i += substr.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,7 +230,7 @@ class DMRoomTile extends React.PureComponent {
|
||||||
let timestamp = null;
|
let timestamp = null;
|
||||||
if (this.props.lastActiveTs) {
|
if (this.props.lastActiveTs) {
|
||||||
const humanTs = humanizeTime(this.props.lastActiveTs);
|
const humanTs = humanizeTime(this.props.lastActiveTs);
|
||||||
timestamp = <span className='mx_DMInviteDialog_roomTile_time'>{humanTs}</span>;
|
timestamp = <span className='mx_InviteDialog_roomTile_time'>{humanTs}</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const avatarSize = 36;
|
const avatarSize = 36;
|
||||||
|
@ -247,61 +250,95 @@ class DMRoomTile extends React.PureComponent {
|
||||||
let checkmark = null;
|
let checkmark = null;
|
||||||
if (this.props.isSelected) {
|
if (this.props.isSelected) {
|
||||||
// To reduce flickering we put the 'selected' room tile above the real avatar
|
// To reduce flickering we put the 'selected' room tile above the real avatar
|
||||||
checkmark = <div className='mx_DMInviteDialog_roomTile_selected' />;
|
checkmark = <div className='mx_InviteDialog_roomTile_selected' />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// To reduce flickering we put the checkmark on top of the actual avatar (prevents
|
// To reduce flickering we put the checkmark on top of the actual avatar (prevents
|
||||||
// the browser from reloading the image source when the avatar remounts).
|
// the browser from reloading the image source when the avatar remounts).
|
||||||
const stackedAvatar = (
|
const stackedAvatar = (
|
||||||
<span className='mx_DMInviteDialog_roomTile_avatarStack'>
|
<span className='mx_InviteDialog_roomTile_avatarStack'>
|
||||||
{avatar}
|
{avatar}
|
||||||
{checkmark}
|
{checkmark}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mx_DMInviteDialog_roomTile' onClick={this._onClick}>
|
<div className='mx_InviteDialog_roomTile' onClick={this._onClick}>
|
||||||
{stackedAvatar}
|
{stackedAvatar}
|
||||||
<span className='mx_DMInviteDialog_roomTile_name'>{this._highlightName(this.props.member.name)}</span>
|
<span className='mx_InviteDialog_roomTile_name'>{this._highlightName(this.props.member.name)}</span>
|
||||||
<span className='mx_DMInviteDialog_roomTile_userId'>{this._highlightName(this.props.member.userId)}</span>
|
<span className='mx_InviteDialog_roomTile_userId'>{this._highlightName(this.props.member.userId)}</span>
|
||||||
{timestamp}
|
{timestamp}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class DMInviteDialog extends React.PureComponent {
|
export default class InviteDialog extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
// Takes an array of user IDs/emails to invite.
|
// Takes an array of user IDs/emails to invite.
|
||||||
onFinished: PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
|
|
||||||
|
// The kind of invite being performed. Assumed to be KIND_DM if
|
||||||
|
// not provided.
|
||||||
|
kind: PropTypes.string,
|
||||||
|
|
||||||
|
// The room ID this dialog is for. Only required for KIND_INVITE.
|
||||||
|
roomId: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
kind: KIND_DM,
|
||||||
};
|
};
|
||||||
|
|
||||||
_debounceTimer: number = null;
|
_debounceTimer: number = null;
|
||||||
_editorRef: any = null;
|
_editorRef: any = null;
|
||||||
|
|
||||||
constructor() {
|
constructor(props) {
|
||||||
super();
|
super(props);
|
||||||
|
|
||||||
|
if (props.kind === KIND_INVITE && !props.roomId) {
|
||||||
|
throw new Error("When using KIND_INVITE a roomId is required for an InviteDialog");
|
||||||
|
}
|
||||||
|
|
||||||
|
let alreadyInvited = [];
|
||||||
|
if (props.roomId) {
|
||||||
|
const room = MatrixClientPeg.get().getRoom(props.roomId);
|
||||||
|
if (!room) throw new Error("Room ID given to InviteDialog does not look like a room");
|
||||||
|
alreadyInvited = [
|
||||||
|
...room.getMembersWithMembership('invite'),
|
||||||
|
...room.getMembersWithMembership('join'),
|
||||||
|
...room.getMembersWithMembership('ban'), // so we don't try to invite them
|
||||||
|
].map(m => m.userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
targets: [], // array of Member objects (see interface above)
|
targets: [], // array of Member objects (see interface above)
|
||||||
filterText: "",
|
filterText: "",
|
||||||
recents: this._buildRecents(),
|
recents: this._buildRecents(alreadyInvited),
|
||||||
numRecentsShown: INITIAL_ROOMS_SHOWN,
|
numRecentsShown: INITIAL_ROOMS_SHOWN,
|
||||||
suggestions: this._buildSuggestions(),
|
suggestions: this._buildSuggestions(alreadyInvited),
|
||||||
numSuggestionsShown: INITIAL_ROOMS_SHOWN,
|
numSuggestionsShown: INITIAL_ROOMS_SHOWN,
|
||||||
serverResultsMixin: [], // { user: DirectoryMember, userId: string }[], like recents and suggestions
|
serverResultsMixin: [], // { user: DirectoryMember, userId: string }[], like recents and suggestions
|
||||||
threepidResultsMixin: [], // { user: ThreepidMember, userId: string}[], like recents and suggestions
|
threepidResultsMixin: [], // { user: ThreepidMember, userId: string}[], like recents and suggestions
|
||||||
canUseIdentityServer: !!MatrixClientPeg.get().getIdentityServerUrl(),
|
canUseIdentityServer: !!MatrixClientPeg.get().getIdentityServerUrl(),
|
||||||
tryingIdentityServer: false,
|
tryingIdentityServer: false,
|
||||||
|
|
||||||
|
// These two flags are used for the 'Go' button to communicate what is going on.
|
||||||
|
busy: false,
|
||||||
|
errorText: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
this._editorRef = createRef();
|
this._editorRef = createRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
_buildRecents(): {userId: string, user: RoomMember, lastActive: number} {
|
_buildRecents(excludedTargetIds: string[]): {userId: string, user: RoomMember, lastActive: number} {
|
||||||
const rooms = DMRoomMap.shared().getUniqueRoomsWithIndividuals();
|
const rooms = DMRoomMap.shared().getUniqueRoomsWithIndividuals();
|
||||||
const recents = [];
|
const recents = [];
|
||||||
for (const userId in rooms) {
|
for (const userId in rooms) {
|
||||||
|
// Filter out user IDs that are already in the room / should be excluded
|
||||||
|
if (excludedTargetIds.includes(userId)) continue;
|
||||||
|
|
||||||
const room = rooms[userId];
|
const room = rooms[userId];
|
||||||
const member = room.getMember(userId);
|
const member = room.getMember(userId);
|
||||||
if (!member) continue; // just skip people who don't have memberships for some reason
|
if (!member) continue; // just skip people who don't have memberships for some reason
|
||||||
|
@ -320,7 +357,7 @@ export default class DMInviteDialog extends React.PureComponent {
|
||||||
return recents;
|
return recents;
|
||||||
}
|
}
|
||||||
|
|
||||||
_buildSuggestions(): {userId: string, user: RoomMember} {
|
_buildSuggestions(excludedTargetIds: string[]): {userId: string, user: RoomMember} {
|
||||||
const maxConsideredMembers = 200;
|
const maxConsideredMembers = 200;
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
const excludedUserIds = [client.getUserId(), SdkConfig.get()['welcomeUserId']];
|
const excludedUserIds = [client.getUserId(), SdkConfig.get()['welcomeUserId']];
|
||||||
|
@ -337,6 +374,11 @@ export default class DMInviteDialog extends React.PureComponent {
|
||||||
|
|
||||||
const joinedMembers = room.getJoinedMembers().filter(u => !excludedUserIds.includes(u.userId));
|
const joinedMembers = room.getJoinedMembers().filter(u => !excludedUserIds.includes(u.userId));
|
||||||
for (const member of joinedMembers) {
|
for (const member of joinedMembers) {
|
||||||
|
// Filter out user IDs that are already in the room / should be excluded
|
||||||
|
if (excludedTargetIds.includes(member.userId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!members[member.userId]) {
|
if (!members[member.userId]) {
|
||||||
members[member.userId] = {
|
members[member.userId] = {
|
||||||
member: member,
|
member: member,
|
||||||
|
@ -384,12 +426,101 @@ export default class DMInviteDialog extends React.PureComponent {
|
||||||
return members.map(m => ({userId: m.member.userId, user: m.member}));
|
return members.map(m => ({userId: m.member.userId, user: m.member}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_shouldAbortAfterInviteError(result): boolean {
|
||||||
|
const failedUsers = Object.keys(result.states).filter(a => result.states[a] === 'error');
|
||||||
|
if (failedUsers.length > 0) {
|
||||||
|
console.log("Failed to invite users: ", result);
|
||||||
|
this.setState({
|
||||||
|
busy: false,
|
||||||
|
errorText: _t("Failed to invite the following users to chat: %(csvUsers)s", {
|
||||||
|
csvUsers: failedUsers.join(", "),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
return true; // abort
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
_startDm = () => {
|
_startDm = () => {
|
||||||
this.props.onFinished(this.state.targets.map(t => t.userId));
|
this.setState({busy: true});
|
||||||
|
const targetIds = this.state.targets.map(t => t.userId);
|
||||||
|
|
||||||
|
// Check if there is already a DM with these people and reuse it if possible.
|
||||||
|
const existingRoom = DMRoomMap.shared().getDMRoomForIdentifiers(targetIds);
|
||||||
|
if (existingRoom) {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'view_room',
|
||||||
|
room_id: existingRoom.roomId,
|
||||||
|
should_peek: false,
|
||||||
|
joining: false,
|
||||||
|
});
|
||||||
|
this.props.onFinished();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's a traditional DM and create the room if required.
|
||||||
|
// TODO: [Canonical DMs] Remove this check and instead just create the multi-person DM
|
||||||
|
let createRoomPromise = Promise.resolve();
|
||||||
|
if (targetIds.length === 1) {
|
||||||
|
createRoomPromise = createRoom({dmUserId: targetIds[0]});
|
||||||
|
} else {
|
||||||
|
// Create a boring room and try to invite the targets manually.
|
||||||
|
createRoomPromise = createRoom().then(roomId => {
|
||||||
|
return inviteMultipleToRoom(roomId, targetIds);
|
||||||
|
}).then(result => {
|
||||||
|
if (this._shouldAbortAfterInviteError(result)) {
|
||||||
|
return true; // abort
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// the createRoom call will show the room for us, so we don't need to worry about that.
|
||||||
|
createRoomPromise.then(abort => {
|
||||||
|
if (abort === true) return; // only abort on true booleans, not roomIds or something
|
||||||
|
this.props.onFinished();
|
||||||
|
}).catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
this.setState({
|
||||||
|
busy: false,
|
||||||
|
errorText: _t("We couldn't create your DM. Please check the users you want to invite and try again."),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
_inviteUsers = () => {
|
||||||
|
this.setState({busy: true});
|
||||||
|
const targetIds = this.state.targets.map(t => t.userId);
|
||||||
|
|
||||||
|
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
|
||||||
|
if (!room) {
|
||||||
|
console.error("Failed to find the room to invite users to");
|
||||||
|
this.setState({
|
||||||
|
busy: false,
|
||||||
|
errorText: _t("Something went wrong trying to invite the users."),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
inviteMultipleToRoom(this.props.roomId, targetIds).then(result => {
|
||||||
|
if (!this._shouldAbortAfterInviteError(result)) { // handles setting error message too
|
||||||
|
this.props.onFinished();
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
this.setState({
|
||||||
|
busy: false,
|
||||||
|
errorText: _t(
|
||||||
|
"We couldn't invite those users. Please check the users you want to invite and try again.",
|
||||||
|
),
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
_cancel = () => {
|
_cancel = () => {
|
||||||
this.props.onFinished([]);
|
// We do not want the user to close the dialog while an action is in progress
|
||||||
|
if (this.state.busy) return;
|
||||||
|
|
||||||
|
this.props.onFinished();
|
||||||
};
|
};
|
||||||
|
|
||||||
_updateFilter = (e) => {
|
_updateFilter = (e) => {
|
||||||
|
@ -599,7 +730,11 @@ export default class DMInviteDialog extends React.PureComponent {
|
||||||
let showNum = kind === 'recents' ? this.state.numRecentsShown : this.state.numSuggestionsShown;
|
let showNum = kind === 'recents' ? this.state.numRecentsShown : this.state.numSuggestionsShown;
|
||||||
const showMoreFn = kind === 'recents' ? this._showMoreRecents.bind(this) : this._showMoreSuggestions.bind(this);
|
const showMoreFn = kind === 'recents' ? this._showMoreRecents.bind(this) : this._showMoreSuggestions.bind(this);
|
||||||
const lastActive = (m) => kind === 'recents' ? m.lastActive : null;
|
const lastActive = (m) => kind === 'recents' ? m.lastActive : null;
|
||||||
const sectionName = kind === 'recents' ? _t("Recent Conversations") : _t("Suggestions");
|
let sectionName = kind === 'recents' ? _t("Recent Conversations") : _t("Suggestions");
|
||||||
|
|
||||||
|
if (this.props.kind === KIND_INVITE) {
|
||||||
|
sectionName = kind === 'recents' ? _t("Recently Direct Messaged") : _t("Suggestions");
|
||||||
|
}
|
||||||
|
|
||||||
// Mix in the server results if we have any, but only if we're searching. We track the additional
|
// Mix in the server results if we have any, but only if we're searching. We track the additional
|
||||||
// members separately because we want to filter sourceMembers but trust the mixin arrays to have
|
// members separately because we want to filter sourceMembers but trust the mixin arrays to have
|
||||||
|
@ -631,7 +766,7 @@ export default class DMInviteDialog extends React.PureComponent {
|
||||||
|
|
||||||
if (sourceMembers.length === 0 && additionalMembers.length === 0) {
|
if (sourceMembers.length === 0 && additionalMembers.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className='mx_DMInviteDialog_section'>
|
<div className='mx_InviteDialog_section'>
|
||||||
<h3>{sectionName}</h3>
|
<h3>{sectionName}</h3>
|
||||||
<p>{_t("No results")}</p>
|
<p>{_t("No results")}</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -672,7 +807,7 @@ export default class DMInviteDialog extends React.PureComponent {
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
return (
|
return (
|
||||||
<div className='mx_DMInviteDialog_section'>
|
<div className='mx_InviteDialog_section'>
|
||||||
<h3>{sectionName}</h3>
|
<h3>{sectionName}</h3>
|
||||||
{tiles}
|
{tiles}
|
||||||
{showMore}
|
{showMore}
|
||||||
|
@ -695,7 +830,7 @@ export default class DMInviteDialog extends React.PureComponent {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<div className='mx_DMInviteDialog_editor' onClick={this._onClickInputArea}>
|
<div className='mx_InviteDialog_editor' onClick={this._onClickInputArea}>
|
||||||
{targets}
|
{targets}
|
||||||
{input}
|
{input}
|
||||||
</div>
|
</div>
|
||||||
|
@ -739,35 +874,67 @@ export default class DMInviteDialog extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
||||||
|
const Spinner = sdk.getComponent("elements.Spinner");
|
||||||
|
|
||||||
|
let spinner = null;
|
||||||
|
if (this.state.busy) {
|
||||||
|
spinner = <Spinner w={20} h={20} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let title;
|
||||||
|
let helpText;
|
||||||
|
let buttonText;
|
||||||
|
let goButtonFn;
|
||||||
|
|
||||||
|
if (this.props.kind === KIND_DM) {
|
||||||
const userId = MatrixClientPeg.get().getUserId();
|
const userId = MatrixClientPeg.get().getUserId();
|
||||||
return (
|
|
||||||
<BaseDialog
|
title = _t("Direct Messages");
|
||||||
className='mx_DMInviteDialog'
|
helpText = _t(
|
||||||
hasCancel={true}
|
|
||||||
onFinished={this._cancel}
|
|
||||||
title={_t("Direct Messages")}
|
|
||||||
>
|
|
||||||
<div className='mx_DMInviteDialog_content'>
|
|
||||||
<p>
|
|
||||||
{_t(
|
|
||||||
"If you can't find someone, ask them for their username, or share your " +
|
"If you can't find someone, ask them for their username, or share your " +
|
||||||
"username (%(userId)s) or <a>profile link</a>.",
|
"username (%(userId)s) or <a>profile link</a>.",
|
||||||
{userId},
|
{userId},
|
||||||
{a: (sub) => <a href={makeUserPermalink(userId)} rel="noopener" target="_blank">{sub}</a>},
|
{a: (sub) => <a href={makeUserPermalink(userId)} rel="noopener" target="_blank">{sub}</a>},
|
||||||
)}
|
);
|
||||||
</p>
|
buttonText = _t("Go");
|
||||||
<div className='mx_DMInviteDialog_addressBar'>
|
goButtonFn = this._startDm;
|
||||||
|
} else { // KIND_INVITE
|
||||||
|
title = _t("Invite to this room");
|
||||||
|
helpText = _t(
|
||||||
|
"If you can't find someone, ask them for their username (e.g. @user:server.com) or " +
|
||||||
|
"<a>share this room</a>.", {},
|
||||||
|
{a: (sub) => <a href={makeRoomPermalink(this.props.roomId)} rel="noopener" target="_blank">{sub}</a>},
|
||||||
|
);
|
||||||
|
buttonText = _t("Invite");
|
||||||
|
goButtonFn = this._inviteUsers;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseDialog
|
||||||
|
className='mx_InviteDialog'
|
||||||
|
hasCancel={true}
|
||||||
|
onFinished={this._cancel}
|
||||||
|
title={title}
|
||||||
|
>
|
||||||
|
<div className='mx_InviteDialog_content'>
|
||||||
|
<p>{helpText}</p>
|
||||||
|
<div className='mx_InviteDialog_addressBar'>
|
||||||
{this._renderEditor()}
|
{this._renderEditor()}
|
||||||
{this._renderIdentityServerWarning()}
|
<div className='mx_InviteDialog_buttonAndSpinner'>
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
kind="primary"
|
kind="primary"
|
||||||
onClick={this._startDm}
|
onClick={goButtonFn}
|
||||||
className='mx_DMInviteDialog_goButton'
|
className='mx_InviteDialog_goButton'
|
||||||
|
disabled={this.state.busy}
|
||||||
>
|
>
|
||||||
{_t("Go")}
|
{buttonText}
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
|
{spinner}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
{this._renderIdentityServerWarning()}
|
||||||
|
<div className='error'>{this.state.errorText}</div>
|
||||||
{this._renderSection('recents')}
|
{this._renderSection('recents')}
|
||||||
{this._renderSection('suggestions')}
|
{this._renderSection('suggestions')}
|
||||||
</div>
|
</div>
|
|
@ -22,6 +22,9 @@ import * as sdk from '../../../index';
|
||||||
|
|
||||||
import { _t, _td } from '../../../languageHandler';
|
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
|
* Dialog which asks the user whether they want to share their keys with
|
||||||
* an unverified device.
|
* an unverified device.
|
||||||
|
|
|
@ -210,8 +210,8 @@ export default class BasicMessageEditor extends React.Component {
|
||||||
const selectedParts = range.parts.map(p => p.serialize());
|
const selectedParts = range.parts.map(p => p.serialize());
|
||||||
event.clipboardData.setData("application/x-riot-composer", JSON.stringify(selectedParts));
|
event.clipboardData.setData("application/x-riot-composer", JSON.stringify(selectedParts));
|
||||||
if (type === "cut") {
|
if (type === "cut") {
|
||||||
selection.deleteFromDocument();
|
// Remove the text, updating the model as appropriate
|
||||||
range.replace([]);
|
replaceRangeAndMoveCaret(range, []);
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,7 +137,7 @@ export default class BridgeSettingsTab extends React.Component {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
const roomState = (client.getRoom(roomId)).currentState;
|
const roomState = (client.getRoom(roomId)).currentState;
|
||||||
|
|
||||||
const bridgeEvents = Array.concat(...BRIDGE_EVENT_TYPES.map((typeName) =>
|
const bridgeEvents = [].concat(...BRIDGE_EVENT_TYPES.map((typeName) =>
|
||||||
Object.values(roomState.events[typeName] || {}),
|
Object.values(roomState.events[typeName] || {}),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
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 * as sdk from "../../../index";
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
import Modal from "../../../Modal";
|
||||||
|
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||||
|
import DeviceListener from '../../../DeviceListener';
|
||||||
|
|
||||||
|
export default class VerifySessionToast extends React.PureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
toastKey: PropTypes.string.isRequired,
|
||||||
|
deviceId: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
_onLaterClick = () => {
|
||||||
|
DeviceListener.sharedInstance().dismissVerification(this.props.deviceId);
|
||||||
|
};
|
||||||
|
|
||||||
|
_onVerifyClick = async () => {
|
||||||
|
const cli = MatrixClientPeg.get();
|
||||||
|
const DeviceVerifyDialog = sdk.getComponent('views.dialogs.DeviceVerifyDialog');
|
||||||
|
|
||||||
|
const device = await cli.getStoredDevice(cli.getUserId(), this.props.deviceId);
|
||||||
|
|
||||||
|
Modal.createTrackedDialog('New Session Verify', 'Starting dialog', DeviceVerifyDialog, {
|
||||||
|
userId: MatrixClientPeg.get().getUserId(),
|
||||||
|
device,
|
||||||
|
}, null, /* priority = */ false, /* static = */ true);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const FormButton = sdk.getComponent("elements.FormButton");
|
||||||
|
return (<div>
|
||||||
|
<div className="mx_Toast_description">{_t("Other users may not trust it")}</div>
|
||||||
|
<div className="mx_Toast_buttons" aria-live="off">
|
||||||
|
<FormButton label={_t("Later")} kind="danger" onClick={this._onLaterClick} />
|
||||||
|
<FormButton label={_t("Verify")} onClick={this._onVerifyClick} />
|
||||||
|
</div>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||||
import {RIGHT_PANEL_PHASES} from "../../../stores/RightPanelStorePhases";
|
import {RIGHT_PANEL_PHASES} from "../../../stores/RightPanelStorePhases";
|
||||||
import {userLabelForEventRoom} from "../../../utils/KeyVerificationStateObserver";
|
import {userLabelForEventRoom} from "../../../utils/KeyVerificationStateObserver";
|
||||||
import dis from "../../../dispatcher";
|
import dis from "../../../dispatcher";
|
||||||
|
import ToastStore from "../../../stores/ToastStore";
|
||||||
|
|
||||||
export default class VerificationRequestToast extends React.PureComponent {
|
export default class VerificationRequestToast extends React.PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -48,12 +49,12 @@ export default class VerificationRequestToast extends React.PureComponent {
|
||||||
_checkRequestIsPending = () => {
|
_checkRequestIsPending = () => {
|
||||||
const {request} = this.props;
|
const {request} = this.props;
|
||||||
if (request.ready || request.started || request.done || request.cancelled || request.observeOnly) {
|
if (request.ready || request.started || request.done || request.cancelled || request.observeOnly) {
|
||||||
this.props.dismiss();
|
ToastStore.sharedInstance().dismissToast(this.props.toastKey);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
cancel = () => {
|
cancel = () => {
|
||||||
this.props.dismiss();
|
ToastStore.sharedInstance().dismissToast(this.props.toastKey);
|
||||||
try {
|
try {
|
||||||
this.props.request.cancel();
|
this.props.request.cancel();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -62,7 +63,7 @@ export default class VerificationRequestToast extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
accept = async () => {
|
accept = async () => {
|
||||||
this.props.dismiss();
|
ToastStore.sharedInstance().dismissToast(this.props.toastKey);
|
||||||
const {request} = this.props;
|
const {request} = this.props;
|
||||||
const {event} = request;
|
const {event} = request;
|
||||||
// no room id for to_device requests
|
// no room id for to_device requests
|
||||||
|
@ -111,6 +112,6 @@ export default class VerificationRequestToast extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
VerificationRequestToast.propTypes = {
|
VerificationRequestToast.propTypes = {
|
||||||
dismiss: PropTypes.func.isRequired,
|
|
||||||
request: PropTypes.object.isRequired,
|
request: PropTypes.object.isRequired,
|
||||||
|
toastKey: PropTypes.string.isRequired,
|
||||||
};
|
};
|
||||||
|
|
|
@ -117,7 +117,7 @@ export default class DocumentPosition {
|
||||||
}
|
}
|
||||||
offset += this.offset;
|
offset += this.offset;
|
||||||
const lastPart = model.parts[this.index];
|
const lastPart = model.parts[this.index];
|
||||||
const atEnd = offset >= lastPart.text.length;
|
const atEnd = !lastPart || offset >= lastPart.text.length; // if no last part, we're at the end
|
||||||
return new DocumentOffset(offset, atEnd);
|
return new DocumentOffset(offset, atEnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -85,6 +85,7 @@
|
||||||
"%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(time)s",
|
"%(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": "%(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",
|
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s",
|
||||||
|
"New Session": "New Session",
|
||||||
"Who would you like to add to this community?": "Who would you like to add to this community?",
|
"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",
|
"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",
|
"Invite new community members": "Invite new community members",
|
||||||
|
@ -372,7 +373,7 @@
|
||||||
"Render simple counters in room header": "Render simple counters in room header",
|
"Render simple counters in room header": "Render simple counters in room header",
|
||||||
"Multiple integration managers": "Multiple integration managers",
|
"Multiple integration managers": "Multiple integration managers",
|
||||||
"Try out new ways to ignore people (experimental)": "Try out new ways to ignore people (experimental)",
|
"Try out new ways to ignore people (experimental)": "Try out new ways to ignore people (experimental)",
|
||||||
"New DM invite dialog (under development)": "New DM invite dialog (under development)",
|
"New invite dialog": "New invite dialog",
|
||||||
"Show a presence dot next to DMs in the room list": "Show a presence dot next to DMs in the room list",
|
"Show a presence dot next to DMs in the room list": "Show a presence dot next to DMs in the room list",
|
||||||
"Enable cross-signing to verify per-user instead of per-device (in development)": "Enable cross-signing to verify per-user instead of per-device (in development)",
|
"Enable cross-signing to verify per-user instead of per-device (in development)": "Enable cross-signing to verify per-user instead of per-device (in development)",
|
||||||
"Enable local event indexing and E2EE search (requires restart)": "Enable local event indexing and E2EE search (requires restart)",
|
"Enable local event indexing and E2EE search (requires restart)": "Enable local event indexing and E2EE search (requires restart)",
|
||||||
|
@ -513,6 +514,9 @@
|
||||||
"Headphones": "Headphones",
|
"Headphones": "Headphones",
|
||||||
"Folder": "Folder",
|
"Folder": "Folder",
|
||||||
"Pin": "Pin",
|
"Pin": "Pin",
|
||||||
|
"Other users may not trust it": "Other users may not trust it",
|
||||||
|
"Later": "Later",
|
||||||
|
"Verify": "Verify",
|
||||||
"Decline (%(counter)s)": "Decline (%(counter)s)",
|
"Decline (%(counter)s)": "Decline (%(counter)s)",
|
||||||
"Accept <policyLink /> to continue:": "Accept <policyLink /> to continue:",
|
"Accept <policyLink /> to continue:": "Accept <policyLink /> to continue:",
|
||||||
"Upload": "Upload",
|
"Upload": "Upload",
|
||||||
|
@ -1134,7 +1138,6 @@
|
||||||
"This client does not support end-to-end encryption.": "This client does not support end-to-end encryption.",
|
"This client does not support end-to-end encryption.": "This client does not support end-to-end encryption.",
|
||||||
"Messages in this room are not end-to-end encrypted.": "Messages in this room are not end-to-end encrypted.",
|
"Messages in this room are not end-to-end encrypted.": "Messages in this room are not end-to-end encrypted.",
|
||||||
"Messages in this room are end-to-end encrypted.": "Messages in this room are end-to-end encrypted.",
|
"Messages in this room are end-to-end encrypted.": "Messages in this room are end-to-end encrypted.",
|
||||||
"Verify": "Verify",
|
|
||||||
"Security": "Security",
|
"Security": "Security",
|
||||||
"Sunday": "Sunday",
|
"Sunday": "Sunday",
|
||||||
"Monday": "Monday",
|
"Monday": "Monday",
|
||||||
|
@ -1442,14 +1445,6 @@
|
||||||
"View Servers in Room": "View Servers in Room",
|
"View Servers in Room": "View Servers in Room",
|
||||||
"Toolbox": "Toolbox",
|
"Toolbox": "Toolbox",
|
||||||
"Developer Tools": "Developer Tools",
|
"Developer Tools": "Developer Tools",
|
||||||
"Failed to find the following users": "Failed to find the following users",
|
|
||||||
"The following users might not exist or are invalid, and cannot be invited: %(csvNames)s": "The following users might not exist or are invalid, and cannot be invited: %(csvNames)s",
|
|
||||||
"Recent Conversations": "Recent Conversations",
|
|
||||||
"Suggestions": "Suggestions",
|
|
||||||
"Show more": "Show more",
|
|
||||||
"Direct Messages": "Direct Messages",
|
|
||||||
"If you can't find someone, ask them for their username, or share your username (%(userId)s) or <a>profile link</a>.": "If you can't find someone, ask them for their username, or share your username (%(userId)s) or <a>profile link</a>.",
|
|
||||||
"Go": "Go",
|
|
||||||
"An error has occurred.": "An error has occurred.",
|
"An error has occurred.": "An error has occurred.",
|
||||||
"Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.": "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.",
|
"Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.": "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.",
|
||||||
"Verifying this user will mark their device as trusted, and also mark your device as trusted to them.": "Verifying this user will mark their device as trusted, and also mark your device as trusted to them.",
|
"Verifying this user will mark their device as trusted, and also mark your device as trusted to them.": "Verifying this user will mark their device as trusted, and also mark your device as trusted to them.",
|
||||||
|
@ -1459,6 +1454,20 @@
|
||||||
"Enable 'Manage Integrations' in Settings to do this.": "Enable 'Manage Integrations' in Settings to do this.",
|
"Enable 'Manage Integrations' in Settings to do this.": "Enable 'Manage Integrations' in Settings to do this.",
|
||||||
"Integrations not allowed": "Integrations not allowed",
|
"Integrations not allowed": "Integrations not allowed",
|
||||||
"Your Riot doesn't allow you to use an Integration Manager to do this. Please contact an admin.": "Your Riot doesn't allow you to use an Integration Manager to do this. Please contact an admin.",
|
"Your Riot doesn't allow you to use an Integration Manager to do this. Please contact an admin.": "Your Riot doesn't allow you to use an Integration Manager to do this. Please contact an admin.",
|
||||||
|
"Failed to invite the following users to chat: %(csvUsers)s": "Failed to invite the following users to chat: %(csvUsers)s",
|
||||||
|
"We couldn't create your DM. Please check the users you want to invite and try again.": "We couldn't create your DM. Please check the users you want to invite and try again.",
|
||||||
|
"Something went wrong trying to invite the users.": "Something went wrong trying to invite the users.",
|
||||||
|
"We couldn't invite those users. Please check the users you want to invite and try again.": "We couldn't invite those users. Please check the users you want to invite and try again.",
|
||||||
|
"Failed to find the following users": "Failed to find the following users",
|
||||||
|
"The following users might not exist or are invalid, and cannot be invited: %(csvNames)s": "The following users might not exist or are invalid, and cannot be invited: %(csvNames)s",
|
||||||
|
"Recent Conversations": "Recent Conversations",
|
||||||
|
"Suggestions": "Suggestions",
|
||||||
|
"Recently Direct Messaged": "Recently Direct Messaged",
|
||||||
|
"Show more": "Show more",
|
||||||
|
"Direct Messages": "Direct Messages",
|
||||||
|
"If you can't find someone, ask them for their username, or share your username (%(userId)s) or <a>profile link</a>.": "If you can't find someone, ask them for their username, or share your username (%(userId)s) or <a>profile link</a>.",
|
||||||
|
"Go": "Go",
|
||||||
|
"If you can't find someone, ask them for their username (e.g. @user:server.com) or <a>share this room</a>.": "If you can't find someone, ask them for their username (e.g. @user:server.com) or <a>share this room</a>.",
|
||||||
"You added a new device '%(displayName)s', which is requesting encryption keys.": "You added a new device '%(displayName)s', which is requesting encryption keys.",
|
"You added a new device '%(displayName)s', which is requesting encryption keys.": "You added a new device '%(displayName)s', which is requesting encryption keys.",
|
||||||
"Your unverified device '%(displayName)s' is requesting encryption keys.": "Your unverified device '%(displayName)s' is requesting encryption keys.",
|
"Your unverified device '%(displayName)s' is requesting encryption keys.": "Your unverified device '%(displayName)s' is requesting encryption keys.",
|
||||||
"Start verification": "Start verification",
|
"Start verification": "Start verification",
|
||||||
|
|
|
@ -83,6 +83,7 @@ export class IntegrationManagers {
|
||||||
}
|
}
|
||||||
|
|
||||||
async _setupHomeserverManagers() {
|
async _setupHomeserverManagers() {
|
||||||
|
if (!MatrixClientPeg.get()) return;
|
||||||
try {
|
try {
|
||||||
console.log("Updating homeserver-configured integration managers...");
|
console.log("Updating homeserver-configured integration managers...");
|
||||||
const homeserverDomain = MatrixClientPeg.getHomeserverName();
|
const homeserverDomain = MatrixClientPeg.getHomeserverName();
|
||||||
|
|
|
@ -130,7 +130,7 @@ export const SETTINGS = {
|
||||||
},
|
},
|
||||||
"feature_ftue_dms": {
|
"feature_ftue_dms": {
|
||||||
isFeature: true,
|
isFeature: true,
|
||||||
displayName: _td("New DM invite dialog (under development)"),
|
displayName: _td("New invite dialog"),
|
||||||
supportedLevels: LEVELS_FEATURE,
|
supportedLevels: LEVELS_FEATURE,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import EventEmitter from 'events';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds the active toasts
|
||||||
|
*/
|
||||||
|
export default class ToastStore extends EventEmitter {
|
||||||
|
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 = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
addOrReplaceToast(newToast) {
|
||||||
|
const oldIndex = this._toasts.findIndex(t => t.key === newToast.key);
|
||||||
|
if (oldIndex === -1) {
|
||||||
|
this._toasts.push(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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -124,6 +124,27 @@ export default class DMRoomMap {
|
||||||
return this._getUserToRooms()[userId] || [];
|
return this._getUserToRooms()[userId] || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the DM room which the given IDs share, if any.
|
||||||
|
* @param {string[]} ids The identifiers (user IDs and email addresses) to look for.
|
||||||
|
* @returns {Room} The DM room which all IDs given share, or falsey if no common room.
|
||||||
|
*/
|
||||||
|
getDMRoomForIdentifiers(ids) {
|
||||||
|
// TODO: [Canonical DMs] Handle lookups for email addresses.
|
||||||
|
// For now we'll pretend we only get user IDs and end up returning nothing for email addresses
|
||||||
|
|
||||||
|
let commonRooms = this.getDMRoomsForUserId(ids[0]);
|
||||||
|
for (let i = 1; i < ids.length; i++) {
|
||||||
|
const userRooms = this.getDMRoomsForUserId(ids[i]);
|
||||||
|
commonRooms = commonRooms.filter(r => userRooms.includes(r));
|
||||||
|
}
|
||||||
|
|
||||||
|
const joinedRooms = commonRooms.map(r => MatrixClientPeg.get().getRoom(r))
|
||||||
|
.filter(r => r && r.getMyMembership() === 'join');
|
||||||
|
|
||||||
|
return joinedRooms[0];
|
||||||
|
}
|
||||||
|
|
||||||
getUserIdForRoomId(roomId) {
|
getUserIdForRoomId(roomId) {
|
||||||
if (this.roomToUser == null) {
|
if (this.roomToUser == null) {
|
||||||
// we lazily populate roomToUser so you can use
|
// we lazily populate roomToUser so you can use
|
||||||
|
|
91
yarn.lock
91
yarn.lock
|
@ -1570,6 +1570,14 @@ anymatch@^2.0.0:
|
||||||
micromatch "^3.1.4"
|
micromatch "^3.1.4"
|
||||||
normalize-path "^2.1.1"
|
normalize-path "^2.1.1"
|
||||||
|
|
||||||
|
anymatch@~3.1.1:
|
||||||
|
version "3.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142"
|
||||||
|
integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==
|
||||||
|
dependencies:
|
||||||
|
normalize-path "^3.0.0"
|
||||||
|
picomatch "^2.0.4"
|
||||||
|
|
||||||
aproba@^1.0.3, aproba@^1.1.1:
|
aproba@^1.0.3, aproba@^1.1.1:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
|
resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
|
||||||
|
@ -1884,6 +1892,11 @@ binary-extensions@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65"
|
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65"
|
||||||
integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==
|
integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==
|
||||||
|
|
||||||
|
binary-extensions@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c"
|
||||||
|
integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==
|
||||||
|
|
||||||
bluebird@^3.5.0, bluebird@^3.5.5:
|
bluebird@^3.5.0, bluebird@^3.5.5:
|
||||||
version "3.7.2"
|
version "3.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
|
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
|
||||||
|
@ -1928,6 +1941,13 @@ braces@^2.3.1, braces@^2.3.2:
|
||||||
split-string "^3.0.2"
|
split-string "^3.0.2"
|
||||||
to-regex "^3.0.1"
|
to-regex "^3.0.1"
|
||||||
|
|
||||||
|
braces@~3.0.2:
|
||||||
|
version "3.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
|
||||||
|
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
|
||||||
|
dependencies:
|
||||||
|
fill-range "^7.0.1"
|
||||||
|
|
||||||
brorand@^1.0.1:
|
brorand@^1.0.1:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
|
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
|
||||||
|
@ -2232,7 +2252,7 @@ cheerio@^1.0.0-rc.3:
|
||||||
lodash "^4.15.0"
|
lodash "^4.15.0"
|
||||||
parse5 "^3.0.1"
|
parse5 "^3.0.1"
|
||||||
|
|
||||||
chokidar@^2.0.2, chokidar@^2.1.2, chokidar@^2.1.8:
|
chokidar@^2.0.2, chokidar@^2.1.8:
|
||||||
version "2.1.8"
|
version "2.1.8"
|
||||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917"
|
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917"
|
||||||
integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==
|
integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==
|
||||||
|
@ -2251,6 +2271,21 @@ chokidar@^2.0.2, chokidar@^2.1.2, chokidar@^2.1.8:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents "^1.2.7"
|
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==
|
||||||
|
dependencies:
|
||||||
|
anymatch "~3.1.1"
|
||||||
|
braces "~3.0.2"
|
||||||
|
glob-parent "~5.1.0"
|
||||||
|
is-binary-path "~2.1.0"
|
||||||
|
is-glob "~4.0.1"
|
||||||
|
normalize-path "~3.0.0"
|
||||||
|
readdirp "~3.3.0"
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents "~2.1.2"
|
||||||
|
|
||||||
chownr@^1.1.1:
|
chownr@^1.1.1:
|
||||||
version "1.1.3"
|
version "1.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142"
|
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142"
|
||||||
|
@ -3654,6 +3689,13 @@ fill-range@^4.0.0:
|
||||||
repeat-string "^1.6.1"
|
repeat-string "^1.6.1"
|
||||||
to-regex-range "^2.1.0"
|
to-regex-range "^2.1.0"
|
||||||
|
|
||||||
|
fill-range@^7.0.1:
|
||||||
|
version "7.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
|
||||||
|
integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
|
||||||
|
dependencies:
|
||||||
|
to-regex-range "^5.0.1"
|
||||||
|
|
||||||
find-cache-dir@^2.0.0, find-cache-dir@^2.1.0:
|
find-cache-dir@^2.0.0, find-cache-dir@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7"
|
resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7"
|
||||||
|
@ -3815,6 +3857,11 @@ fsevents@^1.2.7:
|
||||||
nan "^2.12.1"
|
nan "^2.12.1"
|
||||||
node-pre-gyp "^0.12.0"
|
node-pre-gyp "^0.12.0"
|
||||||
|
|
||||||
|
fsevents@~2.1.2:
|
||||||
|
version "2.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805"
|
||||||
|
integrity sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==
|
||||||
|
|
||||||
function-bind@^1.1.1:
|
function-bind@^1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
|
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
|
||||||
|
@ -3909,6 +3956,13 @@ glob-parent@^3.1.0:
|
||||||
is-glob "^3.1.0"
|
is-glob "^3.1.0"
|
||||||
path-dirname "^1.0.0"
|
path-dirname "^1.0.0"
|
||||||
|
|
||||||
|
glob-parent@~5.1.0:
|
||||||
|
version "5.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2"
|
||||||
|
integrity sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==
|
||||||
|
dependencies:
|
||||||
|
is-glob "^4.0.1"
|
||||||
|
|
||||||
glob-to-regexp@^0.3.0:
|
glob-to-regexp@^0.3.0:
|
||||||
version "0.3.0"
|
version "0.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
|
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
|
||||||
|
@ -4463,6 +4517,13 @@ is-binary-path@^1.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
binary-extensions "^1.0.0"
|
binary-extensions "^1.0.0"
|
||||||
|
|
||||||
|
is-binary-path@~2.1.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
|
||||||
|
integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
|
||||||
|
dependencies:
|
||||||
|
binary-extensions "^2.0.0"
|
||||||
|
|
||||||
is-boolean-object@^1.0.0:
|
is-boolean-object@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.0.tgz#98f8b28030684219a95f375cfbd88ce3405dff93"
|
resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.0.tgz#98f8b28030684219a95f375cfbd88ce3405dff93"
|
||||||
|
@ -4622,7 +4683,7 @@ is-glob@^3.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-extglob "^2.1.0"
|
is-extglob "^2.1.0"
|
||||||
|
|
||||||
is-glob@^4.0.0, is-glob@^4.0.1:
|
is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
|
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
|
||||||
integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
|
integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
|
||||||
|
@ -4663,6 +4724,11 @@ is-number@^3.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
kind-of "^3.0.2"
|
kind-of "^3.0.2"
|
||||||
|
|
||||||
|
is-number@^7.0.0:
|
||||||
|
version "7.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
|
||||||
|
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
|
||||||
|
|
||||||
is-obj@^1.0.0:
|
is-obj@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
|
resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
|
||||||
|
@ -6104,7 +6170,7 @@ normalize-path@^2.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
remove-trailing-separator "^1.0.1"
|
remove-trailing-separator "^1.0.1"
|
||||||
|
|
||||||
normalize-path@^3.0.0:
|
normalize-path@^3.0.0, normalize-path@~3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
|
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
|
||||||
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
|
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
|
||||||
|
@ -6578,6 +6644,11 @@ performance-now@^2.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
|
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
|
||||||
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
|
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
|
||||||
|
|
||||||
|
picomatch@^2.0.4, picomatch@^2.0.7:
|
||||||
|
version "2.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.1.tgz#21bac888b6ed8601f831ce7816e335bc779f0a4a"
|
||||||
|
integrity sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA==
|
||||||
|
|
||||||
pify@^3.0.0:
|
pify@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
|
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
|
||||||
|
@ -7169,6 +7240,13 @@ readdirp@^2.2.1:
|
||||||
micromatch "^3.1.10"
|
micromatch "^3.1.10"
|
||||||
readable-stream "^2.0.2"
|
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==
|
||||||
|
dependencies:
|
||||||
|
picomatch "^2.0.7"
|
||||||
|
|
||||||
realpath-native@^1.1.0:
|
realpath-native@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.1.0.tgz#2003294fea23fb0672f2476ebe22fcf498a2d65c"
|
resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.1.0.tgz#2003294fea23fb0672f2476ebe22fcf498a2d65c"
|
||||||
|
@ -8375,6 +8453,13 @@ to-regex-range@^2.1.0:
|
||||||
is-number "^3.0.0"
|
is-number "^3.0.0"
|
||||||
repeat-string "^1.6.1"
|
repeat-string "^1.6.1"
|
||||||
|
|
||||||
|
to-regex-range@^5.0.1:
|
||||||
|
version "5.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
|
||||||
|
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
|
||||||
|
dependencies:
|
||||||
|
is-number "^7.0.0"
|
||||||
|
|
||||||
to-regex@^3.0.1, to-regex@^3.0.2:
|
to-regex@^3.0.1, to-regex@^3.0.2:
|
||||||
version "3.0.2"
|
version "3.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce"
|
resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce"
|
||||||
|
|
Loading…
Reference in New Issue